Compare commits

..

61 Commits

Author SHA1 Message Date
pozorvlak
61183509b0 Merge pull request #1612 from Growstuff/Br3nda-patch-1
Pick the first photo
2018-04-12 15:34:22 +01:00
Brenda Wallace
6c1bbe9f1c Merge branch 'dev' into Br3nda-patch-1 2018-04-12 14:15:49 +12:00
Brenda Wallace
bab41143fa Merge branch 'master' into dev 2018-04-12 11:01:53 +12:00
Brenda Wallace
3c7688d2ed Merge branch 'dev' into Br3nda-patch-1 2018-04-09 09:57:24 +12:00
Brenda Wallace
ef20d1a333 Merge pull request #1613 from jenkr55/OnlyShowPlantingSuggestionIfMatch
Only show planting suggestion if there's a match
2018-04-09 09:57:04 +12:00
jenkr55
9c0ddb61e7 Prevent error when @matching_plantings is nil 2018-04-08 10:17:06 -05:00
Jennifer Kruse
19acb238fd Merge branch 'dev' into OnlyShowPlantingSuggestionIfMatch 2018-04-08 09:49:29 -05:00
jenkr55
d38167fd14 Only show planting suggestion if theres a match 2018-04-08 09:47:15 -05:00
Brenda Wallace
6b68e9ee4d Style fix 2018-04-08 10:04:43 +12:00
Brenda Wallace
3e2b4f4183 Pick the first photo 2018-04-08 09:56:19 +12:00
Brenda Wallace
65bb523d16 Merge pull request #1609 from jenkr55/FixVariousWrappingIssues
Fix various wrapping issues
2018-04-08 09:03:26 +12:00
jenkr55
efa8c513ea Adding myself as a contributor 2018-04-07 14:59:14 -05:00
jenkr55
43813d1318 Fixing various wrapping issues 2018-04-07 14:58:07 -05:00
Cesy
7e2aec9ec3 Merge pull request #1608 from Growstuff/dev
Release 44
2018-04-06 07:23:07 +01:00
Brenda Wallace
952224b2e1 Merge branch 'master' into dev 2018-04-03 17:46:37 +12:00
Brenda Wallace
26b1a3a6ca Merge pull request #1605 from Br3nda/feature/ownable
Adding counter caches and ownable concern
2018-04-03 08:54:15 +12:00
Brenda Wallace
03c9151ecd Merge branch 'dev' into feature/ownable 2018-04-03 08:46:34 +12:00
pozorvlak
1dd3d658eb Merge pull request #1606 from Br3nda/fix/dependents
Set the behaviour of dependent models when crops are deleted from the database:

 - plantings, seeds and harvests are deleted
 - child crops are orphaned.
2018-04-02 15:52:10 +01:00
pozorvlak
c3b694ea7d Merge branch 'dev' into fix/dependents 2018-04-02 15:39:16 +01:00
pozorvlak
a3e2b892d1 Merge pull request #1596 from Br3nda/fix/coverage
Adding codeclimate coverage
2018-04-02 15:36:48 +01:00
pozorvlak
d0f6d63d36 Merge branch 'dev' into fix/dependents 2018-04-02 15:26:07 +01:00
pozorvlak
879ce26de7 Merge branch 'dev' into fix/coverage 2018-04-02 15:21:51 +01:00
pozorvlak
98e27b5af4 Merge pull request #1602 from Br3nda/fix/ordering
Fix ordering of recent plantings on crops#show
2018-04-02 15:21:29 +01:00
pozorvlak
a7cfecf82c Merge branch 'dev' into feature/ownable 2018-04-02 14:47:48 +01:00
pozorvlak
d0f28d4bef Merge branch 'dev' into fix/ordering 2018-04-02 14:43:40 +01:00
pozorvlak
2c090757cc Merge pull request #1601 from Br3nda/feature/fluid-layout
Feature/fluid layout
2018-04-02 14:40:44 +01:00
Brenda Wallace
106d187e67 Add dependent clauses on crop model 2018-04-02 10:29:10 +12:00
Brenda Wallace
8521047803 Merge branch 'dev' into feature/ownable 2018-04-02 10:23:07 +12:00
Brenda Wallace
e173d0c130 Merge branch 'dev' into fix/ordering 2018-04-02 10:22:20 +12:00
Brenda Wallace
53197f27eb Merge branch 'dev' into feature/fluid-layout 2018-04-02 10:22:03 +12:00
Brenda Wallace
2460f73673 Merge branch 'dev' into fix/coverage 2018-04-02 10:21:22 +12:00
Brenda Wallace
324dcfc821 Merge pull request #1598 from Growstuff/bundle-update-2018-03-22-131347
Bundle Update on 2018-03-22
2018-04-02 10:21:04 +12:00
Brenda Wallace
e3bb43477b Adding counter caches and ownable concern 2018-04-02 10:13:19 +12:00
Brenda Wallace
2d309b7645 Removing jasmine, we have no jasmine specs 2018-04-02 09:58:02 +12:00
Brenda Wallace
a59f7dd137 Merge branch 'dev' into fix/coverage 2018-04-02 09:50:11 +12:00
Brenda Wallace
1fb3c4e0f9 Regnerated and secured code climate key 2018-04-02 09:47:04 +12:00
Brenda Wallace
b64a294245 Removed duplicate margins in css 2018-04-01 18:45:35 +12:00
Brenda Wallace
31d2aa052a More bundle updates 2018-04-01 14:16:50 +12:00
Brenda Wallace
b7f78d5dc9 Fix ordering of recent plantings on crops#show 2018-04-01 13:05:23 +12:00
Brenda Wallace
f1afe1ca79 Expanding layout to use full screen 2018-04-01 12:44:59 +12:00
Brenda Wallace
e029ef17ab Merge remote-tracking branch 'upstream/dev' into dev 2018-04-01 09:21:37 +12:00
Brenda Wallace
d5c8b9ae27 Merge branch 'dev' into bundle-update-2018-03-22-131347 2018-03-31 18:15:25 +13:00
Brenda Wallace
f907ba9e4b Merge pull request #1599 from Growstuff/dev
Release 43
2018-03-30 16:14:06 +13:00
Brenda Wallace
25070f73d7 Merge pull request #1591 from Br3nda/feature/photos-helper
Use photos helpers to find photos
2018-03-26 18:14:32 +13:00
Brenda Wallace
2fe6012342 Merge branch 'dev' into feature/photos-helper 2018-03-23 15:03:01 +13:00
deppbot
61468b3587 Bundle Update on 2018-03-22 2018-03-22 13:13:48 +08:00
Brenda Wallace
58c4d82087 Merge pull request #1590 from Growstuff/dev
Release 42
2018-03-22 15:01:52 +13:00
Brenda Wallace
69018f2c5c Merge remote-tracking branch 'upstream/dev' into dev 2018-03-22 11:53:29 +13:00
Brenda Wallace
00ea96a73e Can't rename GROWSTUFF_ELASTICSEARCH 2018-03-20 11:35:01 +13:00
Brenda Wallace
a1e33ae36e Only set up database and assets in test run, not linter run 2018-03-20 11:13:11 +13:00
Brenda Wallace
7f24927dc9 Clean up errors on undefined shell vars 2018-03-20 11:06:37 +13:00
Brenda Wallace
a178d3d42a Fix ES install on travis 2018-03-19 18:02:26 +13:00
Brenda Wallace
6089198f5c fixed path to scripts 2018-03-19 17:52:25 +13:00
Brenda Wallace
715ff4d41d Merge branch 'dev' into fix/coverage 2018-03-19 17:47:01 +13:00
Brenda Wallace
5992c974c7 Adding codeclimate coverage 2018-03-19 17:08:04 +13:00
Brenda Wallace
511e89fa58 Merge branch 'dev' into feature/photos-helper 2018-03-19 13:39:02 +13:00
Brenda Wallace
f8e83cdb9c Merge branch 'dev' into feature/photos-helper 2018-03-16 14:54:53 +13:00
Brenda Wallace
ece7113c89 DRY photo helper spec 2018-03-15 22:56:32 +13:00
Brenda Wallace
8941c577e4 Specs for photos helper 2018-03-14 22:25:30 +13:00
Brenda Wallace
1bf1076076 Photos helper 2018-03-14 19:28:18 +13:00
Brenda Wallace
8906fd38ae For seeds, use planting photo if no seeds photo 2018-03-14 15:21:40 +13:00
50 changed files with 294 additions and 1152 deletions

View File

@@ -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="

View File

@@ -83,6 +83,7 @@ submit the change with your pull request.
- Jeff Kingswood / [ancyentmariner](https://github.com/ancyentmariner)
- Logan Gingerich / [logangingerich](https://github.com/logangingerich)
- Mark Taffman / [mftaff](https://github.com/mftaff)
- Jennifer Kruse / [jenkr55](https://github.com/jenkr55)
## Bots

View File

@@ -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'

View File

@@ -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

View File

@@ -73,10 +73,16 @@ p.stats
display: flex
flex: none
flex-wrap: wrap
justify-content: space-between
.seeds-row
display: grid
grid-template-columns: 50% 50%
grid-gap: 25px
grid-row-gap: 5px
.member-thumbnail
padding: .25em
margin: 1em
div
width: 5em
@@ -146,6 +152,7 @@ p.stats
border: none
text-align: center
margin-bottom: 1.5em
max-width: 160px
.member-thumbnail
text-align: left
@@ -221,6 +228,7 @@ footer
#maincontainer
min-height: 80%
padding: 50px
html, body
height: 100%
@@ -334,3 +342,6 @@ ul.thumbnail-buttons
height: 180px
.seed-thumbnail
height: 220px
#maincontainer
padding: 10px

View File

@@ -3,37 +3,49 @@ module PhotosHelper
if crop.default_photo.present?
crop.default_photo.thumbnail_url
else
default_image
placeholder_image
end
end
def garden_image_path(garden)
if garden.default_photo.present?
garden.default_photo.thumbnail_url
else
placeholder_image
end
end
def planting_image_path(planting)
if planting.photos.present?
planting.photos.first.thumbnail_url
planting.photos.order(date_taken: :desc).first.thumbnail_url
else
default_image
placeholder_image
end
end
def harvest_image_path(harvest)
if harvest.photos.present?
harvest.photos.first.thumbnail_url
harvest.photos.order(date_taken: :desc).first.thumbnail_url
elsif harvest.planting.present?
planting_image_path(harvest.planting)
else
default_image
placeholder_image
end
end
def seed_image_path(seed)
if seed.default_photo
if seed.default_photo.present?
seed.default_photo.thumbnail_url
elsif seed.crop.default_photo.present?
seed.crop.default_photo.thumbnail_url
else
default_image
placeholder_image
end
end
private
def default_image
def placeholder_image
'placeholder_150.png'
end
end

View File

@@ -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

View File

@@ -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 }) }
##
@@ -120,12 +118,7 @@ class Crop < ActiveRecord::Base
# later we can choose a default photo based on different criteria,
# eg. popularity
def default_photo
# most recent photo
return photos.order(created_at: :desc).first if photos.any?
# Crop has no photos? Look for the most recent harvest with a photo.
harvest_with_photo = Harvest.where(crop_id: id).joins(:photos).order('harvests.id DESC').limit(1).first
harvest_with_photo.photos.first if harvest_with_photo
first_photo(:plantings) || first_photo(:harvests) || first_photo(:seeds)
end
# returns hash indicating whether this crop is grown in
@@ -242,4 +235,8 @@ class Crop < ActiveRecord::Base
return unless reason_for_rejection == "other" && rejection_notes.blank?
errors.add(:rejection_notes, "must be added if the reason for rejection is \"other\"")
end
def first_photo(type)
Photo.joins(type).where("#{type}": { crop_id: id }).order("photos.created_at DESC").first
end
end

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,5 +1,5 @@
class Photo < ActiveRecord::Base
belongs_to :owner, class_name: 'Member'
include Ownable
PHOTO_CAPABLE = %w(Garden Planting Harvest Seed).freeze

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,7 @@
.well
.row
.col-md-4
= link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(crop_image_path(crop),
alt: '',
class: 'img crop-image'),
crop

View File

@@ -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" }

View File

@@ -2,7 +2,7 @@
.thumbnail
.crop-thumbnail
- if crop
= link_to image_tag((crop.default_photo ? crop.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(crop_image_path(crop),
alt: crop.name, class: 'img'),
crop
.cropinfo

View File

@@ -1,3 +1,3 @@
= link_to image_tag((garden.default_photo ? garden.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(garden_image_path(garden),
alt: garden.name, class: 'img-responsive'),
garden_path(garden)

View File

@@ -8,7 +8,7 @@
.panel-body{ id: "gardens_panel_body" }
.row
.col-md-4
= link_to image_tag((garden.default_photo ? garden.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(garden_image_path(garden),
alt: garden.name, class: 'img'),
garden_path(garden)
.col-md-8

View File

@@ -8,7 +8,7 @@
.panel-body
.row
.col-md-4
= link_to image_tag((harvest.default_photo ? harvest.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(harvest_image_path(harvest),
alt: harvest.crop.name, class: 'img'),
harvest
.col-md-8

View File

@@ -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

View File

@@ -3,7 +3,7 @@
planting_path(@harvest.planting)
in
= link_to @harvest.planting.garden, garden_path(@harvest.planting.garden)
- elsif @matching_plantings && @harvest.owner == current_member
- elsif @matching_plantings && @matching_plantings.any? && @harvest.owner == current_member
Is this from one of these plantings?
= form_for(@harvest) do |f|
- @matching_plantings.each do |planting|

View File

@@ -1,7 +1,7 @@
.thumbnail
.harvest-thumbnail
- if harvest
= link_to image_tag((harvest.default_photo ? harvest.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(harvest_image_path(harvest),
alt: harvest.crop.name, class: 'img'),
harvest
.harvestinfo

View File

@@ -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))

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -5,7 +5,7 @@
%body
= render partial: "layouts/header"
.container#maincontainer
#maincontainer
.row
.col-md-12
- if content_for?(:title)

View File

@@ -8,7 +8,7 @@
.panel-body
.row
.col-xs-12.col-md-5
= link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(planting_image_path(planting),
alt: planting.crop_id, class: 'img img-responsive'),
planting
.col-xs-12.col-md-7

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,7 +1,7 @@
.thumbnail
.planting-thumbnail
- if planting
= link_to image_tag((planting.default_photo ? planting.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(planting_image_path(planting),
alt: planting.crop.name, class: 'img'),
planting
.plantinginfo

View File

@@ -8,7 +8,7 @@
.panel-body
.row
.col-md-4
= link_to image_tag((seed.crop.default_photo ? seed.crop.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(seed_image_path(seed),
alt: seed.crop.name, class: 'img'),
seed.crop
.col-md-8

View File

@@ -1,6 +1,6 @@
.thumbnail
.seed-thumbnail
= link_to image_tag((seed.default_photo ? seed.default_photo.thumbnail_url : 'placeholder_150.png'),
= link_to image_tag(seed_image_path(seed),
alt: seed.crop.name, class: 'img'),
seed_path(seed)
.seedinfo

View File

@@ -24,10 +24,10 @@
= page_entries_info @seeds
= will_paginate @seeds
.row
.seeds-row
- unless @seeds.empty?
- @seeds.each do |seed|
.col-md-6
.seedcard
= render 'seeds/card', seed: seed
.pagination

View File

@@ -0,0 +1,6 @@
class AddMemberCountCaches < ActiveRecord::Migration
def change
add_column :members, :photos_count, :integer
add_column :members, :forums_count, :integer
end
end

View File

@@ -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

7
script/install_codeclimate.sh Executable file
View File

@@ -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

11
script/install_elasticsearch.sh Executable file
View File

@@ -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

View File

@@ -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

14
script/install_linters.sh Executable file
View File

@@ -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

View File

@@ -0,0 +1,79 @@
require 'rails_helper'
describe PhotosHelper do
let(:crop) { FactoryBot.create :crop }
let(:garden) { FactoryBot.create :garden }
let(:garden_photo) { FactoryBot.create(:photo, thumbnail_url: 'garden.jpg') }
let(:planting) { FactoryBot.create :planting, crop: crop }
let(:planting_photo) { FactoryBot.create(:photo, thumbnail_url: 'planting.jpg') }
let(:harvest) { FactoryBot.create :harvest, crop: crop }
let(:harvest_photo) { FactoryBot.create(:photo, thumbnail_url: 'harvest.jpg') }
let(:seed) { FactoryBot.create :seed, crop: crop }
let(:seed_photo) { FactoryBot.create(:photo, thumbnail_url: 'seed.jpg') }
describe "crops" do
subject { crop_image_path(crop) }
it { is_expected.to eq 'placeholder_150.png' }
describe "with a planting" do
before { planting.photos << planting_photo }
it "uses planting photos" do
is_expected.to eq planting_photo.thumbnail_url
end
end
describe "with a harvest photos" do
before { harvest.photos << harvest_photo }
it "uses harvest photos" do
is_expected.to eq harvest_photo.thumbnail_url
end
end
describe "uses seed photo" do
before { seed.photos << seed_photo }
it "uses seed photos" do
is_expected.to eq seed_photo.thumbnail_url
end
end
end
describe "gardens" do
subject { garden_image_path(garden) }
it { is_expected.to eq 'placeholder_150.png' }
describe "uses garden's own photo" do
before { garden.photos << garden_photo }
it { is_expected.to eq garden_photo.thumbnail_url }
end
end
describe 'plantings' do
subject { planting_image_path(planting) }
it { is_expected.to eq 'placeholder_150.png' }
describe "uses planting's own photo" do
before { planting.photos << planting_photo }
it { is_expected.to eq planting_photo.thumbnail_url }
end
end
describe 'harvests' do
subject { harvest_image_path(harvest) }
it { is_expected.to eq 'placeholder_150.png' }
describe "uses harvest's own photo" do
before { harvest.photos << harvest_photo }
it { is_expected.to eq harvest_photo.thumbnail_url }
end
end
describe 'seeds' do
subject { seed_image_path(seed) }
it { is_expected.to eq 'placeholder_150.png' }
describe "uses seed's own photo" do
before { seed.photos << seed_photo }
it { is_expected.to eq seed_photo.thumbnail_url }
end
end
end

View File

@@ -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);
}

View File

@@ -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'

View File

@@ -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

View File

@@ -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 $('<div id="sandbox" />').attr(attributesToSet)
}
jasmine.Fixtures.prototype.createContainer_ = function (html) {
var container = $('<div>')
.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 += '<script>' + $xhr.responseText + '</script>'
},
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 = $('<div></div>').html(html).text()
, style = $('<style>' + styleText + '</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 $('<div/>').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);