diff --git a/.travis.yml b/.travis.yml index 5d94d6a86..323d3a097 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,47 +7,37 @@ cache: - tmp/cache/assets/test/sprockets env: matrix: - - GROWSTUFF_ELASTICSEARCH='true' RSPEC_TAG=elasticsearch STATIC_CHECKS=false - - GROWSTUFF_ELASTICSEARCH='false' RSPEC_TAG=~elasticsearch STATIC_CHECKS=false + - GROWSTUFF_ELASTICSEARCH=true RSPEC_TAG=elasticsearch COVERAGE=true + - GROWSTUFF_ELASTICSEARCH=false RSPEC_TAG=~elasticsearch COVERAGE=false - STATIC_CHECKS=true global: - GROWSTUFF_SITE_NAME="Growstuff (travis)" - RAILS_SECRET_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' - secure: "Z5TpM2jEX4UCvNePnk/LwltQX48U2u9BRc+Iypr1x9QW2o228QJhPIOH39a8RMUrepGnkQIq9q3ZRUn98RfrJz1yThtlNFL3NmzdQ57gKgjGwfpa0e4Dwj/ZJqV2D84tDGjvdVYLP7zzaYZxQcwk/cgNpzKf/jq97HLNP7CYuf4=" before_install: - - ./script/install_phantomjs; + - ./script/install_phantomjs.sh - export PATH=$PWD/travis_phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH + - ./script/install_codeclimate.sh + - ./script/install_linters.sh # Force Travis to use Elastic Search 2.4.0 - - > - if [ "${GROWSTUFF_ELASTICSEARCH}" = "true" ]; then - sudo dpkg -r elasticsearch; - curl -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.4.0/elasticsearch-2.4.0.deb; - sudo dpkg -i --force-confnew elasticsearch-2.4.0.deb; - sudo service elasticsearch start; - sleep 10; - curl localhost:9200; - fi -before_script: - - set -e - - > - if [ "${STATIC_CHECKS}" = "true" ]; then - ./script/install_linters; - else - RAILS_ENV=test bundle exec rake db:create db:migrate; - bundle exec rake assets:precompile; - fi - - set +e + - ./script/install_elasticsearch.sh script: - set -e - > if [ "${STATIC_CHECKS}" = "true" ]; then ./script/check_static.rb else - bundle exec rake db:migrate --trace; + set +e; + RAILS_ENV=test bundle exec rake db:create db:migrate; + bundle exec rake assets:precompile; bundle exec rspec --tag $RSPEC_TAG spec/; - bundle exec rake jasmine:ci; fi; - set +e +after_script: + - > + if [ "${COVERAGE}" = "true" ]; then + ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT; + fi before_deploy: - bundle exec script/heroku_maintenance.rb on deploy: @@ -70,4 +60,5 @@ after_deploy: - bundle exec script/heroku_maintenance.rb off addons: code_climate: - repo_token: 462e015bbdaabfb20910fc07f2fea253410ecb131444e00f97dbf32dc6789ca6 + repo_token: + secure: "PfhLGBKRgNqhKuYCJsK+VPhdAzcgWFGeeOyxC/eS8gtlvIISVdgyZE+r30uIei0DFI6zEiN62eW4d+xtT4j7/e2ZcAcx7U52mza/SnQNuu3nCGQDJB8VOvV5NbnwXfi8vfr4e889Mt7k3ocd2c4gqB4UtRqrzhygj7HN+B/GfEk=" diff --git a/Gemfile b/Gemfile index 55be65010..bc3d45a34 100644 --- a/Gemfile +++ b/Gemfile @@ -137,7 +137,6 @@ group :development, :test do gem 'haml-rails' # HTML templating language gem 'haml_lint' # Checks haml files for goodness gem 'i18n-tasks' # adds tests for finding missing and unused translations - gem 'jasmine' # javascript unit testing gem 'poltergeist' # for headless JS testing gem 'rainbow', '< 2.2.0' # See https://github.com/sickill/rainbow/issues/44 gem 'rspec-activemodel-mocks' diff --git a/Gemfile.lock b/Gemfile.lock index b15ea0120..8f7585851 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,8 +26,8 @@ GEM addressable active_median (0.1.4) activerecord - active_utils (3.3.9) - activesupport (>= 3.2, < 5.2.0) + active_utils (3.3.11) + activesupport (>= 3.2, < 6.0) i18n activejob (4.2.10) activesupport (= 4.2.10) @@ -51,7 +51,7 @@ GEM public_suffix (>= 2.0.2, < 4.0) arel (6.0.4) ast (2.4.0) - autoprefixer-rails (7.2.5) + autoprefixer-rails (8.2.0) execjs bcrypt (3.1.11) better_errors (2.2.0) @@ -61,10 +61,8 @@ GEM binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) bluecloth (2.2.0) - bonsai-elasticsearch-rails (0.2.0) - elasticsearch-model (~> 0) - elasticsearch-rails (~> 0) - bootstrap-datepicker-rails (1.7.1.1) + bonsai-elasticsearch-rails (0.0.4) + bootstrap-datepicker-rails (1.8.0.1) railties (>= 3.0) bootstrap-kaminari-views (0.0.5) kaminari (>= 0.13) @@ -74,12 +72,12 @@ GEM sass (>= 3.3.4) bootstrap_form (2.7.0) builder (3.2.3) - bullet (5.7.2) + bullet (5.7.5) activesupport (>= 3.0.0) uniform_notifier (~> 1.11.0) - byebug (10.0.0) + byebug (10.0.2) cancancan (2.1.3) - capybara (2.17.0) + capybara (2.18.0) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) @@ -92,13 +90,11 @@ GEM capybara-screenshot (1.0.18) capybara (>= 1.0, < 3) launchy - chartkick (2.2.5) - childprocess (0.8.0) + chartkick (2.3.3) + childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) climate_control (0.2.0) cliver (0.3.2) - cocaine (0.5.8) - climate_control (>= 0.0.3, < 1.0) codeclimate-test-reporter (1.0.8) simplecov (<= 0.13) codemirror-rails (5.16.0) @@ -137,13 +133,13 @@ GEM crass (1.0.3) csv_shaper (1.3.0) activesupport (>= 3.0.0) - dalli (2.7.6) + dalli (2.7.7) database_cleaner (1.6.2) debug_inspector (0.0.3) - devise (4.4.1) + devise (4.4.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) - railties (>= 4.1.0, < 5.2) + railties (>= 4.1.0, < 6.0) responders warden (~> 1.2.3) diff-lcs (1.3) @@ -156,16 +152,16 @@ GEM elasticsearch-transport (= 2.0.2) elasticsearch-api (2.0.2) multi_json - elasticsearch-model (0.1.9) + elasticsearch-model (5.0.0) activesupport (> 3) - elasticsearch (> 0.4) + elasticsearch (> 1) hashie - elasticsearch-rails (0.1.9) + elasticsearch-rails (5.0.2) elasticsearch-transport (2.0.2) faraday multi_json erubis (2.7.0) - excon (0.60.0) + excon (0.62.0) execjs (2.7.0) factory_bot (4.8.2) activesupport (>= 3.0.0) @@ -176,16 +172,16 @@ GEM i18n (>= 0.7) faraday (0.12.2) multipart-post (>= 1.2, < 3) - ffi (1.9.21) + ffi (1.9.23) figaro (1.1.1) thor (~> 0.14) flickraw (0.9.9) - font-awesome-sass (4.7.0) + font-awesome-sass (5.0.9) sass (>= 3.2) formatador (0.2.5) friendly_id (5.2.3) activerecord (>= 4.0.0) - geocoder (1.4.5) + geocoder (1.4.7) gibbon (1.2.1) httparty multi_json (>= 1.9.0) @@ -241,9 +237,9 @@ GEM haml (>= 4.0, < 6) nokogiri (>= 1.6.0) ruby_parser (~> 3.5) - httparty (0.15.6) + httparty (0.16.2) multi_xml (>= 0.5.2) - i18n (0.9.3) + i18n (0.9.5) concurrent-ruby (~> 1.0) i18n-tasks (0.9.12) activesupport (>= 4.0.2) @@ -255,12 +251,6 @@ GEM parser (>= 2.2.3.0) term-ansicolor (>= 1.3.2) terminal-table (>= 1.5.1) - jasmine (2.9.0) - jasmine-core (>= 2.9.0, < 3.0.0) - phantomjs - rack (>= 1.2.1) - rake - jasmine-core (2.99.0) jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -300,10 +290,10 @@ GEM rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - loofah (2.2.1) + loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) - lumberjack (1.0.12) + lumberjack (1.0.13) mail (2.7.0) mini_mime (>= 0.1.1) memcachier (0.0.2) @@ -320,7 +310,7 @@ GEM multi_xml (0.6.0) multipart-post (2.0.0) nenv (0.3.0) - newrelic_rpm (4.8.0.341) + newrelic_rpm (5.0.0.342) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) notiffany (0.1.1) @@ -351,17 +341,16 @@ GEM omniauth-oauth (~> 1.1) rack orm_adapter (0.5.0) - paperclip (5.2.1) + paperclip (6.0.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) - cocaine (~> 0.5.5) mime-types mimemagic (~> 0.3.0) + terrapin (~> 0.6.0) parallel (1.12.1) - parser (2.4.0.2) - ast (~> 2.3) + parser (2.5.0.5) + ast (~> 2.4.0) pg (0.21.0) - phantomjs (2.1.1.0) platform-api (2.1.0) heroics (~> 0.0.23) moneta (~> 0.8.1) @@ -375,7 +364,7 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) - public_suffix (3.0.1) + public_suffix (3.0.2) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) rack (1.6.9) @@ -403,8 +392,8 @@ GEM activesupport (>= 4.2.0, < 5.0) nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) rails-i18n (4.0.9) i18n (~> 0.7) railties (~> 4.0) @@ -420,8 +409,8 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (2.1.0) raindrops (0.19.0) - rake (12.3.0) - rb-fsevent (0.10.2) + rake (12.3.1) + rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) redis (4.0.1) @@ -461,12 +450,12 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) - ruby-units (2.2.1) + ruby-units (2.3.0) ruby_dep (1.5.0) - ruby_parser (3.10.1) + ruby_parser (3.11.0) sexp_processor (~> 4.9) rubyzip (1.2.1) - sass (3.5.5) + sass (3.5.6) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -477,16 +466,16 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - selenium-webdriver (3.9.0) + selenium-webdriver (3.11.0) childprocess (~> 0.5) rubyzip (~> 1.2) - sexp_processor (4.10.0) + sexp_processor (4.10.1) shellany (0.0.1) - sidekiq (5.1.0) + sidekiq (5.1.2) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (>= 3.3.4, < 5) + redis (>= 3.3.5, < 5) simplecov (0.12.0) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -507,6 +496,8 @@ GEM tins (~> 1.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + terrapin (0.6.0) + climate_control (>= 0.0.3, < 1.0) thor (0.19.4) thread (0.2.2) thread_safe (0.3.6) @@ -516,7 +507,7 @@ GEM trollop (1.16.2) tzinfo (1.2.5) thread_safe (~> 0.1) - uglifier (4.1.5) + uglifier (4.1.8) execjs (>= 0.3.0, < 3) unicode-display_width (1.3.0) unicorn (5.4.0) @@ -587,7 +578,6 @@ DEPENDENCIES haml_lint hashie (>= 3.5.3) i18n-tasks - jasmine jquery-rails jquery-ui-rails (~> 5.0.2) js-routes @@ -631,5 +621,6 @@ DEPENDENCIES RUBY VERSION ruby 2.4.1p111 + BUNDLED WITH 1.16.1 diff --git a/app/assets/stylesheets/overrides.sass b/app/assets/stylesheets/overrides.sass index 338a2dbc5..6a97975fb 100644 --- a/app/assets/stylesheets/overrides.sass +++ b/app/assets/stylesheets/overrides.sass @@ -146,6 +146,7 @@ p.stats border: none text-align: center margin-bottom: 1.5em + max-width: 160px .member-thumbnail text-align: left @@ -221,6 +222,7 @@ footer #maincontainer min-height: 80% + padding: 50px html, body height: 100% @@ -334,3 +336,6 @@ ul.thumbnail-buttons height: 180px .seed-thumbnail height: 220px + + #maincontainer + padding: 10px diff --git a/app/models/concerns/ownable.rb b/app/models/concerns/ownable.rb new file mode 100644 index 000000000..b9f229973 --- /dev/null +++ b/app/models/concerns/ownable.rb @@ -0,0 +1,7 @@ +module Ownable + extend ActiveSupport::Concern + + included do + belongs_to :owner, class_name: 'Member', foreign_key: 'owner_id', counter_cache: true + end +end diff --git a/app/models/crop.rb b/app/models/crop.rb index 682610436..bead8e89b 100644 --- a/app/models/crop.rb +++ b/app/models/crop.rb @@ -11,28 +11,26 @@ class Crop < ActiveRecord::Base has_many :scientific_names, after_add: :update_index, after_remove: :update_index, dependent: :destroy accepts_nested_attributes_for :scientific_names, allow_destroy: true, reject_if: :all_blank has_many :alternate_names, after_add: :update_index, after_remove: :update_index, dependent: :destroy - has_many :plantings + has_many :plantings, dependent: :destroy + has_many :seeds, dependent: :destroy + has_many :harvests, dependent: :destroy has_many :photos, through: :plantings - has_many :seeds - has_many :harvests has_many :plant_parts, -> { uniq.reorder("plant_parts.name") }, through: :harvests belongs_to :creator, class_name: 'Member' belongs_to :requester, class_name: 'Member' belongs_to :parent, class_name: 'Crop' - has_many :varieties, class_name: 'Crop', foreign_key: 'parent_id' + has_many :varieties, class_name: 'Crop', foreign_key: 'parent_id', dependent: :nullify has_and_belongs_to_many :posts # rubocop:disable Rails/HasAndBelongsToMany ## ## Scopes scope :recent, -> { approved.order(created_at: :desc) } scope :toplevel, -> { approved.where(parent_id: nil) } - scope :popular, -> { approved.reorder("plantings_count desc, lower(name) asc") } - # ok on sqlite and psql, but not on mysql - scope :randomized, -> { approved.reorder('random()') } + scope :popular, -> { approved.order("plantings_count desc, lower(name) asc") } scope :pending_approval, -> { where(approval_status: "pending") } scope :approved, -> { where(approval_status: "approved") } scope :rejected, -> { where(approval_status: "rejected") } - scope :interesting, -> { approved.has_photos.randomized } + scope :interesting, -> { approved.has_photos } scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) } ## diff --git a/app/models/forum.rb b/app/models/forum.rb index 7cbc0d43d..34afad592 100644 --- a/app/models/forum.rb +++ b/app/models/forum.rb @@ -1,10 +1,10 @@ class Forum < ActiveRecord::Base extend FriendlyId + include Ownable validates :name, presence: true friendly_id :name, use: %i(slugged finders) has_many :posts - belongs_to :owner, class_name: "Member" def to_s name diff --git a/app/models/garden.rb b/app/models/garden.rb index 0c3378b00..682796674 100644 --- a/app/models/garden.rb +++ b/app/models/garden.rb @@ -2,9 +2,9 @@ class Garden < ActiveRecord::Base extend FriendlyId include Geocodable include PhotoCapable + include Ownable friendly_id :garden_slug, use: %i(slugged finders) - belongs_to :owner, class_name: 'Member', foreign_key: 'owner_id', counter_cache: true has_many :plantings, dependent: :destroy has_many :crops, through: :plantings diff --git a/app/models/harvest.rb b/app/models/harvest.rb index 9bc4195b4..2b0a5a3a1 100644 --- a/app/models/harvest.rb +++ b/app/models/harvest.rb @@ -2,6 +2,7 @@ class Harvest < ActiveRecord::Base include ActionView::Helpers::NumberHelper extend FriendlyId include PhotoCapable + include Ownable friendly_id :harvest_slug, use: %i(slugged finders) @@ -33,7 +34,6 @@ class Harvest < ActiveRecord::Base ## ## Relationships belongs_to :crop - belongs_to :owner, class_name: 'Member', counter_cache: true belongs_to :plant_part belongs_to :planting diff --git a/app/models/photo.rb b/app/models/photo.rb index 07531672b..75a35b10a 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -1,5 +1,5 @@ class Photo < ActiveRecord::Base - belongs_to :owner, class_name: 'Member' + include Ownable PHOTO_CAPABLE = %w(Garden Planting Harvest Seed).freeze diff --git a/app/models/planting.rb b/app/models/planting.rb index e358a6bb4..3dff4c006 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -2,6 +2,7 @@ class Planting < ActiveRecord::Base extend FriendlyId include PhotoCapable include Finishable + include Ownable friendly_id :planting_slug, use: %i(slugged finders) # Constants @@ -17,7 +18,6 @@ class Planting < ActiveRecord::Base before_save :calculate_lifespan belongs_to :garden - belongs_to :owner, class_name: 'Member', counter_cache: true belongs_to :crop, counter_cache: true has_many :harvests, dependent: :destroy diff --git a/app/models/seed.rb b/app/models/seed.rb index 63c88a47c..e83747b77 100644 --- a/app/models/seed.rb +++ b/app/models/seed.rb @@ -2,6 +2,7 @@ class Seed < ActiveRecord::Base extend FriendlyId include PhotoCapable include Finishable + include Ownable friendly_id :seed_slug, use: %i(slugged finders) TRADABLE_TO_VALUES = %w(nowhere locally nationally internationally).freeze @@ -12,8 +13,6 @@ class Seed < ActiveRecord::Base # # Relationships belongs_to :crop - belongs_to :owner, class_name: 'Member', foreign_key: 'owner_id', counter_cache: true - belongs_to :parent_planting, class_name: 'Planting', foreign_key: 'parent_planting_id' # parent has_many :child_plantings, class_name: 'Planting', foreign_key: 'parent_seed_id', dependent: :nullify # children diff --git a/app/views/crops/_plantings.html.haml b/app/views/crops/_plantings.html.haml index 351c6ba4c..ca05632a3 100644 --- a/app/views/crops/_plantings.html.haml +++ b/app/views/crops/_plantings.html.haml @@ -4,9 +4,9 @@ Nobody has planted this crop yet. - else %ul - - crop.plantings.take(3).each do |planting| + - crop.plantings.order(planted_at: :desc).limit(3).each do |planting| %li - = link_to display_planting(planting), planting_path(planting) + = link_to planting, planting_path(planting) = render partial: 'members/location', locals: { member: planting.owner } %small = distance_of_time_in_words(planting.created_at, Time.zone.now) @@ -18,4 +18,3 @@ %p= link_to "Plant #{crop.name}", new_planting_path(crop_id: crop.id) - else = render partial: 'shared/signin_signup', locals: { to: "track your #{crop.name} plantings" } - diff --git a/app/views/harvests/_list.html.haml b/app/views/harvests/_list.html.haml index bb0178238..8e4e1a7a1 100644 --- a/app/views/harvests/_list.html.haml +++ b/app/views/harvests/_list.html.haml @@ -1,11 +1,10 @@ - harvests.each do |h| - cache h do .row - .col-md-3.col-xs-4{ style: 'padding-bottom: 6px' } + .col-lg-6.col-md-3.col-xs-4.homepage-listing = render 'harvests/image_with_popover', harvest: h - .col-md-9.col-xs-4 + .col-lg-3.col-md-9.col-xs-4 = link_to h.crop, crop_path(h.crop) %br/ %small - %i - = h.owner.location + %i= h.owner.location diff --git a/app/views/home/_blurb.html.haml b/app/views/home/_blurb.html.haml index 0459d1095..81e65510f 100644 --- a/app/views/home/_blurb.html.haml +++ b/app/views/home/_blurb.html.haml @@ -1,16 +1,13 @@ -.col-md-12 - -%h1= ENV['GROWSTUFF_SITE_NAME'] - -.row - .col-md-8.info - %p= t('.intro', site_name: ENV['GROWSTUFF_SITE_NAME']) - - = render partial: 'stats' - .col-md-4.signup - %p= t('.perks') - %p= link_to(t('.sign_up'), new_member_registration_path, class: 'btn btn-primary btn-lg') - %p - %small - = t('.already_html', sign_in: link_to(t('.sign_in_linktext'), new_member_session_path)) - +.container + .row + .col-md-12 + %h1= ENV['GROWSTUFF_SITE_NAME'] + .col-md-8.info + %p= t('.intro', site_name: ENV['GROWSTUFF_SITE_NAME']) + = render partial: 'stats' + .col-md-4.signup + %p= t('.perks') + %p= link_to(t('.sign_up'), new_member_registration_path, class: 'btn btn-primary btn-lg') + %p + %small + = t('.already_html', sign_in: link_to(t('.sign_in_linktext'), new_member_session_path)) diff --git a/app/views/home/_crops.html.haml b/app/views/home/_crops.html.haml index bfcbc9a71..8cffe4b78 100644 --- a/app/views/home/_crops.html.haml +++ b/app/views/home/_crops.html.haml @@ -1,5 +1,6 @@ - cache cache_key_for(Crop, 'interesting'), expires_in: 1.day do .row %h2= t('.our_crops') - - Crop.interesting.includes(:scientific_names, :photos).limit(8).each do |c| - .col-md-4.col-sm-3.col-xs-6= render 'crops/thumbnail', crop: c + - Crop.interesting.includes(:scientific_names, :photos).shuffle.first(12).each do |c| + .col-lg-2.col-md-4.col-sm-3.col-xs-6 + = render 'crops/thumbnail', crop: c diff --git a/app/views/home/_harvests.html.haml b/app/views/home/_harvests.html.haml index d4c2a1117..2229eae63 100644 --- a/app/views/home/_harvests.html.haml +++ b/app/views/home/_harvests.html.haml @@ -1,3 +1,3 @@ - cache cache_key_for(Harvest) do %h2 Recently Harvested - = render 'harvests/list', harvests: Harvest.includes(:crop, :owner, :photos).has_photos.recent.first(5) + = render 'harvests/list', harvests: Harvest.includes(:crop, :owner, :photos).has_photos.recent.first(6) diff --git a/app/views/home/_plantings.html.haml b/app/views/home/_plantings.html.haml index 595ad4586..dd631d2ff 100644 --- a/app/views/home/_plantings.html.haml +++ b/app/views/home/_plantings.html.haml @@ -1,3 +1,3 @@ - cache cache_key_for(Planting, 'home'), expires_in: 1.day do %h2= t('.recently_planted') - = render 'plantings/list', plantings: Planting.includes(:crop, garden: :owner).has_photos.recent.limit(5) + = render 'plantings/list', plantings: Planting.includes(:crop, garden: :owner).has_photos.recent.limit(6) diff --git a/app/views/home/_seeds.html.haml b/app/views/home/_seeds.html.haml index d53759738..4a28f9590 100644 --- a/app/views/home/_seeds.html.haml +++ b/app/views/home/_seeds.html.haml @@ -1,7 +1,7 @@ - cache cache_key_for(Seed, 'interesting'), expires_in: 1.day do %h2= t('.title') .row - - Seed.current.tradable.order(created_at: :desc).limit(6).each do |seed| + - Seed.current.tradable.includes(:owner, :crop).order(created_at: :desc).limit(6).each do |seed| .col-md-2.col-sm-2.col-xs-6 .thumbnail.seed-thumbnail - cache cache_key_for(Crop, seed.id) do diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index c28a8985b..d05d6a37a 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -19,11 +19,11 @@ = render 'blurb' .row - .col-md-6.col-sm-12 + .col-lg-8.col-md-6.col-sm-12 = render 'crops' - .col-md-3.col-sm-6 + .col-lg-2.col-md-3.col-sm-6 = render 'plantings' - .col-md-3.col-sm-6 + .col-lg-2.col-md-3.col-sm-6 = render 'harvests' .col-md-12 - cache cache_key_for(Crop, 'recent') do diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2c0f6b9bb..6b9cfd40f 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -5,7 +5,7 @@ %body = render partial: "layouts/header" - .container#maincontainer + #maincontainer .row .col-md-12 - if content_for?(:title) diff --git a/app/views/plantings/_image_with_popover.html.haml b/app/views/plantings/_image_with_popover.html.haml index 70bfc1836..740562fda 100644 --- a/app/views/plantings/_image_with_popover.html.haml +++ b/app/views/plantings/_image_with_popover.html.haml @@ -5,6 +5,6 @@ planting, rel: "popover", 'data-trigger': 'hover', - 'data-title': planting.to_s, + 'data-title': planting.crop.name, 'data-content': render('plantings/popover', planting: planting), 'data-html': true diff --git a/app/views/plantings/_list.html.haml b/app/views/plantings/_list.html.haml index aa7dcf9fa..21be75416 100644 --- a/app/views/plantings/_list.html.haml +++ b/app/views/plantings/_list.html.haml @@ -1,15 +1,10 @@ - plantings.each do |p| - cache p do .row - .col-md-3.col-xs-4.homepage-listing + .col-lg-6.col-md-3.col-xs-4.homepage-listing = render 'plantings/image_with_popover', planting: p - .col-md-9.col-xs-4 + .col-lg-3.col-md-9.col-xs-4 = link_to p.crop, p.crop - in - = succeed "'s" do - = link_to p.garden.owner, p.garden.owner - = link_to display_garden_name(p.garden), p.garden %br/ %small - %i - = p.location + %i= p.location diff --git a/app/views/plantings/_popover.html.haml b/app/views/plantings/_popover.html.haml index d7a83f616..44fd1744f 100644 --- a/app/views/plantings/_popover.html.haml +++ b/app/views/plantings/_popover.html.haml @@ -1,7 +1,11 @@ -%p - %small - Quantity: - = planting.quantity ? planting.quantity : 'unknown' - %br/ - Planted on: - = planting.planted_at.to_s +- if planting.quantity.present? + %p + %small + Quantity: + = planting.quantity + +- if planting.planted_at.present? + %p + %small + Planted: + = planting.planted_at.to_s diff --git a/db/migrate/20180401220637_add_member_count_caches.rb b/db/migrate/20180401220637_add_member_count_caches.rb new file mode 100644 index 000000000..151f19fcd --- /dev/null +++ b/db/migrate/20180401220637_add_member_count_caches.rb @@ -0,0 +1,6 @@ +class AddMemberCountCaches < ActiveRecord::Migration + def change + add_column :members, :photos_count, :integer + add_column :members, :forums_count, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 7e9cad2e0..71338e4a2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180213005731) do +ActiveRecord::Schema.define(version: 20180401220637) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -318,6 +318,8 @@ ActiveRecord::Schema.define(version: 20180213005731) do t.integer "harvests_count" t.integer "seeds_count" t.datetime "deleted_at" + t.integer "photos_count" + t.integer "forums_count" end add_index "members", ["confirmation_token"], name: "index_members_on_confirmation_token", unique: true, using: :btree diff --git a/script/install_codeclimate.sh b/script/install_codeclimate.sh new file mode 100755 index 000000000..f48091a9a --- /dev/null +++ b/script/install_codeclimate.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [ "${COVERAGE}" = "true" ]; then + set -euv + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter; + chmod +x ./cc-test-reporter; +fi diff --git a/script/install_elasticsearch.sh b/script/install_elasticsearch.sh new file mode 100755 index 000000000..1543a71bc --- /dev/null +++ b/script/install_elasticsearch.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ "${GROWSTUFF_ELASTICSEARCH}" = "true" ]; then + set -euv + sudo dpkg -r elasticsearch + curl -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.4.0/elasticsearch-2.4.0.deb + sudo dpkg -i --force-confnew elasticsearch-2.4.0.deb + sudo service elasticsearch start + sleep 10 + curl -v localhost:9200 +fi \ No newline at end of file diff --git a/script/install_linters b/script/install_linters deleted file mode 100755 index b1d7534b1..000000000 --- a/script/install_linters +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -set -euv - -gem install --update overcommit rubocop haml-lint bundler-audit -npm install -pip install yamllint --user - -overcommit --install -overcommit --sign -overcommit --sign pre-commit - -bundle-audit update diff --git a/script/install_linters.sh b/script/install_linters.sh new file mode 100755 index 000000000..679a6b64a --- /dev/null +++ b/script/install_linters.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +if [ "${STATIC_CHECKS}" = "true" ]; then + set -euv + gem install --update overcommit rubocop haml-lint bundler-audit; + npm install; + pip install yamllint --user; + + overcommit --install; + overcommit --sign; + overcommit --sign pre-commit; + + bundle-audit update; +fi diff --git a/script/install_phantomjs b/script/install_phantomjs.sh similarity index 100% rename from script/install_phantomjs rename to script/install_phantomjs.sh diff --git a/spec/javascripts/support/jasmine.css b/spec/javascripts/support/jasmine.css deleted file mode 100644 index 4736015b6..000000000 --- a/spec/javascripts/support/jasmine.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - padding-top: 0; -} - -#jasmine_content { - width: 1000px; - height: 1000px; - position: absolute; - -webkit-transform: rotateY(-90deg); - -moz-transform: rotateY(-90deg); - -ms-transform: rotateY(-90deg); - transform: rotateY(-90deg); -} \ No newline at end of file diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml deleted file mode 100644 index 41a71db95..000000000 --- a/spec/javascripts/support/jasmine.yml +++ /dev/null @@ -1,123 +0,0 @@ -# src_files -# -# Return an array of filepaths relative to src_dir to include before jasmine specs. -# Default: [] -# -# EXAMPLE: -# -# src_files: -# - lib/source1.js -# - lib/source2.js -# - dist/**/*.js -# -src_files: - - assets/application.js - -# stylesheets -# -# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs. -# Default: [] -# -# EXAMPLE: -# -# stylesheets: -# - css/style.css -# - stylesheets/*.css -# -stylesheets: - - assets/application.css - - spec/javascripts/support/jasmine.css -# helpers -# -# Return an array of filepaths relative to spec_dir to include before jasmine specs. -# Default: ["helpers/**/*.js"] -# -# EXAMPLE: -# -# helpers: -# - helpers/**/*.js -# -helpers: - - 'support/**/*.js' - -# spec_files -# -# Return an array of filepaths relative to spec_dir to include. -# Default: ["**/*[sS]pec.js"] -# -# EXAMPLE: -# -# spec_files: -# - **/*[sS]pec.js -# -spec_files: - - '**/*[sS]pec.js' - -# src_dir -# -# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank. -# Default: project root -# -# EXAMPLE: -# -# src_dir: public -# -src_dir: - -# spec_dir -# -# Spec directory path. Your spec_files must be returned relative to this path. -# Default: spec/javascripts -# -# EXAMPLE: -# -# spec_dir: spec/javascripts -# -spec_dir: - -# spec_helper -# -# Ruby file that Jasmine server will require before starting. -# Returned relative to your root path -# Default spec/javascripts/support/jasmine_helper.rb -# -# EXAMPLE: -# -# spec_helper: spec/javascripts/support/jasmine_helper.rb -# -spec_helper: spec/javascripts/support/jasmine_helper.rb - -# boot_dir -# -# Boot directory path. Your boot_files must be returned relative to this path. -# Default: Built in boot file -# -# EXAMPLE: -# -# boot_dir: spec/javascripts/support/boot -# -boot_dir: - -# boot_files -# -# Return an array of filepaths relative to boot_dir to include in order to boot Jasmine -# Default: Built in boot file -# -# EXAMPLE -# -# boot_files: -# - '**/*.js' -# -boot_files: - -# rack_options -# -# Extra options to be passed to the rack server -# by default, Port and AccessLog are passed. -# -# This is an advanced options, and left empty by default -# -# EXAMPLE -# -# rack_options: -# server: 'thin' diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb deleted file mode 100644 index 35ec28327..000000000 --- a/spec/javascripts/support/jasmine_helper.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Use this file to set/override Jasmine configuration options -# You can remove it if you don't need it. -# This file is loaded *after* jasmine.yml is interpreted. -# -# Example: using a different boot file. -# Jasmine.configure do |config| -# config.boot_dir = '/absolute/path/to/boot_dir' -# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] } -# end -# -# Example: prevent PhantomJS auto install, uses PhantomJS already on your path. -# Jasmine.configure do |config| -# config.prevent_phantom_js_auto_install = true -# end - -Jasmine.configure do |config| - # Enable console.log for debugging - config.show_console_log = true -end diff --git a/spec/javascripts/support/vendor/jasmine-jquery.js b/spec/javascripts/support/vendor/jasmine-jquery.js deleted file mode 100644 index 89071b063..000000000 --- a/spec/javascripts/support/vendor/jasmine-jquery.js +++ /dev/null @@ -1,813 +0,0 @@ -/*! -Jasmine-jQuery: a set of jQuery helpers for Jasmine tests. - -Version 2.0.5 - -https://github.com/velesin/jasmine-jquery - -Copyright (c) 2010-2014 Wojciech Zawistowski, Travis Jeffery - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -+function (window, jasmine, $) { "use strict"; - - jasmine.spiedEventsKey = function (selector, eventName) { - return [$(selector).selector, eventName].toString() - } - - jasmine.getFixtures = function () { - return jasmine.currentFixtures_ = jasmine.currentFixtures_ || new jasmine.Fixtures() - } - - jasmine.getStyleFixtures = function () { - return jasmine.currentStyleFixtures_ = jasmine.currentStyleFixtures_ || new jasmine.StyleFixtures() - } - - jasmine.Fixtures = function () { - this.containerId = 'jasmine-fixtures' - this.fixturesCache_ = {} - this.fixturesPath = 'spec/javascripts/fixtures' - } - - jasmine.Fixtures.prototype.set = function (html) { - this.cleanUp() - return this.createContainer_(html) - } - - jasmine.Fixtures.prototype.appendSet= function (html) { - this.addToContainer_(html) - } - - jasmine.Fixtures.prototype.preload = function () { - this.read.apply(this, arguments) - } - - jasmine.Fixtures.prototype.load = function () { - this.cleanUp() - this.createContainer_(this.read.apply(this, arguments)) - } - - jasmine.Fixtures.prototype.appendLoad = function () { - this.addToContainer_(this.read.apply(this, arguments)) - } - - jasmine.Fixtures.prototype.read = function () { - var htmlChunks = [] - , fixtureUrls = arguments - - for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { - htmlChunks.push(this.getFixtureHtml_(fixtureUrls[urlIndex])) - } - - return htmlChunks.join('') - } - - jasmine.Fixtures.prototype.clearCache = function () { - this.fixturesCache_ = {} - } - - jasmine.Fixtures.prototype.cleanUp = function () { - $('#' + this.containerId).remove() - } - - jasmine.Fixtures.prototype.sandbox = function (attributes) { - var attributesToSet = attributes || {} - return $('
').attr(attributesToSet) - } - - jasmine.Fixtures.prototype.createContainer_ = function (html) { - var container = $('
') - .attr('id', this.containerId) - .html(html) - - $(document.body).append(container) - return container - } - - jasmine.Fixtures.prototype.addToContainer_ = function (html){ - var container = $(document.body).find('#'+this.containerId).append(html) - - if (!container.length) { - this.createContainer_(html) - } - } - - jasmine.Fixtures.prototype.getFixtureHtml_ = function (url) { - if (typeof this.fixturesCache_[url] === 'undefined') { - this.loadFixtureIntoCache_(url) - } - return this.fixturesCache_[url] - } - - jasmine.Fixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { - var self = this - , url = this.makeFixtureUrl_(relativeUrl) - , htmlText = '' - , request = $.ajax({ - async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded - cache: false, - url: url, - success: function (data, status, $xhr) { - htmlText = $xhr.responseText - } - }).fail(function ($xhr, status, err) { - throw new Error('Fixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') - }) - - var scripts = $($.parseHTML(htmlText, true)).find('script[src]') || []; - - scripts.each(function(){ - $.ajax({ - async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded - cache: false, - dataType: 'script', - url: $(this).attr('src'), - success: function (data, status, $xhr) { - htmlText += '' - }, - error: function ($xhr, status, err) { - throw new Error('Script could not be loaded: ' + scriptSrc + ' (status: ' + status + ', message: ' + err.message + ')') - } - }); - }) - - self.fixturesCache_[relativeUrl] = htmlText; - } - - jasmine.Fixtures.prototype.makeFixtureUrl_ = function (relativeUrl){ - return this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl - } - - jasmine.Fixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) { - return this[methodName].apply(this, passedArguments) - } - - - jasmine.StyleFixtures = function () { - this.fixturesCache_ = {} - this.fixturesNodes_ = [] - this.fixturesPath = 'spec/javascripts/fixtures' - } - - jasmine.StyleFixtures.prototype.set = function (css) { - this.cleanUp() - this.createStyle_(css) - } - - jasmine.StyleFixtures.prototype.appendSet = function (css) { - this.createStyle_(css) - } - - jasmine.StyleFixtures.prototype.preload = function () { - this.read_.apply(this, arguments) - } - - jasmine.StyleFixtures.prototype.load = function () { - this.cleanUp() - this.createStyle_(this.read_.apply(this, arguments)) - } - - jasmine.StyleFixtures.prototype.appendLoad = function () { - this.createStyle_(this.read_.apply(this, arguments)) - } - - jasmine.StyleFixtures.prototype.cleanUp = function () { - while(this.fixturesNodes_.length) { - this.fixturesNodes_.pop().remove() - } - } - - jasmine.StyleFixtures.prototype.createStyle_ = function (html) { - var styleText = $('
').html(html).text() - , style = $('') - - this.fixturesNodes_.push(style) - $('head').append(style) - } - - jasmine.StyleFixtures.prototype.clearCache = jasmine.Fixtures.prototype.clearCache - jasmine.StyleFixtures.prototype.read_ = jasmine.Fixtures.prototype.read - jasmine.StyleFixtures.prototype.getFixtureHtml_ = jasmine.Fixtures.prototype.getFixtureHtml_ - jasmine.StyleFixtures.prototype.loadFixtureIntoCache_ = jasmine.Fixtures.prototype.loadFixtureIntoCache_ - jasmine.StyleFixtures.prototype.makeFixtureUrl_ = jasmine.Fixtures.prototype.makeFixtureUrl_ - jasmine.StyleFixtures.prototype.proxyCallTo_ = jasmine.Fixtures.prototype.proxyCallTo_ - - jasmine.getJSONFixtures = function () { - return jasmine.currentJSONFixtures_ = jasmine.currentJSONFixtures_ || new jasmine.JSONFixtures() - } - - jasmine.JSONFixtures = function () { - this.fixturesCache_ = {} - this.fixturesPath = 'spec/javascripts/fixtures/json' - } - - jasmine.JSONFixtures.prototype.load = function () { - this.read.apply(this, arguments) - return this.fixturesCache_ - } - - jasmine.JSONFixtures.prototype.read = function () { - var fixtureUrls = arguments - - for(var urlCount = fixtureUrls.length, urlIndex = 0; urlIndex < urlCount; urlIndex++) { - this.getFixtureData_(fixtureUrls[urlIndex]) - } - - return this.fixturesCache_ - } - - jasmine.JSONFixtures.prototype.clearCache = function () { - this.fixturesCache_ = {} - } - - jasmine.JSONFixtures.prototype.getFixtureData_ = function (url) { - if (!this.fixturesCache_[url]) this.loadFixtureIntoCache_(url) - return this.fixturesCache_[url] - } - - jasmine.JSONFixtures.prototype.loadFixtureIntoCache_ = function (relativeUrl) { - var self = this - , url = this.fixturesPath.match('/$') ? this.fixturesPath + relativeUrl : this.fixturesPath + '/' + relativeUrl - - $.ajax({ - async: false, // must be synchronous to guarantee that no tests are run before fixture is loaded - cache: false, - dataType: 'json', - url: url, - success: function (data) { - self.fixturesCache_[relativeUrl] = data - }, - error: function ($xhr, status, err) { - throw new Error('JSONFixture could not be loaded: ' + url + ' (status: ' + status + ', message: ' + err.message + ')') - } - }) - } - - jasmine.JSONFixtures.prototype.proxyCallTo_ = function (methodName, passedArguments) { - return this[methodName].apply(this, passedArguments) - } - - jasmine.jQuery = function () {} - - jasmine.jQuery.browserTagCaseIndependentHtml = function (html) { - return $('
').append(html).html() - } - - jasmine.jQuery.elementToString = function (element) { - return $(element).map(function () { return this.outerHTML; }).toArray().join(', ') - } - - var data = { - spiedEvents: {} - , handlers: [] - } - - jasmine.jQuery.events = { - spyOn: function (selector, eventName) { - var handler = function (e) { - data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] = jasmine.util.argsToArray(arguments) - } - - $(selector).on(eventName, handler) - data.handlers.push(handler) - - return { - selector: selector, - eventName: eventName, - handler: handler, - reset: function (){ - delete data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - } - } - }, - - args: function (selector, eventName) { - var actualArgs = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - - if (!actualArgs) { - throw "There is no spy for " + eventName + " on " + selector.toString() + ". Make sure to create a spy using spyOnEvent." - } - - return actualArgs - }, - - wasTriggered: function (selector, eventName) { - return !!(data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)]) - }, - - wasTriggeredWith: function (selector, eventName, expectedArgs, util, customEqualityTesters) { - var actualArgs = jasmine.jQuery.events.args(selector, eventName).slice(1) - - if (Object.prototype.toString.call(expectedArgs) !== '[object Array]') - actualArgs = actualArgs[0] - - return util.equals(expectedArgs, actualArgs, customEqualityTesters) - }, - - wasPrevented: function (selector, eventName) { - var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - , e = args ? args[0] : undefined - - return e && e.isDefaultPrevented() - }, - - wasStopped: function (selector, eventName) { - var args = data.spiedEvents[jasmine.spiedEventsKey(selector, eventName)] - , e = args ? args[0] : undefined - return e && e.isPropagationStopped() - }, - - cleanUp: function () { - data.spiedEvents = {} - data.handlers = [] - } - } - - var hasProperty = function (actualValue, expectedValue) { - if (expectedValue === undefined) - return actualValue !== undefined - - return actualValue === expectedValue - } - - beforeEach(function () { - jasmine.addMatchers({ - toHaveClass: function () { - return { - compare: function (actual, className) { - return { pass: $(actual).hasClass(className) } - } - } - }, - - toHaveCss: function () { - return { - compare: function (actual, css) { - for (var prop in css){ - var value = css[prop] - // see issue #147 on gh - ;if (value === 'auto' && $(actual).get(0).style[prop] === 'auto') continue - if ($(actual).css(prop) !== value) return { pass: false } - } - return { pass: true } - } - } - }, - - toBeVisible: function () { - return { - compare: function (actual) { - return { pass: $(actual).is(':visible') } - } - } - }, - - toBeHidden: function () { - return { - compare: function (actual) { - return { pass: $(actual).is(':hidden') } - } - } - }, - - toBeSelected: function () { - return { - compare: function (actual) { - return { pass: $(actual).is(':selected') } - } - } - }, - - toBeChecked: function () { - return { - compare: function (actual) { - return { pass: $(actual).is(':checked') } - } - } - }, - - toBeEmpty: function () { - return { - compare: function (actual) { - return { pass: $(actual).is(':empty') } - } - } - }, - - toBeInDOM: function () { - return { - compare: function (actual) { - return { pass: $.contains(document.documentElement, $(actual)[0]) } - } - } - }, - - toExist: function () { - return { - compare: function (actual) { - return { pass: $(actual).length } - } - } - }, - - toHaveLength: function () { - return { - compare: function (actual, length) { - return { pass: $(actual).length === length } - } - } - }, - - toHaveAttr: function () { - return { - compare: function (actual, attributeName, expectedAttributeValue) { - return { pass: hasProperty($(actual).attr(attributeName), expectedAttributeValue) } - } - } - }, - - toHaveProp: function () { - return { - compare: function (actual, propertyName, expectedPropertyValue) { - return { pass: hasProperty($(actual).prop(propertyName), expectedPropertyValue) } - } - } - }, - - toHaveId: function () { - return { - compare: function (actual, id) { - return { pass: $(actual).attr('id') == id } - } - } - }, - - toHaveHtml: function () { - return { - compare: function (actual, html) { - return { pass: $(actual).html() == jasmine.jQuery.browserTagCaseIndependentHtml(html) } - } - } - }, - - toContainHtml: function () { - return { - compare: function (actual, html) { - var actualHtml = $(actual).html() - , expectedHtml = jasmine.jQuery.browserTagCaseIndependentHtml(html) - - return { pass: (actualHtml.indexOf(expectedHtml) >= 0) } - } - } - }, - - toHaveText: function () { - return { - compare: function (actual, text) { - var actualText = $(actual).text() - var trimmedText = $.trim(actualText) - - if (text && $.isFunction(text.test)) { - return { pass: text.test(actualText) || text.test(trimmedText) } - } else { - return { pass: (actualText == text || trimmedText == text) } - } - } - } - }, - - toContainText: function () { - return { - compare: function (actual, text) { - var trimmedText = $.trim($(actual).text()) - - if (text && $.isFunction(text.test)) { - return { pass: text.test(trimmedText) } - } else { - return { pass: trimmedText.indexOf(text) != -1 } - } - } - } - }, - - toHaveValue: function () { - return { - compare: function (actual, value) { - return { pass: $(actual).val() === value } - } - } - }, - - toHaveData: function () { - return { - compare: function (actual, key, expectedValue) { - return { pass: hasProperty($(actual).data(key), expectedValue) } - } - } - }, - - toContainElement: function () { - return { - compare: function (actual, selector) { - if (window.debug) debugger - return { pass: $(actual).find(selector).length } - } - } - }, - - toBeMatchedBy: function () { - return { - compare: function (actual, selector) { - return { pass: $(actual).filter(selector).length } - } - } - }, - - toBeDisabled: function () { - return { - compare: function (actual, selector) { - return { pass: $(actual).is(':disabled') } - } - } - }, - - toBeFocused: function (selector) { - return { - compare: function (actual, selector) { - return { pass: $(actual)[0] === $(actual)[0].ownerDocument.activeElement } - } - } - }, - - toHandle: function () { - return { - compare: function (actual, event) { - var events = $._data($(actual).get(0), "events") - - if (!events || !event || typeof event !== "string") { - return { pass: false } - } - - var namespaces = event.split(".") - , eventType = namespaces.shift() - , sortedNamespaces = namespaces.slice(0).sort() - , namespaceRegExp = new RegExp("(^|\\.)" + sortedNamespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") - - if (events[eventType] && namespaces.length) { - for (var i = 0; i < events[eventType].length; i++) { - var namespace = events[eventType][i].namespace - - if (namespaceRegExp.test(namespace)) - return { pass: true } - } - } else { - return { pass: (events[eventType] && events[eventType].length > 0) } - } - - return { pass: false } - } - } - }, - - toHandleWith: function () { - return { - compare: function (actual, eventName, eventHandler) { - var normalizedEventName = eventName.split('.')[0] - , stack = $._data($(actual).get(0), "events")[normalizedEventName] - - for (var i = 0; i < stack.length; i++) { - if (stack[i].handler == eventHandler) return { pass: true } - } - - return { pass: false } - } - } - }, - - toHaveBeenTriggeredOn: function () { - return { - compare: function (actual, selector) { - var result = { pass: jasmine.jQuery.events.wasTriggered(selector, actual) } - - result.message = result.pass ? - "Expected event " + $(actual) + " not to have been triggered on " + selector : - "Expected event " + $(actual) + " to have been triggered on " + selector - - return result; - } - } - }, - - toHaveBeenTriggered: function (){ - return { - compare: function (actual) { - var eventName = actual.eventName - , selector = actual.selector - , result = { pass: jasmine.jQuery.events.wasTriggered(selector, eventName) } - - result.message = result.pass ? - "Expected event " + eventName + " not to have been triggered on " + selector : - "Expected event " + eventName + " to have been triggered on " + selector - - return result - } - } - }, - - toHaveBeenTriggeredOnAndWith: function (j$, customEqualityTesters) { - return { - compare: function (actual, selector, expectedArgs) { - var wasTriggered = jasmine.jQuery.events.wasTriggered(selector, actual) - , result = { pass: wasTriggered && jasmine.jQuery.events.wasTriggeredWith(selector, actual, expectedArgs, j$, customEqualityTesters) } - - if (wasTriggered) { - var actualArgs = jasmine.jQuery.events.args(selector, actual, expectedArgs)[1] - result.message = result.pass ? - "Expected event " + actual + " not to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) : - "Expected event " + actual + " to have been triggered with " + jasmine.pp(expectedArgs) + " but it was triggered with " + jasmine.pp(actualArgs) - - } else { - // todo check on this - result.message = result.pass ? - "Expected event " + actual + " not to have been triggered on " + selector : - "Expected event " + actual + " to have been triggered on " + selector - } - - return result - } - } - }, - - toHaveBeenPreventedOn: function () { - return { - compare: function (actual, selector) { - var result = { pass: jasmine.jQuery.events.wasPrevented(selector, actual) } - - result.message = result.pass ? - "Expected event " + actual + " not to have been prevented on " + selector : - "Expected event " + actual + " to have been prevented on " + selector - - return result - } - } - }, - - toHaveBeenPrevented: function () { - return { - compare: function (actual) { - var eventName = actual.eventName - , selector = actual.selector - , result = { pass: jasmine.jQuery.events.wasPrevented(selector, eventName) } - - result.message = result.pass ? - "Expected event " + eventName + " not to have been prevented on " + selector : - "Expected event " + eventName + " to have been prevented on " + selector - - return result - } - } - }, - - toHaveBeenStoppedOn: function () { - return { - compare: function (actual, selector) { - var result = { pass: jasmine.jQuery.events.wasStopped(selector, actual) } - - result.message = result.pass ? - "Expected event " + actual + " not to have been stopped on " + selector : - "Expected event " + actual + " to have been stopped on " + selector - - return result; - } - } - }, - - toHaveBeenStopped: function () { - return { - compare: function (actual) { - var eventName = actual.eventName - , selector = actual.selector - , result = { pass: jasmine.jQuery.events.wasStopped(selector, eventName) } - - result.message = result.pass ? - "Expected event " + eventName + " not to have been stopped on " + selector : - "Expected event " + eventName + " to have been stopped on " + selector - - return result - } - } - } - }) - - jasmine.getEnv().addCustomEqualityTester(function(a, b) { - if (a && b) { - if (a instanceof $ || jasmine.isDomNode(a)) { - var $a = $(a) - - if (b instanceof $) - return $a.length == b.length && a.is(b) - - return $a.is(b); - } - - if (b instanceof $ || jasmine.isDomNode(b)) { - var $b = $(b) - - if (a instanceof $) - return a.length == $b.length && $b.is(a) - - return $(b).is(a); - } - } - }) - - jasmine.getEnv().addCustomEqualityTester(function (a, b) { - if (a instanceof $ && b instanceof $ && a.size() == b.size()) - return a.is(b) - }) - }) - - afterEach(function () { - jasmine.getFixtures().cleanUp() - jasmine.getStyleFixtures().cleanUp() - jasmine.jQuery.events.cleanUp() - }) - - window.readFixtures = function () { - return jasmine.getFixtures().proxyCallTo_('read', arguments) - } - - window.preloadFixtures = function () { - jasmine.getFixtures().proxyCallTo_('preload', arguments) - } - - window.loadFixtures = function () { - jasmine.getFixtures().proxyCallTo_('load', arguments) - } - - window.appendLoadFixtures = function () { - jasmine.getFixtures().proxyCallTo_('appendLoad', arguments) - } - - window.setFixtures = function (html) { - return jasmine.getFixtures().proxyCallTo_('set', arguments) - } - - window.appendSetFixtures = function () { - jasmine.getFixtures().proxyCallTo_('appendSet', arguments) - } - - window.sandbox = function (attributes) { - return jasmine.getFixtures().sandbox(attributes) - } - - window.spyOnEvent = function (selector, eventName) { - return jasmine.jQuery.events.spyOn(selector, eventName) - } - - window.preloadStyleFixtures = function () { - jasmine.getStyleFixtures().proxyCallTo_('preload', arguments) - } - - window.loadStyleFixtures = function () { - jasmine.getStyleFixtures().proxyCallTo_('load', arguments) - } - - window.appendLoadStyleFixtures = function () { - jasmine.getStyleFixtures().proxyCallTo_('appendLoad', arguments) - } - - window.setStyleFixtures = function (html) { - jasmine.getStyleFixtures().proxyCallTo_('set', arguments) - } - - window.appendSetStyleFixtures = function (html) { - jasmine.getStyleFixtures().proxyCallTo_('appendSet', arguments) - } - - window.loadJSONFixtures = function () { - return jasmine.getJSONFixtures().proxyCallTo_('load', arguments) - } - - window.getJSONFixture = function (url) { - return jasmine.getJSONFixtures().proxyCallTo_('read', arguments)[url] - } -}(window, window.jasmine, window.jQuery);