Merge remote-tracking branch 'upstream/dev' into feature/memcachier

This commit is contained in:
Brenda Wallace
2019-04-19 09:30:48 +12:00
113 changed files with 1172 additions and 768 deletions

15
Gemfile
View File

@@ -21,6 +21,8 @@ gem 'font-awesome-sass'
gem 'uglifier' # JavaScript compressor
gem 'oj' # Speeds up json
# planting and harvest predictions
# based on median values for the crop
gem 'active_median', '0.1.4' # needs postgresql update https://github.com/Growstuff/growstuff/issues/1757
@@ -78,17 +80,12 @@ gem 'omniauth-facebook'
gem 'omniauth-flickr', '>= 0.0.15'
gem 'omniauth-twitter'
# Pretty charts
gem "chartkick"
# client for Elasticsearch. Elasticsearch is a flexible
# and powerful, distributed, real-time search and analytics engine.
# An example of the use in the project is fuzzy crop search.
# Project does not use semver, so we want to be in sync with the version of
# elasticsearch we use
# See https://github.com/elastic/elasticsearch-ruby#compatibility
gem "elasticsearch-api", "~> 6.0.0"
gem "elasticsearch-model", "~> 6.0.0"
gem "elasticsearch-rails", "~> 6.0.0"
# clever elastic search
gem 'searchkick'
gem "hashie", ">= 3.5.3"
gem 'rake', '>= 10.0.0'

View File

@@ -79,8 +79,8 @@ GEM
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.0.1)
cancancan (2.3.0)
capybara (3.15.0)
cancancan (3.0.1)
capybara (3.16.2)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
@@ -134,7 +134,7 @@ GEM
activesupport (>= 3.0.0)
dalli (2.7.10)
database_cleaner (1.7.0)
devise (4.6.1)
devise (4.6.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0, < 6.0)
@@ -142,17 +142,17 @@ GEM
warden (~> 1.2.3)
diff-lcs (1.3)
docile (1.1.5)
elasticsearch (6.0.3)
elasticsearch-api (= 6.0.3)
elasticsearch-transport (= 6.0.3)
elasticsearch-api (6.0.3)
elasticsearch (6.2.0)
elasticsearch-api (= 6.2.0)
elasticsearch-transport (= 6.2.0)
elasticsearch-api (6.2.0)
multi_json
elasticsearch-model (6.0.0)
activesupport (> 3)
elasticsearch (> 1)
hashie
elasticsearch-rails (6.0.0)
elasticsearch-transport (6.0.3)
elasticsearch-transport (6.2.0)
faraday
multi_json
erubi (1.8.0)
@@ -212,7 +212,7 @@ GEM
excon
moneta
multi_json (>= 1.9.2)
highline (2.0.0)
highline (2.0.1)
html2haml (2.2.0)
erubis (~> 2.7.0)
haml (>= 4.0, < 6)
@@ -223,7 +223,7 @@ GEM
multi_xml (>= 0.5.2)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
i18n-tasks (0.9.28)
i18n-tasks (0.9.29)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
erubi
@@ -240,11 +240,11 @@ GEM
thor (>= 0.14, < 2.0)
jquery-ui-rails (6.0.1)
railties (>= 3.2.16)
js-routes (1.4.4)
railties (>= 3.2)
js-routes (1.4.6)
railties (>= 4)
sprockets-rails
json (2.1.0)
jsonapi-resources (0.9.5)
jsonapi-resources (0.9.6)
activerecord (>= 4.1)
concurrent-ruby
railties (>= 4.1)
@@ -265,7 +265,7 @@ GEM
kramdown (2.1.0)
launchy (2.4.3)
addressable (~> 2.3)
leaflet-rails (1.3.1)
leaflet-rails (1.4.0)
rails (>= 4.2.0)
letter_opener (1.7.0)
launchy (~> 2.2)
@@ -296,7 +296,7 @@ GEM
multipart-post (2.0.0)
newrelic_rpm (6.2.0.354)
nio4r (2.3.1)
nokogiri (1.10.1)
nokogiri (1.10.2)
mini_portile2 (~> 2.4.0)
oauth (0.5.4)
oauth2 (1.4.1)
@@ -305,6 +305,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
oj (3.7.11)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
@@ -323,10 +324,10 @@ GEM
omniauth-oauth (~> 1.1)
rack
orm_adapter (0.5.0)
parallel (1.14.0)
parallel (1.17.0)
paranoia (2.4.1)
activerecord (>= 4.0, < 5.3)
parser (2.6.0.0)
parser (2.6.2.1)
ast (~> 2.4.0)
pg (0.21.0)
platform-api (2.2.0)
@@ -339,7 +340,7 @@ GEM
psych (3.1.0)
public_suffix (3.0.3)
puma (3.12.1)
rack (2.0.6)
rack (2.0.7)
rack-protection (2.0.5)
rack
rack-test (1.1.0)
@@ -390,7 +391,7 @@ GEM
rb-inotify (0.10.0)
ffi (~> 1.0)
redis (4.1.0)
regexp_parser (1.3.0)
regexp_parser (1.4.0)
responders (2.4.1)
actionpack (>= 4.2.0, < 6.0)
railties (>= 4.2.0, < 6.0)
@@ -415,7 +416,7 @@ GEM
rspec-mocks (~> 3.8.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
rubocop (0.66.0)
rubocop (0.67.2)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
@@ -452,11 +453,15 @@ GEM
sprockets-rails
tilt
scout_apm (2.4.24)
searchkick (3.1.2)
activemodel (>= 4.2)
elasticsearch (>= 5)
hashie
selenium-webdriver (3.141.0)
childprocess (~> 0.5)
rubyzip (~> 1.2, >= 1.2.2)
sexp_processor (4.12.0)
sidekiq (5.2.5)
sidekiq (5.2.6)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
@@ -536,9 +541,6 @@ DEPENDENCIES
dalli
database_cleaner
devise
elasticsearch-api (~> 6.0.0)
elasticsearch-model (~> 6.0.0)
elasticsearch-rails (~> 6.0.0)
factory_bot_rails
faker
figaro
@@ -565,6 +567,7 @@ DEPENDENCIES
loofah (>= 2.2.1)
memcachier
newrelic_rpm
oj
omniauth (~> 1.3)
omniauth-facebook
omniauth-flickr (>= 0.0.15)
@@ -588,6 +591,7 @@ DEPENDENCIES
ruby-units
sass-rails
scout_apm
searchkick
selenium-webdriver
sidekiq
timecop

View File

@@ -17,7 +17,7 @@ encourage participation from people of all backgrounds and skill levels.
## Important links
* [Issues](http://github.com/Growstuff/growstuff/issues) (features we're
* [Issues](https://github.com/orgs/Growstuff/projects/1) (features we're
working on, known bugs, etc)
* [IRC](https://webchat.freenode.net/) growstuff channel (general chat, brainstorming and troubleshooting) or [Gitter](https://gitter.im/Growstuff/growstuff)
* [Wiki](https://github.com/Growstuff/growstuff/wiki) (general documentation, etc. Help by migrating from the [old wiki](https://web.archive.org/web/*/wiki.growstuff.org))
@@ -44,9 +44,9 @@ You might like to check out:
Here on Github, you might find these useful:
* [Waffle](http://waffle.io/Growstuff/growstuff) has stories in "ready" that can be worked on.
* [Github Project Board](https://github.com/orgs/Growstuff/projects/1) has stories in "ready" that can be worked on.
* [needs: design](https://github.com/Growstuff/growstuff/labels/needs:%20design) - tasks requiring high-level design
* [needs: visual design](https://github.com/Growstuff/growstuff/labels/needs:%20visual design) - tasks requiring visual/graphical design
* [needs: visual design](https://github.com/Growstuff/growstuff/labels/needs:%20visual+design) - tasks requiring visual/graphical design
* [needs: documentation](https://github.com/Growstuff/growstuff/labels/needs:%20documentation)
* [needs: data](https://github.com/Growstuff/growstuff/labels/needs:%20data) - tasks requiring data entry, data design, data import, or similar
* [curated:beginner](https://github.com/Growstuff/growstuff/labels/curated:%20beginner) - tasks that are ideal for beginner programmers or people new to the project

View File

@@ -42,14 +42,18 @@ class CropsController < ApplicationController
def search
@term = params[:term]
@matches = Crop.search(@term)
@paginated_matches = @matches.paginate(page: params[:page])
respond_with @matches
@crops = CropSearchService.search(
@term, page: params[:page],
per_page: 12,
current_member: current_member
)
respond_with @crops
end
def show
@crop = Crop.includes(:scientific_names, plantings: :photos).find_by!(slug: params[:slug])
@crop = Crop.includes(:scientific_names, plantings: :photos)
.find_by!(slug: params[:slug])
@posts = @crop.posts.order(created_at: :desc).paginate(page: params[:page])
@photos = Photo.by_crop(@crop)

View File

@@ -0,0 +1,51 @@
class GardenTypesController < ApplicationController
respond_to :html, :json
load_and_authorize_resource
# GET /garden_types
def index
@garden_types = GardenType.all.paginate(page: params[:page])
respond_with @garden_types
end
# GET /garden_types/1
def show
respond_with @garden_type
end
# GET /garden_types/new
def new
@garden_type = GardenType.new
end
# GET /garden_types/1/edit
def edit; end
# POST /garden_types
def create
@garden_type = GardenType.create(garden_type_params)
respond_with(@garden_type)
end
# PATCH/PUT /garden_types/1
def update
@garden_type.update(garden_type_params)
respond_with @garden_type
end
# DELETE /garden_types/1
def destroy
@garden_type.destroy
respond_with @garden_type
end
private
def set_garden_type
@garden_type = GardenType.find(params[:id])
end
def garden_type_params
params.require(:garden_type).permit(:description, :slug)
end
end

View File

@@ -13,6 +13,7 @@ class GardensController < ApplicationController
@gardens = @gardens.active unless @show_all
@gardens = @gardens.where(owner: @owner) if @owner.present?
@gardens = @gardens.joins(:owner).order(:name).paginate(page: params[:page])
@gardens = @gardens.includes(:owner, plantings: [:owner, crop: :parent])
respond_with(@gardens)
end
@@ -67,6 +68,6 @@ class GardensController < ApplicationController
def garden_params
params.require(:garden).permit(:name, :slug, :description, :active,
:location, :latitude, :longitude, :area, :area_unit)
:location, :latitude, :longitude, :area, :area_unit, :garden_type_id)
end
end

View File

@@ -27,11 +27,9 @@ module ApplicationHelper
end
def required_field_help_text
# rubocop:disable Rails/OutputSafety
asterisk = content_tag :span, '*', class: ['red']
text = content_tag :em, 'denotes a required field'
content_tag :div, asterisk + ' '.html_safe + text, class: ['margin-bottom']
# rubocop:enable Rails/OutputSafety
end
#

View File

@@ -10,8 +10,7 @@ module AutoSuggestHelper
end
resource = resource.class.name.downcase
source_path = Rails.application.routes.url_helpers.send("search_#{source}s_path")
source_path = Rails.application.routes.url_helpers.send("search_#{source}s_path", format: :json)
%(
<input id="#{source}" class="auto-suggest #{options[:class]}"
type="text" value="#{default}" data-source-url="#{source_path}",

View File

@@ -42,6 +42,10 @@ class Ability
can :read, AlternateName do |an|
an.crop.approved?
end
cannot :create, GardenType
cannot :update, GardenType
cannot :destroy, GardenType
end
def member_abilities(member) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
@@ -124,6 +128,10 @@ class Ability
can :destroy, Follow
cannot :destroy, Follow, followed_id: member.id # can't unfollow yourself
cannot :create, GardenType
cannot :update, GardenType
cannot :destroy, GardenType
end
def admin_abilities(member)

View File

@@ -1,7 +1,10 @@
class AlternateName < ApplicationRecord
after_commit { |an| an.crop.__elasticsearch__.index_document if an.crop && ENV['GROWSTUFF_ELASTICSEARCH'] == "true" }
belongs_to :crop
belongs_to :creator, class_name: 'Member', inverse_of: :created_alternate_names
validates :name, presence: true
validates :crop, presence: true
after_commit :reindex if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
delegate :reindex, to: :crop
end

View File

@@ -8,9 +8,9 @@ class Crop < ApplicationRecord
##
## Relationships
has_many :scientific_names, after_add: :update_index, after_remove: :update_index, dependent: :destroy
has_many :scientific_names, 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 :alternate_names, dependent: :destroy
has_many :plantings, dependent: :destroy
has_many :seeds, dependent: :destroy
has_many :harvests, dependent: :destroy
@@ -34,6 +34,9 @@ class Crop < ApplicationRecord
scope :interesting, -> { approved.has_photos }
scope :has_photos, -> { includes(:photos).where.not(photos: { id: nil }) }
# Special scope to control if it's in the search index
scope :search_import, -> { approved }
##
## Validations
# Reasons are only necessary when rejecting
@@ -53,47 +56,7 @@ class Crop < ApplicationRecord
####################################
# Elastic search configuration
if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
# In order to avoid clashing between different environments,
# use Rails.env as a part of index name (eg. development_growstuff)
index_name [Rails.env, "growstuff"].join('_')
settings index: { number_of_shards: 1 },
analysis: {
tokenizer: {
gs_edgeNGram_tokenizer: {
type: "edgeNGram", # edgeNGram: NGram match from the start of a token
min_gram: 3,
max_gram: 10,
# token_chars: Elasticsearch will split on characters
# that don't belong to any of these classes
token_chars: %w(letter digit)
}
},
analyzer: {
gs_edgeNGram_analyzer: {
tokenizer: "gs_edgeNGram_tokenizer",
filter: ["lowercase"]
}
}
} do
mappings dynamic: 'false' do
indexes :id, type: 'long'
indexes :name, type: 'text', analyzer: 'gs_edgeNGram_analyzer'
indexes :approval_status, type: 'text'
indexes :scientific_names do
indexes :name,
type: 'text',
analyzer: 'gs_edgeNGram_analyzer',
# Disabling field-length norm (norm). If the norm option is turned on(by default),
# higher weigh would be given for shorter fields, which in our case is irrelevant.
norms: { enabled: false }
end
indexes :alternate_names do
indexes :name, type: 'text', analyzer: 'gs_edgeNGram_analyzer'
end
end
end
searchkick word_start: %i(name alternate_names scientific_names), case_sensitive: false
end
def planting_photos
@@ -108,13 +71,6 @@ class Crop < ApplicationRecord
Photo.joins(:seeds).where("seeds.crop_id": id)
end
# update the Elasticsearch index (only if we're using it in this
# environment)
def update_index(_name_obj)
__elasticsearch__.index_document if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
end
# End Elasticsearch section
def to_s
name
end
@@ -210,14 +166,24 @@ class Crop < ApplicationRecord
update(median_days_to_last_harvest: Planting.where(crop: self).median(:days_to_last_harvest))
end
def self.search(query)
CropSearchService.search(query)
end
def self.case_insensitive_name(name)
where(["lower(crops.name) = :value", { value: name.downcase }])
end
def should_index?
approved?
end
def search_data
{
name: name,
alternate_names: alternate_names.pluck(:name),
scientific_names: scientific_names.pluck(:name),
plantings_count: plantings_count, # boost the crops that are planted the most
planters_ids: plantings.pluck(:owner_id) # boost this product for these members
}
end
private
def count_uses_of_property(col_name)

View File

@@ -8,6 +8,8 @@ class Garden < ApplicationRecord
has_many :plantings, dependent: :destroy
has_many :crops, through: :plantings
belongs_to :garden_type, optional: true
# set up geocoding
geocoded_by :location
after_validation :geocode
@@ -17,6 +19,7 @@ class Garden < ApplicationRecord
default_scope { joins(:owner) } # Ensures owner exists
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
scope :order_by_name, -> { order("lower(name) asc") }
validates :location, length: { maximum: 255 }
validates :slug, uniqueness: true
@@ -43,7 +46,6 @@ class Garden < ApplicationRecord
}.freeze
validates :area_unit, inclusion: { in: AREA_UNITS_VALUES.values,
message: "%<value>s is not a valid area unit" },
allow_nil: true,
allow_blank: true
after_validation :cleanup_area

19
app/models/garden_type.rb Normal file
View File

@@ -0,0 +1,19 @@
class GardenType < ApplicationRecord
extend FriendlyId
friendly_id :name, use: %i(slugged finders)
has_many :gardens, dependent: :nullify
validates :name, presence: true, uniqueness: true
validates :slug, presence: true, uniqueness: true
def garden_type_slug
name.gsub!(/[^A-Za-z]/, '')
end
def subtitler(garden_type)
num = garden_type.gardens.uniq.count
s = num > 1 || num.zero? ? "s are" : " is"
"#{num} garden#{s} using this garden type"
end
end

View File

@@ -57,11 +57,11 @@ class Harvest < ApplicationRecord
validates :quantity, allow_nil: true, numericality: {
only_integer: false, greater_than_or_equal_to: 0
}
validates :unit, allow_nil: true, allow_blank: true, inclusion: {
validates :unit, allow_blank: true, inclusion: {
in: UNITS_VALUES.values, message: "%<value>s is not a valid unit"
}
validates :weight_quantity, allow_nil: true, numericality: { only_integer: false }
validates :weight_unit, allow_nil: true, allow_blank: true, inclusion: {
validates :weight_unit, allow_blank: true, inclusion: {
in: WEIGHT_UNITS_VALUES.values, message: "%<value>s is not a valid unit"
}
validate :crop_must_match_planting

View File

@@ -23,7 +23,7 @@ class Planting < ApplicationRecord
# Ancestry of food
belongs_to :parent_seed, class_name: 'Seed', # parent
foreign_key: 'parent_seed_id',
required: false,
optional: true,
inverse_of: :child_plantings
has_many :child_seeds, class_name: 'Seed', # children
foreign_key: 'parent_planting_id',
@@ -56,10 +56,10 @@ class Planting < ApplicationRecord
validates :quantity, allow_nil: true, numericality: {
only_integer: true, greater_than_or_equal_to: 0
}
validates :sunniness, allow_nil: true, allow_blank: true, inclusion: {
validates :sunniness, allow_blank: true, inclusion: {
in: SUNNINESS_VALUES, message: "%<value>s is not a valid sunniness value"
}
validates :planted_from, allow_nil: true, allow_blank: true, inclusion: {
validates :planted_from, allow_blank: true, inclusion: {
in: PLANTED_FROM_VALUES, message: "%<value>s is not a valid planting method"
}

View File

@@ -1,7 +1,8 @@
class ScientificName < ApplicationRecord
after_commit { |sn| sn.crop.__elasticsearch__.index_document if sn.crop && ENV['GROWSTUFF_ELASTICSEARCH'] == "true" }
belongs_to :crop
belongs_to :creator, class_name: 'Member', inverse_of: :created_scientific_names
validates :name, presence: true
validates :crop, presence: true
after_commit :reindex if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
delegate :reindex, to: :crop
end

View File

@@ -14,7 +14,7 @@ class Seed < ApplicationRecord
# Relationships
belongs_to :crop
belongs_to :parent_planting, class_name: 'Planting', foreign_key: 'parent_planting_id',
required: false, inverse_of: :child_seeds # parent
optional: true, inverse_of: :child_seeds # parent
has_many :child_plantings, class_name: 'Planting',
foreign_key: 'parent_seed_id', dependent: :nullify,
inverse_of: :parent_seed # children
@@ -29,17 +29,17 @@ class Seed < ApplicationRecord
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :days_until_maturity_max, allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :tradable_to, allow_nil: false, allow_blank: false,
inclusion: { in: TRADABLE_TO_VALUES, message: "You may only trade seed nowhere, "\
validates :tradable_to, allow_blank: false,
inclusion: { in: TRADABLE_TO_VALUES, message: "You may only trade seed nowhere, "\
"locally, nationally, or internationally" }
validates :organic, allow_nil: false, allow_blank: false,
inclusion: { in: ORGANIC_VALUES, message: "You must say whether the seeds "\
validates :organic, allow_blank: false,
inclusion: { in: ORGANIC_VALUES, message: "You must say whether the seeds "\
"are organic or not, or that you don't know" }
validates :gmo, allow_nil: false, allow_blank: false,
inclusion: { in: GMO_VALUES, message: "You must say whether the seeds are "\
validates :gmo, allow_blank: false,
inclusion: { in: GMO_VALUES, message: "You must say whether the seeds are "\
"genetically modified or not, or that you don't know" }
validates :heirloom, allow_nil: false, allow_blank: false,
inclusion: { in: HEIRLOOM_VALUES, message: "You must say whether the seeds"\
validates :heirloom, allow_blank: false,
inclusion: { in: HEIRLOOM_VALUES, message: "You must say whether the seeds"\
"are heirloom, hybrid, or unknown" }
#

View File

@@ -1,40 +1,49 @@
class CropSearchService
# Crop.search(string)
def self.search(query)
if ENV['GROWSTUFF_ELASTICSEARCH'] == "true"
search_str = query.nil? ? "" : query.downcase
response = Crop.__elasticsearch__.search(
query: {
bool: {
filter: {
term: { "approval_status" => "approved" }
},
must: {
query_string: {
query: "*#{search_str}*"
}
}
}
}
)
response.records.to_a
def self.search(query, page: 1, per_page: 12, current_member: nil)
if ENV["GROWSTUFF_ELASTICSEARCH"] == "true"
elasticsearch(query, page: page, per_page: per_page, current_member: current_member)
else
# if we don't have elasticsearch, just do a basic SQL query.
# also, make sure it's an actual array not an activerecord
# collection, so it matches what we get from elasticsearch and we can
# manipulate it in the same ways (eg. deleting elements without deleting
# the whole record from the db)
matches = Crop.approved.where("name ILIKE ?", "%#{query}%").to_a
# we want to make sure that exact matches come first, even if not
# using elasticsearch (eg. in development)
exact_match = Crop.approved.find_by(name: query)
if exact_match
matches.delete(exact_match)
matches.unshift(exact_match)
end
matches
dbsearch(query, page: page, per_page: per_page)
end
end
def self.elasticsearch(query, page: 1, per_page: 12, current_member: nil)
search_params = {
page: page,
per_page: per_page,
fields: %i(name^5 alternate_names scientific_names),
match: :word_start,
boost_by: [:plantings_count],
includes: %i(scientific_names alternate_names),
misspellings: { edit_distance: 2 }
}
# prioritise crops the member has planted
search_params[:boost_where] = { planters_ids: current_member.id } if current_member
Crop.search(query, search_params)
end
def self.dbsearch(query, page: 1, per_page: 12)
# if we don't have elasticsearch, just do a basic SQL query.
# also, make sure it's an actual array not an activerecord
# collection, so it matches what we get from elasticsearch and we can
# manipulate it in the same ways (eg. deleting elements without deleting
# the whole record from the db)
matcher = "%#{query}%"
matches = Crop.approved
.left_outer_joins(:alternate_names, :scientific_names)
.where("crops.name ILIKE ? OR alternate_names.name ILIKE ? OR scientific_names.name ILIKE ?",
matcher, matcher, matcher)
matches = matches.to_a
# we want to make sure that exact matches come first, even if not
# using elasticsearch (eg. in development)
exact_match = Crop.approved.find_by(name: query)
if exact_match
matches.delete(exact_match)
matches.unshift(exact_match)
end
matches.paginate(page: page, per_page: per_page)
end
end

View File

@@ -5,10 +5,12 @@
.row
.col-md-4
%h2 Site admin
%ul#site_admin
%li= link_to "Roles", roles_path
%li= link_to "Forums", forums_path
%li= link_to "CMS", comfy_admin_cms_path
%li= link_to t('.garden_types'), garden_types_path
.col-md-4
%h2 Crop data admin

View File

@@ -11,6 +11,8 @@
- unless crop.scientific_names.empty?
.scientificname
= crop.scientific_names.first.name
.plantingcount
Planted
= pluralize(crop.plantings.size, "time")
- if crop.annual? && crop.median_lifespan.present?
.planting-lifespan
lifespan
%strong= crop.median_lifespan
days

View File

@@ -1,20 +1,5 @@
.varieties
- if crop.parent
%p
= crop.name
is a variety of
= succeed "." do
= link_to crop.parent, crop.parent
- unless crop.varieties.empty?
%p
Varieties of #{crop.name}:
- max = 5
= render partial: 'hierarchy', locals: { display_crops: [crop], max: max }
- if max != 0 && @count > max
= button_tag "Show all #{@count - 1} varieties", class: 'btn btn-link toggle crop-hierarchy'
= button_tag "Show less varieties", class: 'btn btn-link toggle crop-hierarchy hide'
- if !crop.parent && crop.varieties.empty?
%p None known.
- if crop.varieties.size.positive?
%h3 Varieties
.row
- crop.varieties.order(:name).each do |v|
.col-md-2.six-across= render 'crops/thumbnail', crop: v

View File

@@ -1,7 +1,7 @@
- if @term
- content_for :title, "Crops matching \"#{@term}\""
- if @matches
- content_for :subtitle, "#{@matches.size} total"
- if @crops
- content_for :subtitle, "#{@crops.size} total"
- else
- content_for :title, "Crop search"
@@ -15,7 +15,7 @@
value: @term
= submit_tag "Search", class: 'btn btn-primary'
- if @matches.empty?
- if @crops.empty?
%h2 No results found
%p
Sorry, we couldn't find any crops that matched your search for "#{@term}".
@@ -26,13 +26,12 @@
- else
.pagination
= will_paginate @paginated_matches
= will_paginate @crops
#paginated_matches
.row
- @paginated_matches.each do |c|
.col-md-2.six-across
= render partial: "thumbnail", locals: { crop: c }
- @crops.each do |c|
.col-md-2.six-across= render partial: "thumbnail", locals: { crop: c }
.pagination
= will_paginate @paginated_matches
= will_paginate @crops

View File

@@ -36,7 +36,7 @@
%h2 Photos
%p= render 'crops/photos', photos: @photos
%p= link_to 'more photos', crop_photos_path(@crop)
.row
.col-md-3
%h3 Sunniness
@@ -48,6 +48,7 @@
%h3 Harvested for
= pie_chart crop_harvested_for_path(@crop, format: :json), legend: "bottom"
.varieties= render 'varieties', crop: @crop
%h3 Crop Map
%p
@@ -90,8 +91,13 @@
= render 'scientific_names', crop: @crop
= render 'alternate_names', crop: @crop
%h4 #{@crop.name.capitalize} varieties
= render 'varieties', crop: @crop
- if @crop.parent
.parent-crop
= @crop.name
is a variety of
= succeed "." do
= link_to @crop.parent, @crop.parent
= render 'crops/thumbnail', crop: @crop.parent
= render 'plantings', crop: @crop
= render 'harvests', crop: @crop

View File

@@ -0,0 +1,4 @@
.btn-group.garden_type-actions
= render 'shared/buttons/edit', path: edit_garden_type_path(garden_type)
.pull-right
= render 'shared/buttons/delete', path: garden_type_path(garden_type)

View File

@@ -0,0 +1,20 @@
= form_for @garden_type, html: { class: 'form-horizontal', role: "form" } do |f|
- if @garden_type.errors.any?
#error_explanation
%h2= "#{pluralize(@garden_type.errors.count, "error")} prohibited this garden_type from being saved:"
%ul
- @garden_type.errors.full_messages.each do |message|
%li= message
%h2 Basic information
.form-group
= f.label :name, class: 'control-label col-md-2'
.col-md-8
= f.text_field :name, class: 'form-control'
%span.help-block
The name for the garden_type, i.e. "organic", in English (required).
.form-group
.form-actions.col-md-offset-2.col-md-8
= f.submit 'Save', class: 'btn btn-primary'

View File

@@ -0,0 +1,7 @@
- cache cache_key_for(GardenType, garden_type.id) do
.thumbnail
.garden_type-thumbnail
- if garden_type
.garden_typeinfo
.garden_typename
= link_to garden_type.name, garden_type

View File

@@ -0,0 +1,4 @@
- content_for :title, "Edit GardenType"
- if can? :update, @garden_type
= render 'form'

View File

@@ -0,0 +1,17 @@
- content_for :title, 'GardenTypes'
%p
#{ENV['GROWSTUFF_SITE_NAME']} tracks who's growing what, where.
View any garden_type page to see which of our members have used it.
.row
- @garden_types.each do |garden_type|
.col-md-2.six-across
= render partial: "thumbnail", locals: { garden_type: garden_type }
- if can? :create, GardenType
%div
= link_to 'New GardenType', new_garden_type_path, class: 'btn btn-primary'
.pagination
= will_paginate @garden_types

View File

@@ -0,0 +1,4 @@
- content_for :title, "New GardenType"
- if can? :create, @garden_type
= render 'form'

View File

@@ -0,0 +1,12 @@
- content_for :title, @garden_type.name.capitalize
- content_for :subtitle, @garden_type.subtitler(@garden_type)
- if can?(:create, @garden_type) && can?(:edit, @garden_type) && can?(:destroy, @garden_type)
- content_for :buttonbar do
= render 'garden_types/actions', garden_type: @garden_type
- if @garden_type.gardens.uniq.empty?
%p There are no gardens to display.
- else
- @garden_type.gardens.uniq.each do |garden|
= render 'gardens/overview', garden: garden

View File

@@ -43,6 +43,15 @@
.col-md-2
= f.select(:area_unit, Garden::AREA_UNITS_VALUES, { include_blank: false }, class: 'form-control')
.form-group
= f.label :garden_type, class: 'control-label col-md-2'
.col-md-2
= collection_select(:garden, :garden_type_id,
GardenType.all.order(:name),
:id, :name,
selected: @garden.garden_type_id,
class: 'form-control')
.form-group
= f.label :active, 'Active? ', class: 'control-label col-md-2'
.col-md-8

View File

@@ -25,7 +25,7 @@
= f.label :garden_id, 'Where did you plant it?', class: 'control-label col-md-2'
.col-md-8
= collection_select(:planting, :garden_id,
current_member.gardens.active.order("lower(gardens.name)"),
current_member.gardens.active.order_by_name,
:id, :name,
selected: @planting.garden_id || @garden.id,
class: 'form-control')

View File

@@ -89,7 +89,7 @@ Rails.application.configure do
config.active_record.dump_schema_after_migration = false
# Growstuff configuration
config.action_mailer.default_url_options = { host: ENV['MAIL_SENDER_HOST'] }
config.action_mailer.default_url_options = { host: ENV['HOST'] }
config.action_mailer.smtp_settings = {
user_name: ENV['SENDGRID_USERNAME'],

View File

@@ -202,6 +202,8 @@ en:
browse_members: Browse Members
community: Community
community_map: Community Map
garden_type: Garden Type
garden_types: Garden Types
crop_wrangling: Crop Wrangling
crops: Crops
current_memberlogin_name: "%{current_memberlogin_name}"
@@ -309,6 +311,7 @@ en:
planting: Please sign in or sign up to plant something.
post: Please sign in or sign up to post.
seed: Please sign in or sign up to add seeds.
garden_type: Not authorized. Only admins can create garden types.
manage:
all: Not authorized to %{action} %{subject}.
read:

View File

@@ -1,6 +1,9 @@
Rails.application.routes.draw do
get '/robots.txt' => 'robots#robots'
resources :garden_types
resources :plant_parts
devise_for :members, controllers: {
registrations: "registrations",
passwords: "passwords",

View File

@@ -0,0 +1,17 @@
class CreateGardenTypes < ActiveRecord::Migration[5.2]
def change
create_table :garden_types do |t|
t.text :name, null: false, unique: true
t.text :slug, null: false, unique: true
t.timestamps null: false
end
add_column :gardens, :garden_type_id, :integer
add_index :gardens, :garden_type_id
['organic', 'conventional', 'container', 'vertical', 'greenhouse', 'rooftop', 'no-dig', 'raised bed',
'wicking bed', 'permaculture', 'hydroponic', 'aquaponic', 'orchard', 'food forest',
'biodynamic'].each do |name|
say "Creating #{name}"
GardenType.create! name: name
end
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_03_17_023129) do
ActiveRecord::Schema.define(version: 2019_03_26_063855) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -200,6 +200,13 @@ ActiveRecord::Schema.define(version: 2019_03_17_023129) do
t.index ["slug"], name: "index_forums_on_slug", unique: true
end
create_table "garden_types", force: :cascade do |t|
t.text "name", null: false
t.text "slug", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "gardens", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.integer "owner_id"
@@ -213,6 +220,8 @@ ActiveRecord::Schema.define(version: 2019_03_17_023129) do
t.float "longitude"
t.decimal "area"
t.string "area_unit"
t.integer "garden_type_id"
t.index ["garden_type_id"], name: "index_gardens_on_garden_type_id"
t.index ["owner_id"], name: "index_gardens_on_owner_id"
t.index ["slug"], name: "index_gardens_on_slug", unique: true
end

View File

@@ -1,11 +1,6 @@
namespace :search do
desc "Create elastic search index"
task create: :environment do
puts Crop.__elasticsearch__.create_index! force: true
end
desc 'Refresh elastic search index'
task refresh: :environment do
puts Crop.__elasticsearch__.refresh_index!
desc 'reindex'
task reindex: :environment do
puts Crop.reindex
end
end

View File

@@ -40,10 +40,21 @@ describe CropsController do
describe "GET crop search" do
describe 'fetches the crop search page' do
before { get :search }
let!(:tomato) { FactoryBot.create :tomato }
let!(:maize) { FactoryBot.create :maize }
before { Crop.reindex if ENV["GROWSTUFF_ELASTICSEARCH"] == "true" }
describe 'search form page' do
before { get :search }
it { is_expected.to be_success }
it { is_expected.to render_template("crops/search") }
it { is_expected.to be_success }
it { is_expected.to render_template("crops/search") }
end
describe 'perform a search' do
before { get :search, params: { term: 'tom' } }
it { expect(assigns(:term)).to eq 'tom' }
it { expect(assigns(:crops).map(&:name)).to eq ['tomato'] }
end
end
end

View File

@@ -0,0 +1,90 @@
require 'rails_helper'
RSpec.describe GardenTypesController, type: :controller do
include Devise::Test::ControllerHelpers
let(:valid_params) { { name: 'My second GardenType' } }
let(:garden_type) { FactoryBot.create :garden_type }
let(:member) { FactoryBot.create(:member) }
let(:admin_member) { FactoryBot.create(:admin) }
context "when not signed in" do
describe 'GET new' do
before { get :new, params: { id: garden_type.to_param } }
it { expect(response).to redirect_to(root_path) }
end
describe 'PUT create' do
before { put :create, params: { garden_type: valid_params } }
it { expect(response).to redirect_to(root_path) }
end
describe 'changing existing records' do
before do
allow(GardenType).to receive(:find).and_return(:garden_type)
expect(garden_type).not_to receive(:save)
expect(garden_type).not_to receive(:save!)
expect(garden_type).not_to receive(:update)
expect(garden_type).not_to receive(:update!)
expect(garden_type).not_to receive(:destroy)
end
describe 'GET edit' do
before { get :edit, params: { id: garden_type.to_param } }
it { expect(response).to redirect_to(root_path) }
end
describe 'POST update' do
before { post :update, params: { id: garden_type.to_param, garden_type: valid_params } }
it { expect(response).to redirect_to(root_path) }
end
describe 'DELETE' do
before { delete :destroy, params: { id: garden_type.to_param, params: { garden_type: valid_params } } }
it { expect(response).to redirect_to(root_path) }
end
end
end
context "when signed in as a member" do
before { sign_in member }
let!(:member) { FactoryBot.create(:member) }
describe "for any garden_type" do
let(:any_garden_type) { double('garden_type') }
before do
expect(GardenType).to receive(:find).and_return(:any_garden_type)
expect(any_garden_type).not_to receive(:save)
expect(any_garden_type).not_to receive(:save!)
expect(any_garden_type).not_to receive(:update)
expect(any_garden_type).not_to receive(:update!)
expect(any_garden_type).not_to receive(:destroy)
end
describe 'GET edit' do
before { get :edit, params: { id: any_garden_type.to_param } }
it { expect(response).to redirect_to(root_path) }
end
describe 'POST update' do
before { post :update, params: { id: any_garden_type.to_param, garden_type: valid_params } }
it { expect(response).to redirect_to(root_path) }
end
describe 'DELETE' do
before { delete :destroy, params: { id: any_garden_type.to_param, params: { garden_type: valid_params } } }
it { expect(response).to redirect_to(root_path) }
end
end
end
end

View File

@@ -0,0 +1,5 @@
FactoryBot.define do
factory :garden_type do
name { "homemade swamp" }
end
end

View File

@@ -4,7 +4,7 @@ FactoryBot.define do
factory :photo do
owner
flickr_photo_id { 1 }
title { Faker::HarryPotter.quote }
title { Faker::Movies::HarryPotter.quote }
license_name { "CC-BY" }
license_url { "http://example.com/license.html" }
thumbnail_url { "http://example.com/#{Faker::File.file_name}.jpg" }

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
feature "forums", js: true do
describe "forums", js: true do
context "as an admin user" do
let(:member) { create :admin_member }
let(:forum) { create :forum }
background do
before do
login_as member
end
scenario "navigating to forum admin without js", js: false do
it "navigating to forum admin without js", js: false do
visit root_path
click_link "Admin"
expect(current_path).to eq admin_path
@@ -20,7 +20,7 @@ feature "forums", js: true do
expect(page).to have_content "New forum"
end
scenario "navigating to forum admin with js" do
it "navigating to forum admin with js" do
visit root_path
click_link member.login_name
click_link "Admin"
@@ -32,7 +32,7 @@ feature "forums", js: true do
expect(page).to have_content "New forum"
end
scenario "adding a forum" do
it "adding a forum" do
visit forums_path
click_link "New forum"
expect(current_path).to eq new_forum_path
@@ -43,7 +43,7 @@ feature "forums", js: true do
expect(page).to have_content 'Forum was successfully created'
end
scenario 'editing forum' do
it 'editing forum' do
visit forum_path forum
click_link 'Edit'
fill_in 'Name', with: 'Something else'
@@ -54,7 +54,7 @@ feature "forums", js: true do
expect(page).to have_content 'Something else'
end
scenario 'deleting forum' do
it 'deleting forum' do
visit forum_path forum
click_link 'Delete'
expect(current_path).to eq forums_path

View File

@@ -1,16 +1,16 @@
require 'rails_helper'
feature "cms admin" do
describe "cms admin" do
let(:member) { create :member }
let(:admin_member) { create :admin_member }
scenario "can't view CMS admin if not signed in" do
it "can't view CMS admin if not signed in" do
visit comfy_admin_cms_path
expect(current_path).to eq root_path
expect(page).to have_content "Please sign in as an admin user"
end
scenario "can't view CMS admin if not an admin member" do
it "can't view CMS admin if not an admin member" do
# sign in as an ordinary member
login_as member
visit comfy_admin_cms_path
@@ -18,7 +18,7 @@ feature "cms admin" do
expect(page).to have_content "Please sign in as an admin user"
end
scenario "admin members can view CMS admin area" do
it "admin members can view CMS admin area" do
login_as admin_member
visit comfy_admin_cms_path
expect(current_path).to match(/#{comfy_admin_cms_path}/) # match any CMS admin page

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
feature 'Commenting on a post' do
describe 'Commenting on a post' do
let(:member) { create :member }
let(:post) { create :post, author: member }
background do
before do
login_as member
visit new_comment_path post_id: post.id
end
scenario "creating a comment" do
it "creating a comment" do
fill_in "comment_body", with: "This is a sample test for comment"
click_button "Post comment"
expect(page).to have_content "comment was successfully created."
@@ -19,11 +19,11 @@ feature 'Commenting on a post' do
context "editing a comment" do
let(:existing_comment) { create :comment, post: post, author: member }
background do
before do
visit edit_comment_path existing_comment
end
scenario "saving edit" do
it "saving edit" do
fill_in "comment_body", with: "Testing edit for comment"
click_button "Post comment"
expect(page).to have_content "comment was successfully updated."

View File

@@ -1,16 +1,16 @@
require 'rails_helper'
feature "Alternate names", js: true do
describe "Alternate names", js: true do
let!(:alternate_eggplant) { create :alternate_eggplant }
let(:crop) { alternate_eggplant.crop }
scenario "Display alternate names on crop page" do
it "Display alternate names on crop page" do
visit crop_path(alternate_eggplant.crop)
expect(page.status_code).to equal 200
expect(page).to have_content alternate_eggplant.name
end
scenario "Index page for alternate names" do
it "Index page for alternate names" do
visit alternate_names_path
expect(page).to have_content alternate_eggplant.name
end
@@ -19,11 +19,11 @@ feature "Alternate names", js: true do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:member) { crop_wranglers.first }
background do
before do
login_as member
end
scenario "Crop wranglers can edit alternate names" do
it "Crop wranglers can edit alternate names" do
visit crop_path(crop)
expect(page.status_code).to equal 200
expect(page).to have_content "CROP WRANGLER"
@@ -39,7 +39,7 @@ feature "Alternate names", js: true do
expect(page).to have_content 'Alternate name was successfully updated'
end
scenario "Crop wranglers can delete alternate names" do
it "Crop wranglers can delete alternate names" do
visit crop_path(alternate_eggplant.crop)
expect(page).to have_link "Delete",
href: alternate_name_path(alternate_eggplant)
@@ -49,7 +49,7 @@ feature "Alternate names", js: true do
expect(page).to have_content 'Alternate name was successfully deleted'
end
scenario "Crop wranglers can add alternate names" do
it "Crop wranglers can add alternate names" do
visit crop_path(crop)
expect(page).to have_link "Add",
href: new_alternate_name_path(crop_id: crop.id)
@@ -63,7 +63,7 @@ feature "Alternate names", js: true do
expect(page).to have_content 'Alternate name was successfully created'
end
scenario "The show-alternate-name page works" do
it "The show-alternate-name page works" do
visit alternate_name_path(alternate_eggplant)
expect(page.status_code).to equal 200
expect(page).to have_content alternate_eggplant.crop.name
@@ -73,7 +73,7 @@ feature "Alternate names", js: true do
let(:rejected_crop) { create :rejected_crop }
let(:pending_alt_name) { create :alternate_name, crop: rejected_crop }
scenario "Displays crop rejection message" do
it "Displays crop rejection message" do
visit alternate_name_path(pending_alt_name)
expect(page).to have_content "This crop was rejected for the following reason: Totally fake"
end

View File

@@ -1,28 +1,28 @@
require 'rails_helper'
feature "browse crops" do
describe "browse crops" do
let(:tomato) { create :tomato }
let(:maize) { create :maize }
let(:pending_crop) { create :crop_request }
let(:rejected_crop) { create :rejected_crop }
scenario "has a form for sorting by" do
it "has a form for sorting by" do
visit crops_path
expect(page).to have_css "select#sort"
end
scenario "shows a list of crops" do
it "shows a list of crops" do
crop1 = tomato
visit crops_path
expect(page).to have_content crop1.name
end
scenario "pending crops are not listed" do
it "pending crops are not listed" do
visit crops_path
expect(page).not_to have_content pending_crop.name
end
scenario "rejected crops are not listed" do
it "rejected crops are not listed" do
visit crops_path
expect(page).not_to have_content rejected_crop.name
end

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
feature "Crop - " do
describe "Crop - " do
let!(:crop_wrangler) { FactoryBot.create :crop_wrangling_member }
let!(:member) { FactoryBot.create :member }
background do
before do
login_as member
visit new_crop_path
end
scenario "creating a crop with multiple scientific and alternate name", :js do
it "creating a crop with multiple scientific and alternate name", :js do
within "form#new_crop" do
fill_in "crop_name", with: "Philippine flower"
fill_in "en_wikipedia_url", with: "https://en.wikipedia.org/wiki/Jasminum_sambac"

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "crop detail page", js: true do
describe "crop detail page", js: true do
subject do
# Update the medians after all the
# data has been loaded
@@ -14,82 +14,11 @@ feature "crop detail page", js: true do
let(:crop) { create :crop }
context "varieties" do
scenario "The crop DOES NOT have varieties" do
it "The crop DOES NOT have varieties" do
visit crop_path(crop)
within ".varieties" do
expect(page).to have_no_selector('li', text: /tomato/i)
expect(page).to have_no_selector('button', text: /Show+/i)
end
end
scenario "The crop has one variety" do
create :crop, name: 'Roma tomato 1', parent: crop
subject
within ".varieties" do
# It lists all 2 items (note: including the top level item.)
expect(page).to have_selector('li', text: /tomato/i, count: 2)
# It DOES NOT have "Show all/less" toggle link
expect(page).to have_no_selector('button', text: /Show+/i)
end
end
context "many" do
let!(:roma1) { create :crop, name: 'Roma tomato 1', parent: crop }
let!(:roma2) { create :crop, name: 'Roma tomato 2', parent: crop }
let!(:roma3) { create :crop, name: 'Roma tomato 3', parent: crop }
let!(:roma4) { create :crop, name: 'Roma tomato 4', parent: crop }
scenario "The crop has 4 varieties" do
subject
within ".varieties" do
# It lists all 5 items (note: including the top level item.)
expect(page).to have_selector('li', text: /tomato/i, count: 5)
# It DOES NOT have "Show all/less" toggle link
expect(page).to have_no_selector('button', text: /Show+/i)
end
end
scenario "The crop has 5 varieties, including grandchild", js: true do
create :crop, name: 'Roma tomato child 1', parent: roma4
subject
within ".varieties" do
# It lists the first 5 items (note: including the top level item.)
# It HAS have "Show all" toggle link but not "Show less" link
expect(page).to have_selector('li', text: /tomato/i, count: 5)
expect(page).to have_selector('li', text: 'Roma tomato 4')
expect(page).to have_no_selector('li', text: 'Roma tomato child 1')
# It shows the total number (5) correctly
expect(page).to have_selector('button', text: /Show all 5 +/i)
expect(page).to have_no_selector('button', text: /Show less+/i)
# Clik "Show all" link
page.find('button', text: /Show all+/).click
# It lists all 6 items (note: including the top level item.)
# It HAS have "Show less" toggle link but not "Show all" link
expect(page).to have_selector('li', text: /tomato/i, count: 6)
expect(page).to have_selector('li', text: 'Roma tomato 4')
expect(page).to have_selector('li', text: 'Roma tomato child 1')
expect(page).to have_no_selector('button', text: /Show all+/i)
expect(page).to have_selector('button', text: /Show less+/i)
# Clik "Show less" link
page.find('button', text: /Show less+/).click
# It lists 5 items (note: including the top level item.)
# It HAS have "Show all" toggle link but not "Show less" link
expect(page).to have_selector('li', text: /tomato/i, count: 5)
expect(page).to have_selector('li', text: 'Roma tomato 4')
expect(page).to have_no_selector('li', text: 'Roma tomato child 1')
expect(page).to have_selector('button', text: /Show all 5 +/i)
expect(page).to have_no_selector('button', text: /Show less+/i)
end
expect(page).not_to have_text 'tomato'
end
end
end
@@ -97,54 +26,54 @@ feature "crop detail page", js: true do
context "signed in member" do
let(:member) { create :member }
background do
before do
login_as(member)
end
context "action buttons" do
background { subject }
before { subject }
scenario "has a link to plant the crop" do
it "has a link to plant the crop" do
expect(page).to have_link "Plant #{crop.name}", href: new_planting_path(crop_id: crop.id)
end
scenario "has a link to harvest the crop" do
it "has a link to harvest the crop" do
expect(page).to have_link "Harvest #{crop.name}", href: new_harvest_path(crop_id: crop.id)
end
scenario "has a link to add seeds" do
it "has a link to add seeds" do
expect(page).to have_link "Add #{crop.name} seeds to stash", href: new_seed_path(crop_id: crop.id)
end
end
context "SEO" do
background { subject }
before { subject }
scenario "has seed heading with SEO" do
it "has seed heading with SEO" do
expect(page).to have_content "Find #{crop.name} seeds"
end
scenario "has harvest heading with SEO" do
it "has harvest heading with SEO" do
expect(page).to have_content "#{crop.name.capitalize} harvests"
end
scenario "has planting heading with SEO" do
it "has planting heading with SEO" do
expect(page).to have_content "See who's planted #{crop.name.pluralize}"
end
scenario "has planting advice with SEO" do
it "has planting advice with SEO" do
expect(page).to have_content "How to grow #{crop.name}"
end
scenario "has a link to Wikipedia with SEO" do
it "has a link to Wikipedia with SEO" do
expect(page).to have_content "Learn more about #{crop.name}"
expect(page).to have_link "Wikipedia (English)", href: crop.en_wikipedia_url
end
scenario "has a link to OpenFarm" do
it "has a link to OpenFarm" do
expect(page).to have_link "OpenFarm - Growing guide",
href: "https://openfarm.cc/en/crops/#{CGI.escape crop.name}"
end
scenario "has a link to gardenate" do
it "has a link to gardenate" do
expect(page).to have_link "Gardenate - Planting reminders",
href: "http://www.gardenate.com/plant/#{CGI.escape crop.name}"
end
@@ -155,18 +84,18 @@ feature "crop detail page", js: true do
let(:member) { create :member }
let(:seed) { create :seed, crop: crop, quantity: 20, owner: member }
scenario "User not signed in" do
it "User not signed in" do
visit crop_path(seed.crop)
expect(page).not_to have_content "You have 20 seeds"
end
scenario "User signed in" do
it "User signed in" do
login_as(member)
visit crop_path(seed.crop)
expect(page).to have_link "You have 20 seeds of this crop."
end
scenario "click link to your owned seeds" do
it "click link to your owned seeds" do
login_as(member)
visit crop_path(seed.crop)
click_link "You have 20 seeds of this crop."
@@ -190,7 +119,7 @@ feature "crop detail page", js: true do
end
it "predicts harvest" do
is_expected.to have_text("First harvest expected 20 days after planting")
expect(subject).to have_text("First harvest expected 20 days after planting")
end
end
end
@@ -213,12 +142,12 @@ feature "crop detail page", js: true do
end
it "predicts lifespan" do
is_expected.to have_text "Median lifespan"
is_expected.to have_text "99 days"
expect(subject).to have_text "Median lifespan"
expect(subject).to have_text "99 days"
end
it "describes annual crops" do
is_expected.to have_text(
expect(subject).to have_text(
"#{crop.name} is an annual crop (living and reproducing in a single year or less)"
)
end
@@ -235,7 +164,7 @@ feature "crop detail page", js: true do
end
it "describes perennial crops" do
is_expected.to have_text("#{crop.name} is a perennial crop (living more than two years)")
expect(subject).to have_text("#{crop.name} is a perennial crop (living more than two years)")
end
end

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "crop detail page", js: true do
describe "crop detail page", js: true do
subject { page }
let!(:member) { FactoryBot.create :member }
@@ -50,12 +50,14 @@ feature "crop detail page", js: true do
end
context "when signed in" do
background { login_as(FactoryBot.create(:member)) }
before { login_as(FactoryBot.create(:member)) }
include_examples "shows photos"
end
context "when signed in as photos owner" do
background { login_as(member) }
before { login_as(member) }
include_examples "shows photos"
end

View File

@@ -1,7 +1,7 @@
require 'rails_helper'
feature "crop search" do
scenario "search results show the search term in title" do
describe "crop search" do
it "search results show the search term in title" do
visit root_path
within "form#navbar-search" do
fill_in "term", with: "tomato"
@@ -10,12 +10,12 @@ feature "crop search" do
expect(page).to have_css "h1", text: "Crops matching \"tomato\""
end
scenario "search page with no search term shows suitable title" do
it "search page with no search term shows suitable title" do
visit search_crops_path
expect(page).to have_css "h1", text: "Crop search"
end
scenario "search page has a search form on it" do
it "search page has a search form on it" do
visit search_crops_path
expect(page).to have_css "form#crop-search"
end

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "crop wranglers", js: true do
describe "crop wranglers", js: true do
context "signed in wrangler" do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:wrangler) { crop_wranglers.first }
@@ -8,9 +8,9 @@ feature "crop wranglers", js: true do
let!(:requested_crop) { create :crop_request }
let!(:rejected_crop) { create :rejected_crop }
background { login_as wrangler }
before { login_as wrangler }
scenario "sees crop wranglers listed on the crop wrangler page" do
it "sees crop wranglers listed on the crop wrangler page" do
visit root_path
click_link wrangler.login_name
click_link 'Crop Wrangling'
@@ -23,7 +23,7 @@ feature "crop wranglers", js: true do
end
end
scenario "can see list of crops with extra detail of who created a crop" do
it "can see list of crops with extra detail of who created a crop" do
visit root_path
click_link wrangler.login_name
click_link 'Crop Wrangling'
@@ -40,7 +40,7 @@ feature "crop wranglers", js: true do
it { expect(page).to have_link 'Delete' }
end
scenario "can create a new crop" do
it "can create a new crop" do
visit root_path
click_link wrangler.login_name
click_link 'Crop Wrangling'
@@ -53,13 +53,13 @@ feature "crop wranglers", js: true do
expect(page).to have_content 'planticus maximus'
end
scenario "View pending crops" do
it "View pending crops" do
visit crop_path(requested_crop)
expect(page).to have_content "This crop is currently pending approval."
expect(page).to have_content "Please approve this even though it's fake."
end
scenario "View rejected crops" do
it "View rejected crops" do
visit crop_path(rejected_crop)
expect(page).to have_content "This crop was rejected for the following reason: Totally fake"
end
@@ -69,14 +69,14 @@ feature "crop wranglers", js: true do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:member) { create :member }
background { login_as member }
before { login_as member }
scenario "can't see wrangling page without js", js: false do
it "can't see wrangling page without js", js: false do
visit root_path
expect(page).not_to have_link "Crop Wrangling"
end
scenario "can't see wrangling page with js" do
it "can't see wrangling page with js" do
visit root_path
click_link member.login_name
expect(page).not_to have_link "Crop Wrangling"

View File

@@ -1,27 +1,27 @@
require 'rails_helper'
feature "crop wrangling button" do
describe "crop wrangling button" do
let(:crop_wrangler) { create :crop_wrangling_member }
let(:member) { create :member }
context "crop wrangling button" do
background do
before do
login_as crop_wrangler
visit crops_path
end
scenario "has a link to crop wrangling page" do
it "has a link to crop wrangling page" do
expect(page).to have_link "Wrangle Crops", href: wrangle_crops_path
end
end
context "crop wrangling button" do
background do
before do
login_as member
visit crops_path
end
scenario "has no link to crop wrangling page" do
it "has no link to crop wrangling page" do
expect(page).to have_no_link "Wrangle Crops", href: wrangle_crops_path
end
end

View File

@@ -1,20 +1,20 @@
require 'rails_helper'
feature "Delete crop spec" do
describe "Delete crop spec" do
context "As a crop wrangler" do
let(:wrangler) { FactoryBot.create :crop_wrangling_member }
let!(:pending_crop) { FactoryBot.create :crop_request }
let!(:approved_crop) { FactoryBot.create :crop }
background { login_as wrangler }
before { login_as wrangler }
scenario "Delete approved crop" do
it "Delete approved crop" do
visit crop_path(approved_crop)
click_link 'Delete'
expect(page).to have_content "crop was successfully destroyed"
end
scenario "Delete pending crop" do
it "Delete pending crop" do
visit crop_path(pending_crop)
click_link 'Delete'
expect(page).to have_content "crop was successfully destroyed"

View File

@@ -1,10 +1,10 @@
require 'rails_helper'
feature "irregular crop inflections" do
describe "irregular crop inflections" do
# We're just testing a couple of representative crops to
# check that inflection works: you don't need to add
# every crop here.
scenario "crops which are mass nouns" do
it "crops which are mass nouns" do
expect("kale".pluralize).to eq "kale"
expect("broccoli".pluralize).to eq "broccoli"
expect("square foot".pluralize).to eq "square feet"
@@ -26,29 +26,29 @@ feature "irregular crop inflections" do
expect("star anise".pluralize).to eq "star anise"
end
scenario "crops which are particularly irregular" do
it "crops which are particularly irregular" do
expect("curry leaf".pluralize).to eq "curry leaves"
end
scenario "crops which require -es" do
it "crops which require -es" do
expect("mango".pluralize).to eq "mangoes"
expect("potato".pluralize).to eq "potatoes"
end
scenario "crops where the first crop would normally be pluralized" do
it "crops where the first crop would normally be pluralized" do
expect("Potato Onion".pluralize).to eq "Potato Onions"
expect("pear tomato".pluralize).to eq "pear tomatoes"
expect("chilli pepper".pluralize).to eq "chilli peppers"
end
scenario "crops where the proper name succeeds the crop that would normally be pluralized" do
it "crops where the proper name succeeds the crop that would normally be pluralized" do
expect("potato Taranaki".pluralize).to eq "potato Taranaki"
expect("potato Gladstone".pluralize).to eq "potato Gladstone"
expect("potato matariki".pluralize).to eq "potato matariki"
expect("spinach Santana".pluralize).to eq "spinach Santana"
end
scenario "crops of Māori origin" do
it "crops of Māori origin" do
expect("kūmara".pluralize).to eq "kūmara"
expect("pūhā".pluralize).to eq "pūhā"
end

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
feature "Requesting a new crop" do
describe "Requesting a new crop" do
context "As a regular member" do
let(:member) { create :member }
let!(:wrangler) { create :crop_wrangling_member }
background do
before do
login_as member
end
scenario "Submit request" do
it "Submit request" do
visit new_crop_path
fill_in "Name", with: "Couch potato"
fill_in "request_notes", with: "Couch potatoes are real for real."
@@ -24,9 +24,9 @@ feature "Requesting a new crop" do
let!(:crop) { create :crop_request }
let!(:already_approved) { create :crop }
background { login_as wrangler }
before { login_as wrangler }
scenario "Approve a request" do
it "Approve a request" do
visit edit_crop_path(crop)
select "approved", from: "Approval status"
click_button "Save"
@@ -36,7 +36,7 @@ feature "Requesting a new crop" do
expect(page).to have_content "crop was successfully updated."
end
scenario "Rejecting a crop" do
it "Rejecting a crop" do
visit edit_crop_path(crop)
select "rejected", from: "Approval status"
select "not edible", from: "Reason for rejection"

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
feature "Crop - " do
describe "Crop - " do
let(:member) { create :member }
let!(:requested_crop) { create :crop, requester: member, approval_status: 'pending', name: 'puha for dinner' }
background do
before do
login_as member
visit requested_crops_path
end
scenario "creating a crop with multiple scientific and alternate name", :js do
it "creating a crop with multiple scientific and alternate name", :js do
expect(page).to have_content "puha for dinner"
end
end

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature "browse crops" do
describe "browse crops" do
let(:tomato) { create :tomato }
let(:maize) { create :maize }
let(:pending_crop) { create :crop_request }
let(:rejected_crop) { create :rejected_crop }
scenario "Show crop info" do
it "Show crop info" do
visit crop_path(tomato)
expect(page).to have_text 'tomato'
end
@@ -18,7 +18,7 @@ feature "browse crops" do
FactoryBot.create :harvest, crop: tomato, harvested_at: 60.minutes.ago, created_at: 10.minutes.ago
end
scenario "Shows most recently harvested harvest" do
it "Shows most recently harvested harvest" do
visit crop_path(tomato)
expect(page).to have_link(href: harvest_path(most_recent_harvest))
end
@@ -31,7 +31,7 @@ feature "browse crops" do
FactoryBot.create :planting, crop: tomato, planted_at: 60.minutes.ago, created_at: 10.minutes.ago
end
scenario "Shows most recently planted planting" do
it "Shows most recently planted planting" do
visit crop_path(tomato)
expect(page).to have_link(href: planting_path(most_recent_planting))
end

View File

@@ -1,10 +1,10 @@
require 'rails_helper'
feature "follows", :js do
describe "follows", :js do
context "when signed out" do
let(:member) { create :member }
scenario "follow buttons on member profile page" do
it "follow buttons on member profile page" do
visit member_path(member)
expect(page).not_to have_link "Follow"
expect(page).not_to have_link "Unfollow"
@@ -15,36 +15,36 @@ feature "follows", :js do
let(:member) { create :member }
let(:other_member) { create :member }
background do
before do
login_as(member)
end
scenario "your profile doesn't have a follow button" do
it "your profile doesn't have a follow button" do
visit member_path(member)
expect(page).not_to have_link "Follow"
expect(page).not_to have_link "Unfollow"
end
context "following another member" do
background { visit member_path(other_member) }
before { visit member_path(other_member) }
scenario "has a follow button" do
it "has a follow button" do
expect(page).to have_link "Follow", href: follows_path(followed: other_member.slug)
end
scenario "has correct message and unfollow button" do
it "has correct message and unfollow button" do
click_link 'Follow'
expect(page).to have_content "Followed #{other_member.login_name}"
expect(page).to have_link "Unfollow", href: follow_path(member.get_follow(other_member))
end
scenario "has a followed member listed in the following page" do
it "has a followed member listed in the following page" do
click_link 'Follow'
visit member_follows_path(member)
expect(page).to have_content other_member.login_name
end
scenario "has correct message and follow button after unfollow" do
it "has correct message and follow button after unfollow" do
click_link 'Follow'
click_link 'Unfollow'
expect(page).to have_content "Unfollowed #{other_member.login_name}"
@@ -52,19 +52,19 @@ feature "follows", :js do
expect(page).to have_link "Follow", href: follows_path(followed: other_member.slug)
end
scenario "has member in following list" do
it "has member in following list" do
click_link 'Follow'
visit member_follows_path(member)
expect(page).to have_content other_member.login_name
end
scenario "appears in in followed member's followers list" do
it "appears in in followed member's followers list" do
click_link 'Follow'
visit member_followers_path(other_member)
expect(page).to have_content member.login_name
end
scenario "removes members from following and followers lists after unfollow" do
it "removes members from following and followers lists after unfollow" do
click_link 'Follow'
click_link 'Unfollow'
visit member_follows_path(member)

View File

@@ -1,9 +1,9 @@
require 'rails_helper'
feature "footer", js: true do
describe "footer", js: true do
before { visit root_path }
scenario "footer is on home page" do
it "footer is on home page" do
expect(page).to have_css 'footer'
end

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
require 'custom_matchers'
feature "Gardens" do
describe "Gardens" do
context 'logged in' do
subject { page }
let(:member) { FactoryBot.create :member }
background { login_as member }
before { login_as member }
let(:garden) { member.gardens.first }
let(:other_member_garden) { FactoryBot.create :garden }
@@ -15,9 +15,9 @@ feature "Gardens" do
shared_examples "has buttons bar at top" do
it "has buttons bar at top" do
within '.layout-actions' do
is_expected.to have_link 'Add a garden'
is_expected.to have_link 'My gardens'
is_expected.to have_link "Everyone's gardens"
expect(subject).to have_link 'Add a garden'
expect(subject).to have_link 'My gardens'
expect(subject).to have_link "Everyone's gardens"
end
end
end
@@ -27,11 +27,11 @@ feature "Gardens" do
include_examples "has buttons bar at top"
it "has actions on garden" do
is_expected.to have_link 'Plant something here'
is_expected.to have_link 'Mark as inactive'
is_expected.to have_link 'Edit'
is_expected.to have_link 'Add photo'
is_expected.to have_link 'Delete'
expect(subject).to have_link 'Plant something here'
expect(subject).to have_link 'Mark as inactive'
expect(subject).to have_link 'Edit'
expect(subject).to have_link 'Add photo'
expect(subject).to have_link 'Delete'
end
end
@@ -72,36 +72,4 @@ feature "Gardens" do
end
end
end
# background do
# login_as member
# visit new_garden_path
# end
# it "has the required fields help text" do
# expect(page).to have_content "* denotes a required field"
# end
# it "displays required and optional fields properly" do
# expect(page).to have_selector ".form-group.required", text: "Name"
# expect(page).to have_optional 'textarea#garden_description'
# expect(page).to have_optional 'input#garden_location'
# expect(page).to have_optional 'input#garden_area'
# end
# scenario "Create new garden" do
# fill_in "Name", with: "New garden"
# click_button "Save"
# expect(page).to have_content "Garden was successfully created"
# expect(page).to have_content "New garden"
# end
# scenario "Refuse to create new garden with negative area" do
# visit new_garden_path
# fill_in "Name", with: "Negative Garden"
# fill_in "Area", with: -5
# click_button "Save"
# expect(page).not_to have_content "Garden was successfully created"
# expect(page).to have_content "Area must be greater than or equal to 0"
# end
end

View File

@@ -1,10 +1,10 @@
require 'rails_helper'
require 'custom_matchers'
feature "Gardens", :js do
describe "Gardens", :js do
let(:member) { FactoryBot.create :member }
background do
before do
login_as member
visit new_garden_path
end
@@ -20,14 +20,14 @@ feature "Gardens", :js do
expect(page).to have_optional 'input#garden_area'
end
scenario "Create new garden" do
it "Create new garden" do
fill_in "Name", with: "New garden"
click_button "Save"
expect(page).to have_content "Garden was successfully created"
expect(page).to have_content "New garden"
end
scenario "Refuse to create new garden with negative area" do
it "Refuse to create new garden with negative area" do
visit new_garden_path
fill_in "Name", with: "Negative Garden"
fill_in "Area", with: -5

View File

@@ -1,11 +1,11 @@
require 'rails_helper'
require 'custom_matchers'
feature "Gardens#index", :js do
describe "Gardens#index", :js do
context "Logged in as member" do
let(:member) { FactoryBot.create :member, login_name: 'shadow' }
background { login_as member }
before { login_as member }
context "with 10 gardens" do
before do

View File

@@ -1,17 +1,17 @@
require 'rails_helper'
feature "Planting a crop", js: true do
describe "Planting a crop", js: true do
# name is aaa to ensure it is ordered first
let!(:garden) { create :garden, name: 'aaa' }
let!(:planting) { create :planting, garden: garden, owner: garden.owner, planted_at: Date.parse("2013-3-10") }
let!(:tomato) { create :tomato }
let!(:finished_planting) { create :finished_planting, owner: garden.owner, garden: garden, crop: tomato }
background do
before do
login_as garden.owner
end
scenario "View gardens" do
it "View gardens" do
visit gardens_path
expect(page).to have_content "Everyone's gardens"
within '.layout-actions' do
@@ -24,7 +24,7 @@ feature "Planting a crop", js: true do
expect(page).to have_content "Everyone's gardens"
end
scenario "Marking a garden as inactive" do
it "Marking a garden as inactive" do
visit garden_path(garden)
click_link "Mark as inactive"
expect(page).to have_content "Garden was successfully updated"
@@ -33,14 +33,14 @@ feature "Planting a crop", js: true do
expect(page).not_to have_content "Mark as inactive"
end
scenario "List only active gardens" do
it "List only active gardens" do
visit garden_path(garden)
click_link "Mark as inactive"
visit gardens_path
expect(page).not_to have_link garden_path(garden)
end
scenario "Create new garden" do
it "Create new garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
@@ -48,7 +48,7 @@ feature "Planting a crop", js: true do
expect(page).to have_content "New garden"
end
scenario "Refuse to create new garden with negative area" do
it "Refuse to create new garden with negative area" do
visit new_garden_path
fill_in "Name", with: "Negative Garden"
fill_in "Area", with: -5
@@ -58,17 +58,17 @@ feature "Planting a crop", js: true do
end
context "Clicking edit from the index page" do
background do
before do
visit gardens_path
end
scenario "button on index to edit garden" do
it "button on index to edit garden" do
click_link href: edit_garden_path(garden)
expect(page).to have_content 'Edit garden'
end
end
scenario "Edit garden" do
it "Edit garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
@@ -81,7 +81,7 @@ feature "Planting a crop", js: true do
expect(page).to have_content "Different name"
end
scenario "Delete garden" do
it "Delete garden" do
visit new_garden_path
fill_in "Name", with: "New garden"
click_button "Save"
@@ -98,7 +98,7 @@ feature "Planting a crop", js: true do
it_behaves_like "append date"
end
scenario "List only active plantings on a garden" do
it "List only active plantings on a garden" do
visit gardens_path
expect(page).not_to have_content finished_planting.crop_name
end

View File

@@ -1,36 +1,36 @@
require 'rails_helper'
feature "browse harvests" do
describe "browse harvests" do
subject { page }
let!(:member) { create :member }
let!(:harvest) { create :harvest, owner: member }
background { login_as member }
before { login_as member }
feature 'blank optional fields' do
describe 'blank optional fields' do
let!(:harvest) { create :harvest, :no_description }
before { visit harvests_path }
scenario 'read more' do
is_expected.not_to have_link "Read more"
it 'read more' do
expect(subject).not_to have_link "Read more"
end
end
feature "filled in optional fields" do
describe "filled in optional fields" do
let!(:harvest) { create :harvest, :long_description }
before do
visit harvests_path
end
scenario 'read more' do
is_expected.to have_link "Read more"
it 'read more' do
expect(subject).to have_link "Read more"
end
it 'links to #show' do
is_expected.to have_link harvest.crop.name, href: harvest_path(harvest)
expect(subject).to have_link harvest.crop.name, href: harvest_path(harvest)
end
end
end

View File

@@ -1,16 +1,15 @@
require 'rails_helper'
require 'custom_matchers'
feature "Harvesting a crop", :js, :elasticsearch do
describe "Harvesting a crop", :js, :elasticsearch do
let(:member) { create :member }
let!(:maize) { create :maize }
let!(:plant_part) { create :plant_part }
let(:planting) { create :planting, crop: maize, owner: member }
background do
before do
login_as member
visit new_harvest_path
sync_elasticsearch [maize]
end
it_behaves_like "crop suggest", "harvest", "crop"
@@ -19,14 +18,14 @@ feature "Harvesting a crop", :js, :elasticsearch do
expect(page).to have_content "* denotes a required field"
end
it "displays required and optional fields properly" do
expect(page).to have_selector ".form-group.required", text: "What did you harvest?"
expect(page).to have_optional 'input#harvest_quantity'
expect(page).to have_optional 'input#harvest_weight_quantity'
expect(page).to have_optional 'textarea#harvest_description'
describe "displays required and optional fields properly" do
it { expect(page).to have_selector ".form-group.required", text: "What did you harvest?" }
it { expect(page).to have_optional 'input#harvest_quantity' }
it { expect(page).to have_optional 'input#harvest_weight_quantity' }
it { expect(page).to have_optional 'textarea#harvest_description' }
end
scenario "Creating a new harvest", :js do
it "Creating a new harvest", :js do
fill_autocomplete "crop", with: "mai"
select_from_autocomplete "maize"
@@ -45,63 +44,67 @@ feature "Harvesting a crop", :js, :elasticsearch do
context "Clicking edit from the index page" do
let!(:harvest) { create :harvest, crop: maize, owner: member }
background do
before do
visit harvests_path
end
scenario "button on index to edit harvest" do
it "button on index to edit harvest" do
click_link "edit_harvest_glyphicon"
expect(current_path).to eq edit_harvest_path(harvest)
expect(page).to have_content 'Editing harvest'
end
end
scenario "Clicking link to owner's profile" do
it "Clicking link to owner's profile" do
visit member_harvests_path(member)
click_link "View #{member}'s profile >>"
expect(current_path).to eq member_path member
end
scenario "Harvesting from crop page" do
visit crop_path(maize)
within '.crop-actions' do
click_link "Harvest #{maize.name}"
describe "Harvesting from crop page" do
before do
visit crop_path(maize)
within '.crop-actions' do
click_link "Harvest #{maize.name}"
end
within "form#new_harvest" do
select plant_part.name, from: 'harvest[plant_part_id]'
expect(page).to have_selector "input[value='maize']"
click_button "Save"
end
end
within "form#new_harvest" do
it { expect(page).to have_content "harvest was successfully created." }
it { expect(page).to have_content "maize" }
end
describe "Harvesting from planting page" do
let!(:planting) { create :planting, crop: maize, owner: member, garden: member.gardens.first }
before do
visit planting_path(planting)
within ".planting-actions" do
click_link "Harvest"
end
select plant_part.name, from: 'harvest[plant_part_id]'
expect(page).to have_selector "input[value='maize']"
click_button "Save"
end
expect(page).to have_content "harvest was successfully created."
expect(page).to have_content "maize"
end
scenario "Harvesting from planting page" do
planting = create :planting, crop: maize, owner: member, garden: member.gardens.first
visit planting_path(planting)
within ".planting-actions" do
click_link "Harvest"
end
select plant_part.name, from: 'harvest[plant_part_id]'
click_button "Save"
expect(page).to have_content "harvest was successfully created."
expect(page).to have_content planting.garden.name
expect(page).to have_content "maize"
it { expect(page).to have_content "harvest was successfully created." }
it { expect(page).to have_content planting.garden.name }
it { expect(page).to have_content "maize" }
end
context "Editing a harvest" do
let(:existing_harvest) { create :harvest, crop: maize, owner: member }
let!(:other_plant_part) { create :plant_part, name: 'chocolate' }
background do
before do
visit harvest_path(existing_harvest)
click_link "Edit"
end
scenario "Saving without edits" do
it "Saving without edits" do
# Check that the autosuggest helper properly fills inputs with
# existing resource's data
click_button "Save"
@@ -109,7 +112,7 @@ feature "Harvesting a crop", :js, :elasticsearch do
expect(page).to have_content "maize"
end
scenario "change plant part" do
it "change plant part" do
select other_plant_part.name, from: 'harvest[plant_part_id]'
click_button "Save"
expect(page).to have_content "harvest was successfully updated."
@@ -127,11 +130,11 @@ feature "Harvesting a crop", :js, :elasticsearch do
planted_at: Time.zone.yesterday
end
background do
before do
visit harvest_path(existing_harvest)
end
scenario "linking to a planting" do
it "linking to a planting" do
expect(page).to have_content existing_planting.to_s
choose("harvest_planting_id_#{existing_planting.id}")
click_button "save"

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "home page" do
describe "home page" do
subject { page }
let(:member) { FactoryBot.create :member }
@@ -16,7 +16,7 @@ feature "home page" do
let!(:finished_seed) { FactoryBot.create :tradable_seed, finished: true }
let!(:untradable_seed) { FactoryBot.create :untradable_seed }
background do
before do
# Add photos, so they can appear on home page
planting.photos << photo
seed.photos << photo
@@ -27,13 +27,13 @@ feature "home page" do
shared_examples 'shows seeds' do
it "show tradeable seed" do
is_expected.to have_link href: seed_path(tradable_seed)
expect(subject).to have_link href: seed_path(tradable_seed)
end
it "does not show finished seeds" do
is_expected.not_to have_link href: seed_path(finished_seed)
expect(subject).not_to have_link href: seed_path(finished_seed)
end
it "does not show untradable seeds" do
is_expected.not_to have_link href: seed_path(untradable_seed)
expect(subject).not_to have_link href: seed_path(untradable_seed)
end
it { is_expected.to have_text 'View all seeds' }
@@ -41,14 +41,14 @@ feature "home page" do
shared_examples 'show plantings' do
it 'shows plantings section' do
is_expected.to have_text 'Recently Planted'
is_expected.to have_link href: planting_path(planting)
expect(subject).to have_text 'Recently Planted'
expect(subject).to have_link href: planting_path(planting)
end
end
shared_examples 'show harvests' do
it 'shows harvests section' do
is_expected.to have_text 'Recently Harvested'
is_expected.to have_link href: harvest_path(harvest)
expect(subject).to have_text 'Recently Harvested'
expect(subject).to have_link href: harvest_path(harvest)
end
end
@@ -61,12 +61,12 @@ feature "home page" do
describe 'shows recently added crops' do
it { is_expected.to have_text 'Recently Added' }
it 'link to newest crops' do
is_expected.to have_link crop.name, href: crop_path(crop)
expect(subject).to have_link crop.name, href: crop_path(crop)
end
end
it 'includes a link to all crops' do
is_expected.to have_link 'View all crops'
expect(subject).to have_link 'View all crops'
end
end
@@ -79,7 +79,8 @@ feature "home page" do
end
context "when signed in" do
background { login_as member }
before { login_as member }
include_examples 'show crops'
include_examples 'show plantings'
include_examples 'show harvests'

View File

@@ -1,17 +1,17 @@
require 'rails_helper'
feature 'Likeable', js: true do
describe 'Likeable', js: true do
let(:member) { FactoryBot.create(:member) }
let(:another_member) { FactoryBot.create(:london_member) }
let(:post) { FactoryBot.create(:post) }
context 'logged in member' do
background do
before do
login_as member
visit post_path(post)
end
scenario 'can be liked' do
it 'can be liked' do
expect(page).to have_link 'Like'
click_link 'Like'
expect(page).to have_content '1 like'
@@ -23,7 +23,7 @@ feature 'Likeable', js: true do
expect(page).to have_content '0 likes'
end
scenario 'displays correct number of likes' do
it 'displays correct number of likes' do
expect(page).to have_link 'Like'
click_link 'Like'
expect(page).to have_content '1 like'

View File

@@ -1,9 +1,9 @@
require 'rails_helper'
feature "Changing locales", js: true do
describe "Changing locales", js: true do
after { I18n.locale = :en }
scenario "Locale can be set with a query param" do
it "Locale can be set with a query param" do
visit root_path
expect(page).to have_content("a community of food gardeners.")
visit root_path(locale: 'ja')

View File

@@ -1,10 +1,10 @@
require 'rails_helper'
feature "member profile", js: true do
describe "member profile", js: true do
context "signed out member" do
let(:member) { create :member }
scenario "basic details on member profile page" do
it "basic details on member profile page" do
visit member_path(member)
expect(page).to have_css("h1", text: member.login_name)
expect(page).to have_content member.bio
@@ -12,20 +12,20 @@ feature "member profile", js: true do
expect(page).to have_link "More about this garden...", href: garden_path(member.gardens.first)
end
scenario "no bio" do
it "no bio" do
member.bio = nil
member.save
visit member_path(member)
expect(page).to have_content "hasn't written a bio yet"
end
scenario "gravatar" do
it "gravatar" do
visit member_path(member)
expect(page).to have_css "img.avatar"
end
context "location" do
scenario "member has set location" do
it "member has set location" do
london_member = create :london_member
visit member_path(london_member)
expect(page).to have_css("h1>small", text: london_member.location)
@@ -33,7 +33,7 @@ feature "member profile", js: true do
expect(page).to have_content "See other members, plantings, seeds and more near #{london_member.location}"
end
scenario "member has not set location" do
it "member has not set location" do
visit member_path(member)
expect(page).not_to have_css("h1>small")
expect(page).not_to have_css("#membermap")
@@ -42,31 +42,31 @@ feature "member profile", js: true do
end
context "email privacy" do
scenario "public email address" do
it "public email address" do
public_member = create :public_member
visit member_path(public_member)
expect(page).to have_content public_member.email
end
scenario "private email address" do
it "private email address" do
visit member_path(member)
expect(page).not_to have_content member.email
end
end
context "email privacy" do
scenario "public email address" do
it "public email address" do
public_member = create :public_member
visit member_path(public_member)
expect(page).to have_content public_member.email
end
scenario "private email address" do
it "private email address" do
visit member_path(member)
expect(page).not_to have_content member.email
end
end
context "activity stats" do
scenario "with no activity" do
it "with no activity" do
visit member_path(member)
expect(page).to have_content "Activity"
expect(page).to have_content "0 plantings"
@@ -75,7 +75,7 @@ feature "member profile", js: true do
expect(page).to have_content "0 posts"
end
scenario "with some activity" do
it "with some activity" do
create_list :planting, 2, owner: member
create_list :harvest, 3, owner: member
create_list :seed, 4, owner: member
@@ -88,13 +88,13 @@ feature "member profile", js: true do
end
end
scenario "twitter link" do
it "twitter link" do
twitter_auth = create :authentication, member: member
visit member_path(member)
expect(page).to have_link twitter_auth.name, href: "http://twitter.com/#{twitter_auth.name}"
end
scenario "flickr link" do
it "flickr link" do
flickr_auth = create :flickr_authentication, member: member
visit member_path(member)
expect(page).to have_link flickr_auth.name, href: "http://flickr.com/photos/#{flickr_auth.uid}"
@@ -107,56 +107,56 @@ feature "member profile", js: true do
let(:admin_member) { create :admin_member }
let(:crop_wrangler) { create :crop_wrangling_member }
background do
before do
login_as(member)
end
scenario "admin user's page" do
it "admin user's page" do
visit member_path(admin_member)
expect(page).to have_text "Admin"
end
scenario "crop wrangler's page" do
it "crop wrangler's page" do
visit member_path(crop_wrangler)
expect(page).to have_text "Crop Wrangler"
end
scenario "ordinary user's page" do
it "ordinary user's page" do
visit member_path(other_member)
expect(page).not_to have_text "Crop Wrangler"
expect(page).not_to have_text "Admin"
end
context "your own profile page" do
background do
before do
visit member_path(member)
end
scenario "has a link to create new garden" do
it "has a link to create new garden" do
expect(page).to have_link "New Garden", href: new_garden_path
end
scenario "has a button to edit profile" do
it "has a button to edit profile" do
expect(page).to have_link "Edit profile", href: edit_member_registration_path
end
end
context "someone else's profile page" do
background do
before do
visit member_path(other_member)
end
scenario "has a private message button" do
it "has a private message button" do
expect(page).to have_link "Send message", href: new_notification_path(recipient_id: other_member.id)
end
end
context "home page" do
background do
before do
visit root_path
end
scenario "does not have a button to edit profile" do
it "does not have a button to edit profile" do
expect(page).not_to have_link "Edit profile", href: edit_member_registration_path
end
end

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "member deletion" do
describe "member deletion" do
context "with activity and followers" do
let(:member) { FactoryBot.create(:member) }
let(:other_member) { FactoryBot.create(:member) }
@@ -12,7 +12,7 @@ feature "member deletion" do
let!(:secondgarden) { FactoryBot.create(:garden, owner: member) }
let(:admin) { FactoryBot.create(:admin_member) }
background do
before do
login_as(member)
visit member_path(other_member)
click_link 'Follow'
@@ -30,13 +30,13 @@ feature "member deletion" do
FactoryBot.create(:member, login_name: "ex_member")
end
scenario "has option to delete on member profile page" do
it "has option to delete on member profile page" do
visit member_path(member)
click_link 'Edit profile'
expect(page).to have_link "Delete Account"
end
scenario "asks for password before deletion" do
it "asks for password before deletion" do
visit member_path(member)
click_link 'Edit profile'
click_link 'Delete Account'
@@ -44,7 +44,7 @@ feature "member deletion" do
expect(page).to have_content "Current password can't be blank"
end
scenario "password must be correct" do
it "password must be correct" do
visit member_path(member)
click_link 'Edit profile'
click_link 'Delete Account'
@@ -53,7 +53,7 @@ feature "member deletion" do
expect(page).to have_content "Current password is invalid"
end
scenario "deletes and removes bio" do
it "deletes and removes bio" do
visit member_path(member)
click_link 'Edit profile'
click_link 'Delete Account'
@@ -64,7 +64,7 @@ feature "member deletion" do
end
context "deletes and" do
background do
before do
logout
login_as(member)
visit member_path(member)
@@ -79,39 +79,39 @@ feature "member deletion" do
it { expect(Member.with_deleted.find(member.id)).to eq member }
end
scenario "removes plantings" do
it "removes plantings" do
visit planting_path(planting)
expect(page.status_code).to eq(404)
end
scenario "removes gardens" do
it "removes gardens" do
visit garden_path(secondgarden)
expect(page.status_code).to eq(404)
end
scenario "removes harvests and seeds" do
it "removes harvests and seeds" do
visit harvest_path(harvest)
expect(page.status_code).to eq(404)
end
scenario "removes seeds" do
it "removes seeds" do
visit seed_path(seed)
expect(page.status_code).to eq(404)
end
scenario "removes members from following" do
it "removes members from following" do
visit member_follows_path(other_member)
expect(page).not_to have_content member.login_name.to_s
visit member_followers_path(other_member)
expect(page).not_to have_content member.login_name.to_s
end
scenario "replaces posts with deletion note" do
it "replaces posts with deletion note" do
visit post_path(memberpost)
expect(page.status_code).to eq(404)
end
scenario "replaces comments on others' posts with deletion note, leaving post intact" do
it "replaces comments on others' posts with deletion note, leaving post intact" do
FactoryBot.create :comment, post: othermemberpost, author: member, body: 'i am deleting my account'
visit post_path(othermemberpost)
@@ -120,7 +120,7 @@ feature "member deletion" do
expect(page).to have_content "Member Deleted"
end
scenario "can't be interesting" do
it "can't be interesting" do
expect(Member.interesting).not_to include(member)
expect(Planting.interesting).not_to include(planting)
expect(Seed.interesting).not_to include(seed)
@@ -128,7 +128,7 @@ feature "member deletion" do
pending "doesn't show in nearby"
scenario "can no longer sign in" do
it "can no longer sign in" do
visit new_member_session_path
fill_in 'Login', with: member.login_name
fill_in 'Password', with: member.password
@@ -145,7 +145,7 @@ feature "member deletion" do
FactoryBot.create(:cropbot)
let!(:ex_wrangler) { FactoryBot.create(:crop_wrangling_member, login_name: "ex_wrangler") }
scenario "leaves crops behind" do
it "leaves crops behind" do
login_as(otherwrangler)
visit edit_crop_path(crop)
expect(page).to have_content member.login_name

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature "members list" do
describe "members list" do
context "list all members" do
let!(:member1) { create :member, login_name: "Archaeopteryx", confirmed_at: Time.zone.parse('2013-02-10') }
let!(:member2) { create :member, login_name: "Zephyrosaurus", confirmed_at: Time.zone.parse('2014-01-11') }
let!(:member3) { create :member, login_name: "Testingname", confirmed_at: Time.zone.parse('2014-05-09') }
scenario "default alphabetical sort" do
it "default alphabetical sort" do
visit members_path
expect(page).to have_css "#sort"
expect(page).to have_selector "form"
@@ -16,7 +16,7 @@ feature "members list" do
expect(all_links.last).to have_text member2.login_name
end
scenario "recently joined sort" do
it "recently joined sort" do
visit members_path
expect(page).to have_css "#sort"
expect(page).to have_selector "form"

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "Notifications", :js do
describe "Notifications", :js do
let(:sender) { create :member }
let(:recipient) { create :member, login_name: 'beyonce' }
@@ -13,12 +13,12 @@ feature "Notifications", :js do
post_id: nil
end
background do
before do
login_as recipient
visit notification_path(notification)
end
scenario "Replying to the notification" do
it "Replying to the notification" do
click_link "Reply"
expect(page).to have_content "Notification body"

View File

@@ -1,17 +1,17 @@
require 'rails_helper'
feature "new photo page" do
describe "new photo page" do
let(:photo) { FactoryBot.create :photo }
context "signed in member" do
let(:member) { FactoryBot.create :member }
background { login_as member }
before { login_as member }
context "viewing a planting" do
let(:planting) { FactoryBot.create :planting, owner: member }
scenario "add photo" do
it "add photo" do
visit planting_path(planting)
within '.planting-actions' do
click_link('Add photo')
@@ -23,7 +23,7 @@ feature "new photo page" do
context "viewing a harvest" do
let(:harvest) { FactoryBot.create :harvest, owner: member }
scenario "add photo" do
it "add photo" do
visit harvest_path(harvest)
within '.harvest-actions' do
click_link "Add photo"
@@ -35,7 +35,7 @@ feature "new photo page" do
context "viewing a garden" do
let(:garden) { FactoryBot.create :garden, owner: member }
scenario "add photo" do
it "add photo" do
visit garden_path(garden)
within '.garden-actions' do
click_link "Add photo"
@@ -47,7 +47,7 @@ feature "new photo page" do
describe "viewing a seed" do
let(:seed) { FactoryBot.create :seed, owner: member }
scenario "add photo" do
it "add photo" do
visit seed_path(seed)
first('.seed-actions').click_link('Add photo')
expect(page).to have_text seed.to_s

View File

@@ -1,10 +1,10 @@
require 'rails_helper'
feature "show photo page" do
describe "show photo page" do
context "signed in member" do
let(:member) { create :member }
background { login_as member }
before { login_as member }
context "linked to planting" do
let(:planting) { create :planting }

View File

@@ -1,13 +1,13 @@
require "rails_helper"
feature "User searches" do
describe "User searches" do
let(:member) { create :member, location: "Philippines" }
let!(:maize) { create :maize }
let(:garden) { create :garden, owner: member }
let!(:seed1) { create :seed, owner: member }
let!(:planting) { create :planting, garden: garden, owner: member, planted_at: Date.parse("2013-3-10") }
scenario "with a valid place" do
it "with a valid place" do
visit places_path
search_with "Philippines"
expect(page).to have_content "community near Philippines"
@@ -16,7 +16,7 @@ feature "User searches" do
expect(page).not_to have_content "No results found"
end
scenario "with a blank search string" do
it "with a blank search string" do
visit places_path
search_with ""
expect(page).to have_content "Please enter a valid location"

View File

@@ -1,7 +1,7 @@
require 'rails_helper'
require 'capybara/email/rspec'
feature "Planting reminder email", :js do
describe "Planting reminder email", :js do
let(:member) { create :member }
let(:mail) { Notifier.planting_reminder(member) }
@@ -11,16 +11,16 @@ feature "Planting reminder email", :js do
{ host: 'localhost', port: 8080 }
end
scenario "has a greeting" do
it "has a greeting" do
expect(mail).to have_content "Hello"
end
context "when member has no plantings" do
scenario "tells you to track your plantings" do
it "tells you to track your plantings" do
expect(mail).to have_content "planting your first crop"
end
scenario "doesn't list plantings" do
it "doesn't list plantings" do
expect(mail).not_to have_content "most recent plantings you've told us about"
end
end
@@ -31,7 +31,7 @@ feature "Planting reminder email", :js do
let!(:p1) { create :planting, garden: member.gardens.first, owner: member }
let!(:p2) { create :planting, garden: member.gardens.first, owner: member }
scenario "lists plantings" do
it "lists plantings" do
expect(mail).to have_content "most recent plantings you've told us about"
expect(mail).to have_link p1.to_s, href: planting_url(p1)
expect(mail).to have_link p2.to_s, href: planting_url(p2)
@@ -40,11 +40,11 @@ feature "Planting reminder email", :js do
end
context "when member has no harvests" do
scenario "tells you to tracking plantings" do
it "tells you to tracking plantings" do
expect(mail).to have_content "Get started now by tracking your first harvest"
end
scenario "doesn't list plantings" do
it "doesn't list plantings" do
expect(mail).not_to have_content "the last few things you harvested were"
end
end
@@ -55,7 +55,7 @@ feature "Planting reminder email", :js do
let!(:h1) { create :harvest, owner: member }
let!(:h2) { create :harvest, owner: member }
scenario "lists harvests" do
it "lists harvests" do
expect(mail).to have_content "the last few things you harvested were"
expect(mail).to have_link h1.to_s, href: harvest_url(h1)
expect(mail).to have_link h2.to_s, href: harvest_url(h2)

View File

@@ -1,7 +1,7 @@
require "rails_helper"
require 'custom_matchers'
feature "Planting a crop", :js, :elasticsearch do
describe "Planting a crop", :js, :elasticsearch do
let(:member) { FactoryBot.create :member }
let!(:maize) { FactoryBot.create :maize }
let(:garden) { FactoryBot.create :garden, owner: member }
@@ -9,10 +9,9 @@ feature "Planting a crop", :js, :elasticsearch do
FactoryBot.create :planting, garden: garden, owner: member, planted_at: Date.parse("2013-03-10")
end
background do
before do
login_as member
visit new_planting_path
sync_elasticsearch [maize]
end
it_behaves_like "crop suggest", "planting"
@@ -32,26 +31,30 @@ feature "Planting a crop", :js, :elasticsearch do
it { expect(page).to have_optional 'input#planting_finished_at' }
end
scenario "Creating a new planting" do
fill_autocomplete "crop", with: "mai"
select_from_autocomplete "maize"
within "form#new_planting" do
fill_in "When", with: "2014-06-15"
fill_in "How many?", with: 42
select "cutting", from: "Planted from:"
select "semi-shade", from: "Sun or shade?"
fill_in "Tell us more about it", with: "It's rad."
click_button "Save"
describe "Creating a new planting" do
before do
fill_autocomplete "crop", with: "mai"
select_from_autocomplete "maize"
within "form#new_planting" do
fill_in "When", with: "2014-06-15"
fill_in "How many?", with: 42
select "cutting", from: "Planted from:"
select "semi-shade", from: "Sun or shade?"
fill_in "Tell us more about it", with: "It's rad."
click_button "Save"
end
end
expect(page).to have_content "planting was successfully created"
expect(page).to have_content "Not enough data"
it { expect(page).to have_content "planting was successfully created" }
it { expect(page).to have_content "Not enough data" }
end
scenario "Clicking link to owner's profile" do
visit member_plantings_path(member)
click_link "View #{member}'s profile >>"
expect(current_path).to eq member_path(member)
describe "Clicking link to owner's profile" do
before do
visit member_plantings_path(member)
click_link "View #{member}'s profile >>"
end
it { expect(current_path).to eq member_path(member) }
end
describe "Progress bar status on planting creation" do
@@ -151,7 +154,7 @@ feature "Planting a crop", :js, :elasticsearch do
end
end
scenario "Planting from crop page" do
it "Planting from crop page" do
visit crop_path(maize)
within '.crop-actions' do
click_link "Plant maize"
@@ -165,7 +168,7 @@ feature "Planting a crop", :js, :elasticsearch do
expect(page).to have_content "maize"
end
scenario "Editing a planting to add details" do
it "Editing a planting to add details" do
visit planting_path(planting)
click_link "Edit"
fill_in "Tell us more about it", with: "Some extra notes"
@@ -173,7 +176,7 @@ feature "Planting a crop", :js, :elasticsearch do
expect(page).to have_content "planting was successfully updated"
end
scenario "Editing a planting to fill in the finished date" do
it "Editing a planting to fill in the finished date" do
visit planting_path(planting)
expect(page).to have_content "Not enough data"
click_link "Edit"
@@ -184,7 +187,7 @@ feature "Planting a crop", :js, :elasticsearch do
expect(page).not_to have_content "Not enough data"
end
scenario "Marking a planting as finished" do
it "Marking a planting as finished" do
fill_autocomplete "crop", with: "mai"
select_from_autocomplete "maize"
within "form#new_planting" do

View File

@@ -1,14 +1,14 @@
require 'rails_helper'
feature 'Post a post' do
describe 'Post a post' do
let(:member) { create :member }
background do
before do
login_as member
visit new_post_path
end
scenario "creating a post" do
it "creating a post" do
fill_in "post_subject", with: "Testing"
fill_in "post_body", with: "This is a sample test"
click_button "Post"
@@ -19,11 +19,11 @@ feature 'Post a post' do
context "editing a post" do
let(:existing_post) { create :post, author: member }
background do
before do
visit edit_post_path(existing_post)
end
scenario "saving edit" do
it "saving edit" do
fill_in "post_subject", with: "Testing Edit"
click_button "Post"
expect(page).to have_content "Post was successfully updated"

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature 'Comments RSS feed' do
scenario 'The index feed exists' do
describe 'Comments RSS feed' do
it 'The index feed exists' do
visit comments_path(format: 'rss')
expect(page.status_code).to equal 200
end
scenario 'The index title is what we expect' do
it 'The index title is what we expect' do
visit comments_path(format: 'rss')
expect(page).to have_content "Recent comments on all posts (#{ENV['GROWSTUFF_SITE_NAME']})"
end

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature 'Crops RSS feed' do
scenario 'The index feed exists' do
describe 'Crops RSS feed' do
it 'The index feed exists' do
visit crops_path(format: 'rss')
expect(page.status_code).to equal 200
end
scenario 'The index title is what we expect' do
it 'The index title is what we expect' do
visit crops_path(format: 'rss')
expect(page).to have_content "Recently added crops (#{ENV['GROWSTUFF_SITE_NAME']})"
end

View File

@@ -1,14 +1,14 @@
require 'rails_helper'
feature 'Members RSS feed' do
describe 'Members RSS feed' do
let(:member) { create :member }
scenario 'The show action exists' do
it 'The show action exists' do
visit member_path(member, format: 'rss')
expect(page.status_code).to equal 200
end
scenario 'The show action title is what we expect' do
it 'The show action title is what we expect' do
visit member_path(member, format: 'rss')
expect(page).to have_content "#{member.login_name}'s recent posts (#{ENV['GROWSTUFF_SITE_NAME']})"
end

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature 'Plantings RSS feed' do
scenario 'The index feed exists' do
describe 'Plantings RSS feed' do
it 'The index feed exists' do
visit plantings_path(format: 'rss')
expect(page.status_code).to equal 200
end
scenario 'The index title is what we expect' do
it 'The index title is what we expect' do
visit plantings_path(format: 'rss')
expect(page).to have_content "Recent plantings from "\
"#{@owner || 'all members'} (#{ENV['GROWSTUFF_SITE_NAME']})"

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature 'Posts RSS feed' do
scenario 'The index feed exists' do
describe 'Posts RSS feed' do
it 'The index feed exists' do
visit posts_path(format: 'rss')
expect(page.status_code).to equal 200
end
scenario 'The index title is what we expect' do
it 'The index title is what we expect' do
visit posts_path(format: 'rss')
expect(page).to have_content "Recent posts from "\
"#{@author || 'all members'} (#{ENV['GROWSTUFF_SITE_NAME']})"

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature 'Seeds RSS feed' do
scenario 'The index feed exists' do
describe 'Seeds RSS feed' do
it 'The index feed exists' do
visit seeds_path(format: 'rss')
expect(page.status_code).to equal 200
end
scenario 'The index title is what we expect' do
it 'The index title is what we expect' do
visit seeds_path(format: 'rss')
expect(page).to have_content "Recent seeds from "\
"#{@owner || 'all members'} (#{ENV['GROWSTUFF_SITE_NAME']})"

View File

@@ -1,16 +1,16 @@
require 'rails_helper'
feature "Scientific names", js: true do
describe "Scientific names", js: true do
let!(:zea_mays) { create :zea_mays }
let(:crop) { zea_mays.crop }
scenario "Display scientific names on crop page" do
it "Display scientific names on crop page" do
visit crop_path(zea_mays.crop)
expect(page.status_code).to equal 200
expect(page).to have_content zea_mays.name
end
scenario "Index page for scientific names" do
it "Index page for scientific names" do
visit scientific_names_path
expect(page.status_code).to equal 200
expect(page).to have_content zea_mays.name
@@ -20,11 +20,11 @@ feature "Scientific names", js: true do
let!(:crop_wranglers) { create_list :crop_wrangling_member, 3 }
let(:member) { crop_wranglers.first }
background do
before do
login_as(member)
end
scenario "Crop wranglers can edit scientific names" do
it "Crop wranglers can edit scientific names" do
visit crop_path(crop)
expect(page.status_code).to equal 200
expect(page).to have_content "CROP WRANGLER"
@@ -39,7 +39,7 @@ feature "Scientific names", js: true do
expect(page).to have_content 'crop was successfully updated'
end
scenario "Crop wranglers can delete scientific names" do
it "Crop wranglers can delete scientific names" do
visit crop_path(zea_mays.crop)
expect(page).to have_link "Delete",
href: scientific_name_path(zea_mays)
@@ -49,7 +49,7 @@ feature "Scientific names", js: true do
expect(page).to have_content 'Scientific name was successfully deleted.'
end
scenario "Crop wranglers can add scientific names" do
it "Crop wranglers can add scientific names" do
visit crop_path(crop)
expect(page).to have_link "Add",
href: new_scientific_name_path(crop_id: crop.id)
@@ -63,7 +63,7 @@ feature "Scientific names", js: true do
expect(page).to have_content 'crop was successfully created.'
end
scenario "The show-scientific-name page works" do
it "The show-scientific-name page works" do
visit scientific_name_path(zea_mays)
expect(page.status_code).to equal 200
expect(page).to have_link zea_mays.crop.name,
@@ -74,7 +74,7 @@ feature "Scientific names", js: true do
let(:pending_crop) { create :crop_request }
let(:pending_sci_name) { create :scientific_name, crop: pending_crop }
scenario "Displays crop pending message" do
it "Displays crop pending message" do
visit scientific_name_path(pending_sci_name)
expect(page).to have_content "This crop is currently pending approval"
end

View File

@@ -1,14 +1,13 @@
require 'rails_helper'
require 'custom_matchers'
feature "Seeds", :js, :elasticsearch do
describe "Seeds", :js, :elasticsearch do
let(:member) { create :member }
let!(:maize) { create :maize }
background do
before do
login_as member
visit new_seed_path
sync_elasticsearch [maize]
end
it_behaves_like "crop suggest", "seed", "crop"

View File

@@ -1,12 +1,12 @@
require 'rails_helper'
feature "seeds", js: true do
describe "seeds", js: true do
let(:member) { create :member }
context "signed in user" do
let(:crop) { create :crop }
background { login_as member }
before { login_as member }
describe "button on index to edit seed" do
let!(:seed) { create :seed, owner: member }
@@ -41,7 +41,7 @@ feature "seeds", js: true do
# actually adding seeds is in spec/features/seeds_new_spec.rb
scenario "edit seeds" do
it "edit seeds" do
seed = create :seed, owner: member
visit seed_path(seed)
click_link 'Edit'

View File

@@ -1,7 +1,7 @@
require 'rails_helper'
require 'custom_matchers'
feature "Seeds", :js do
describe "Seeds", :js do
subject do
login_as member
visit seed_path(seed)
@@ -31,16 +31,16 @@ feature "Seeds", :js do
let!(:photos) { FactoryBot.create_list :photo, 50 }
it "shows newest photo" do
is_expected.to have_xpath("//img[contains(@src,'#{photos.last.thumbnail_url}')]")
expect(subject).to have_xpath("//img[contains(@src,'#{photos.last.thumbnail_url}')]")
end
it "links to newest photo" do
is_expected.to have_xpath("//a[contains(@href,'#{photo_path(photos.last)}')]")
expect(subject).to have_xpath("//a[contains(@href,'#{photo_path(photos.last)}')]")
end
it "does not show oldest photo" do
is_expected.not_to have_xpath("//img[contains(@src,'#{photos.first.thumbnail_url}')]")
expect(subject).not_to have_xpath("//img[contains(@src,'#{photos.first.thumbnail_url}')]")
end
it "does not link to oldest photo" do
is_expected.not_to have_xpath("//a[contains(@href,'#{photo_path(photos.first)}')]")
expect(subject).not_to have_xpath("//a[contains(@href,'#{photo_path(photos.first)}')]")
end
end
end

View File

@@ -2,7 +2,7 @@ shared_examples "append date" do
let(:this_month) { Time.zone.today.strftime("%B") }
let(:this_year) { Time.zone.today.strftime("%Y") }
background { visit path }
before { visit path }
scenario "Selecting a date with datepicker" do
click_link link_text

View File

@@ -6,8 +6,6 @@ shared_examples "crop suggest" do |resource|
let!(:tomato) { create :tomato }
let!(:roma) { create :roma }
background { sync_elasticsearch [pea, pear, maize, tomato] }
scenario "placeholder text in crop auto suggest field" do
expect(page).to have_selector("input[placeholder='e.g. lettuce']")
end

View File

@@ -1,6 +1,6 @@
require 'rails_helper'
feature "signin", js: true do
describe "signin", js: true do
let(:member) { FactoryBot.create :member }
let(:recipient) { FactoryBot.create :member }
let(:wrangler) { FactoryBot.create :crop_wrangling_member }
@@ -12,34 +12,34 @@ feature "signin", js: true do
click_button 'Sign in'
end
scenario "via email address" do
it "via email address" do
visit crops_path # some random page
click_link 'Sign in'
login
expect(page).to have_content("Sign out")
end
scenario "redirect to previous page after signin" do
it "redirect to previous page after signin" do
visit crops_path # some random page
click_link 'Sign in'
login
expect(current_path).to eq crops_path
end
scenario "don't redirect to devise pages after signin" do
it "don't redirect to devise pages after signin" do
visit new_member_registration_path # devise signup page
click_link 'Sign in'
login
expect(current_path).to eq root_path
end
scenario "redirect to signin page for if not authenticated to view notification" do
it "redirect to signin page for if not authenticated to view notification" do
visit notification_path(notification)
expect(current_path).to eq new_member_session_path
end
shared_examples "redirects to what you were trying to do" do
scenario do
it do
visit "/#{model_name}/new"
expect(current_path).to eq new_member_session_path
login
@@ -55,14 +55,14 @@ feature "signin", js: true do
end
end
scenario "after signin, redirect to new notifications page" do
it "after signin, redirect to new notifications page" do
visit new_notification_path(recipient_id: recipient.id)
expect(current_path).to eq new_member_session_path
login
expect(current_path).to eq new_notification_path
end
scenario "after crop wrangler signs in and crops await wrangling, show alert" do
it "after crop wrangler signs in and crops await wrangling, show alert" do
create :crop_request
visit crops_path # some random page
click_link 'Sign in'
@@ -73,7 +73,7 @@ feature "signin", js: true do
end
context "with facebook" do
scenario "sign in" do
it "sign in" do
# Ordinarily done by database_cleaner
Member.where(login_name: 'tdawg').delete_all

View File

@@ -1,11 +1,11 @@
require 'rails_helper'
feature "signout" do
describe "signout" do
let(:member) { create :member }
let(:path) {}
scenario "redirect to previous page after signout" do
it "redirect to previous page after signout" do
visit crops_path # some random page
click_link 'Sign in'
fill_in 'Login', with: member.login_name
@@ -16,7 +16,7 @@ feature "signout" do
end
shared_examples "sign-in redirects" do |path|
scenario "after signout, redirect to signin page if page needs authentication" do
it "after signout, redirect to signin page if page needs authentication" do
visit path
expect(current_path).to eq new_member_session_path
expect(page).to have_http_status(200)
@@ -39,7 +39,7 @@ feature "signout" do
include_examples "sign-in redirects", "/seeds/new"
end
scenario 'photos' do
it 'photos' do
garden = FactoryBot.create :garden, owner: member
visit "/photos/new?id=#{garden.id}&type=garden"
expect(current_path).to eq new_member_session_path

View File

@@ -1,7 +1,7 @@
require 'rails_helper'
feature "signup", js: true do
scenario "sign up for new account from top menubar" do
describe "signup", js: true do
it "sign up for new account from top menubar" do
visit crops_path # something other than front page, which has multiple signup links
click_link 'Sign up'
fill_in 'Login name', with: 'person123'
@@ -13,7 +13,7 @@ feature "signup", js: true do
expect(current_path).to eq root_path
end
scenario "sign up for new account with existing username" do
it "sign up for new account with existing username" do
visit crops_path # something other than front page, which has multiple signup links
click_link 'Sign up'
fill_in 'Login name', with: 'person123'
@@ -32,7 +32,7 @@ feature "signup", js: true do
click_button 'Sign up'
end
scenario "sign up for new account without accepting TOS" do
it "sign up for new account without accepting TOS" do
visit root_path
first('.signup a').click # click the 'Sign up' button in the middle of the page
fill_in 'Login name', with: 'person123'
@@ -45,7 +45,7 @@ feature "signup", js: true do
end
context "with facebook" do
scenario "sign up" do
it "sign up" do
# Ordinarily done by database_cleaner
Member.where(login_name: 'tdawg').delete_all
Member.where(email: 'tdawg@hotmail.com').delete_all

View File

@@ -1,15 +1,15 @@
require 'rails_helper'
require 'capybara/email/rspec'
feature "unsubscribe" do
describe "unsubscribe" do
let(:member) { create :member }
let(:notification) { create :notification }
background do
before do
clear_emails
end
scenario "from planting reminder mailing list" do
it "from planting reminder mailing list" do
# verifying the initial subscription status of the member
expect(member.send_planting_reminder).to eq(true)
expect(member.send_notification_email).to eq(true)
@@ -26,7 +26,7 @@ feature "unsubscribe" do
expect(updated_member.send_notification_email).to eq(true)
end
scenario "from inbox notification mailing list" do
it "from inbox notification mailing list" do
# verifying the initial subscription status of the member
expect(member.send_planting_reminder).to eq(true)
expect(member.send_notification_email).to eq(true)
@@ -44,7 +44,7 @@ feature "unsubscribe" do
expect(updated_member.send_notification_email).to eq(false)
end
scenario "visit unsubscribe page with a non-encrypted parameter" do
it "visit unsubscribe page with a non-encrypted parameter" do
# verifying the initial subscription status of the member
expect(member.send_planting_reminder).to eq(true)
expect(member.send_notification_email).to eq(true)

View File

@@ -22,7 +22,7 @@ describe PhotosHelper do
before { planting.photos << planting_photo }
it "uses planting photos" do
is_expected.to eq planting_photo.thumbnail_url
expect(subject).to eq planting_photo.thumbnail_url
end
end
@@ -30,7 +30,7 @@ describe PhotosHelper do
before { harvest.photos << harvest_photo }
it "uses harvest photos" do
is_expected.to eq harvest_photo.thumbnail_url
expect(subject).to eq harvest_photo.thumbnail_url
end
end
@@ -38,7 +38,7 @@ describe PhotosHelper do
before { seed.photos << seed_photo }
it "uses seed photos" do
is_expected.to eq seed_photo.thumbnail_url
expect(subject).to eq seed_photo.thumbnail_url
end
end
end

View File

@@ -4,7 +4,6 @@ describe Crop do
let(:pp2) { FactoryBot.create(:plant_part) }
let(:pp1) { FactoryBot.create(:plant_part) }
let(:maize) { FactoryBot.create(:maize) }
context 'all fields present' do
let(:crop) { FactoryBot.create(:tomato) }
@@ -143,57 +142,66 @@ describe Crop do
end
context 'photos' do
before do
@crop = FactoryBot.create(:tomato)
shared_examples 'has default photo' do
it { expect(Crop.has_photos).to include(crop) }
end
let!(:crop) { FactoryBot.create :tomato }
let(:member) { FactoryBot.create :member }
context 'with a planting photo' do
before do
@planting = FactoryBot.create(:planting, crop: @crop)
@photo = FactoryBot.create(:photo, owner: @planting.owner)
@planting.photos << @photo
end
let!(:photo) { FactoryBot.create(:photo, owner: planting.owner) }
let!(:planting) { FactoryBot.create(:planting, crop: crop) }
it 'has a default photo' do
@crop.default_photo.should be_an_instance_of Photo
expect(@crop.default_photo.id).to eq @photo.id
end
before { planting.photos << photo }
it 'is found in has_photos scope' do
Crop.has_photos.should include(@crop)
end
it { expect(crop.default_photo).to eq photo }
include_examples 'has default photo'
end
context 'with a harvest photo' do
before do
@harvest = FactoryBot.create(:harvest, crop: @crop)
@photo = FactoryBot.create(:photo, owner: @harvest.owner)
@harvest.photos << @photo
end
let!(:harvest) { FactoryBot.create(:harvest, crop: crop) }
let!(:photo) { FactoryBot.create(:photo, owner: harvest.owner) }
it 'has a default photo' do
@crop.default_photo.should be_an_instance_of Photo
expect(@crop.default_photo.id).to eq @photo.id
end
before { harvest.photos << photo }
it { expect(crop.default_photo).to eq photo }
include_examples 'has default photo'
context 'and planting photo' do
before do
@planting = FactoryBot.create(:planting, crop: @crop)
@planting_photo = FactoryBot.create(:photo, owner: @planting.owner)
@planting.photos << @planting_photo
end
let(:planting) { FactoryBot.create(:planting, crop: crop) }
let!(:planting_photo) { FactoryBot.create(:photo, owner: planting.owner) }
before { planting.photos << planting_photo }
it 'prefers the planting photo' do
expect(@crop.default_photo.id).to eq @planting_photo.id
expect(crop.default_photo.id).to eq planting_photo.id
end
end
end
context 'with no plantings or harvests' do
it 'has no default photo' do
expect(@crop.default_photo).to eq nil
expect(crop.default_photo).to eq nil
end
end
describe 'finding all photos' do
let(:planting) { FactoryBot.create :planting, crop: crop }
let(:harvest) { FactoryBot.create :harvest, crop: crop }
let(:seed) { FactoryBot.create :seed, crop: crop }
before do
# Add photos to all
planting.photos << FactoryBot.create(:photo, owner: planting.owner)
harvest.photos << FactoryBot.create(:photo, owner: harvest.owner)
seed.photos << FactoryBot.create(:photo, owner: seed.owner)
end
it { expect(crop.photos.size).to eq 3 }
it { expect(crop.planting_photos.size).to eq 1 }
it { expect(crop.harvest_photos.size).to eq 1 }
it { expect(crop.seed_photos.size).to eq 1 }
end
end
context 'sunniness' do
@@ -356,35 +364,6 @@ describe Crop do
expect(@maize.plant_parts).to eq [@pp1]
end
context "search", :elasticsearch do
let(:mushroom) { FactoryBot.create(:crop, name: 'mushroom') }
before { sync_elasticsearch([mushroom]) }
it "finds exact matches" do
expect(Crop.search('mushroom')).to eq [mushroom]
end
it "finds approximate matches" do
expect(Crop.search('mush')).to eq [mushroom]
end
it "doesn't find non-matches" do
Crop.search('mush').should_not include @crop
end
it "searches case insensitively" do
Crop.search('mUsH').should include mushroom
end
it "doesn't find 'rejected' crop" do
@rejected_crop = FactoryBot.create(:rejected_crop, name: 'tomato')
sync_elasticsearch([@rejected_crop])
Crop.search('tomato').should_not include @rejected_crop
end
it "doesn't find 'pending' crop" do
@crop_request = FactoryBot.create(:crop_request, name: 'tomato')
sync_elasticsearch([@crop_request])
Crop.search('tomato').should_not include @crop_request
end
end
context "csv loading" do
before do
# don't use 'let' for this -- we need to actually create it,

View File

@@ -1,8 +1,9 @@
require 'rails_helper'
describe Garden do
let(:owner) { FactoryBot.create(:member, login_name: 'hatupatu') }
let(:garden) { FactoryBot.create(:garden, owner: owner, name: 'Springfield Community Garden') }
let(:owner) { FactoryBot.create(:member, login_name: 'hatupatu') }
let(:garden) { FactoryBot.create(:garden, owner: owner, name: 'Springfield Community Garden') }
let(:garden_type) { FactoryBot.create(:garden_type, name: "aquaponic") }
it "has a slug" do
garden.slug.should match(/hatupatu-springfield-community-garden/)

View File

@@ -0,0 +1,38 @@
require 'rails_helper'
describe GardenType do
let(:garden) { FactoryBot.create(:garden, 'Free Carrots') }
describe "should have a name" do
let(:garden_type) { FactoryBot.build(:garden_type, name: "organic") }
it { expect(garden_type).to be_valid }
end
describe "doesn't allow a nil name" do
let(:garden_type) { FactoryBot.build(:garden_type, name: nil) }
it { expect(garden_type).not_to be_valid }
end
describe "doesn't allow a blank name" do
let(:garden_type) { FactoryBot.build(:garden_type, name: "") }
it { expect(garden_type).not_to be_valid }
end
describe "doesn't allow a name with only spaces" do
let(:garden_type) { FactoryBot.build(:garden_type, name: " ") }
it { expect(garden_type).not_to be_valid }
end
describe "does not delete gardens when deleted" do
before { FactoryBot.create :garden, garden_type: garden_type }
let(:garden_type) { FactoryBot.create(:garden_type, name: "Massive Flower Pot") }
it { expect(garden_type.gardens.size).to eq(1) }
it { expect { garden_type.destroy }.not_to change(Garden, :count) }
end
end

View File

@@ -38,12 +38,12 @@ RSpec.describe 'Gardens', type: :request do
"related" => "#{resource_url}/photos" } }
end
scenario '#index' do
it '#index' do
get '/api/v1/gardens', params: {}, headers: headers
expect(subject['data']).to include(garden_encoded_as_json_api)
end
scenario '#show' do
it '#show' do
get "/api/v1/gardens/#{garden.id}", params: {}, headers: headers
expect(subject['data']).to include(garden_encoded_as_json_api)
end

Some files were not shown because too many files have changed in this diff Show More