cucumber_factory 2.0.2 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,21 +2,25 @@ module CucumberFactory
2
2
  module Factory
3
3
  class Error < StandardError; end
4
4
 
5
- ATTRIBUTES_PATTERN = '( with the .+?)?( (?:which|who|that) is .+?)?'
5
+ ATTRIBUTES_PATTERN = '( with the .+?)?( (?:which|who|that) is .+?)?' # ... with the year 1979 which is science fiction
6
6
  TEXT_ATTRIBUTES_PATTERN = ' (?:with|and) these attributes:'
7
+ UPDATE_ATTR_PATTERN = '(?: (?:has|belongs to)( the .+?)|(?: and| but|,)*( is .+?))' # ... belongs to the collection "Fantasy" and is trending
8
+ TEXT_UPDATE_ATTR_PATTERN = '(?: and|,)* has these attributes:'
7
9
 
8
- RECORD_PATTERN = 'there is an? (.+?)( \(.+?\))?'
9
- NAMED_RECORD_PATTERN = '(?:"([^\"]*)"|\'([^\']*)\') is an? (.+?)( \(.+?\))?'
10
+ RECORD_PATTERN = 'there is an? (.+?)( \(.+?\))?' # Given there is a movie (comedy)
11
+ NAMED_RECORD_PATTERN = '(?:"([^\"]*)"|\'([^\']*)\') is an? (.+?)( \(.+?\))?' # Given "LotR" is a movie
12
+ RECORD_UPDATE_PATTERN = 'the ([^"\',]+?) (above|".+?"|\'.+?\')' # Given the movie "LotR" ...
10
13
 
11
14
  NAMED_RECORDS_VARIABLE = :'@named_cucumber_factory_records'
12
15
 
13
16
  VALUE_INTEGER = /\d+/
14
17
  VALUE_DECIMAL = /[\d\.]+/
15
18
  VALUE_STRING = /"[^"]*"|'[^']*'/
19
+ VALUE_FILE = /file:#{VALUE_STRING}/
16
20
  VALUE_ARRAY = /\[[^\]]*\]/
17
21
  VALUE_LAST_RECORD = /\babove\b/
18
22
 
19
- VALUE_SCALAR = /#{VALUE_STRING}|#{VALUE_DECIMAL}|#{VALUE_INTEGER}/
23
+ VALUE_SCALAR = /#{VALUE_STRING}|#{VALUE_DECIMAL}|#{VALUE_INTEGER}|#{VALUE_FILE}/
20
24
 
21
25
  CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR = {
22
26
  :kind => :Before,
@@ -39,6 +43,12 @@ module CucumberFactory
39
43
  :block => lambda { |a1, a2, a3, a4| CucumberFactory::Factory.send(:parse_creation, self, a1, a2, a3, a4) }
40
44
  }
41
45
 
46
+ UPDATE_STEP_DESCRIPTOR = {
47
+ :kind => :And,
48
+ :pattern => /^#{RECORD_UPDATE_PATTERN}#{UPDATE_ATTR_PATTERN}+$/,
49
+ :block => lambda { |a1, a2, a3, a4| CucumberFactory::Factory.send(:parse_update, self, a1, a2, a3, a4) }
50
+ }
51
+
42
52
  NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = {
43
53
  :kind => :Given,
44
54
  :pattern => /^#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}?$/,
@@ -53,6 +63,13 @@ module CucumberFactory
53
63
  :priority => true
54
64
  }
55
65
 
66
+ UPDATE_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = {
67
+ :kind => :And,
68
+ :pattern => /^#{RECORD_UPDATE_PATTERN}#{UPDATE_ATTR_PATTERN}*#{TEXT_UPDATE_ATTR_PATTERN}$/,
69
+ :block => lambda { |a1, a2, a3, a4, a5| CucumberFactory::Factory.send(:parse_update, self, a1, a2, a3, a4, a5) },
70
+ :priority => true
71
+ }
72
+
56
73
  class << self
57
74
 
58
75
  def add_steps(main)
@@ -61,6 +78,8 @@ module CucumberFactory
61
78
  add_step(main, CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
62
79
  add_step(main, NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
63
80
  add_step(main, CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR)
81
+ add_step(main, UPDATE_STEP_DESCRIPTOR)
82
+ add_step(main, UPDATE_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
64
83
  end
65
84
 
66
85
  private
@@ -69,7 +88,8 @@ module CucumberFactory
69
88
  main.instance_eval {
70
89
  kind = descriptor[:kind]
71
90
  object = send(kind, *[descriptor[:pattern]].compact, &descriptor[:block])
72
- object.overridable(:priority => descriptor[:priority] ? 1 : 0) if kind != :Before
91
+ # cucumber_factory steps get a low priority due to their generic syntax
92
+ object.overridable(:priority => descriptor[:priority] ? -1 : -2) if kind != :Before
73
93
  object
74
94
  }
75
95
  end
@@ -100,15 +120,31 @@ module CucumberFactory
100
120
  end
101
121
 
102
122
  def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
103
- build_strategy = BuildStrategy.from_prose(raw_model, raw_variant)
123
+ build_strategy, transient_attributes = CucumberFactory::BuildStrategy.from_prose(raw_model, raw_variant)
104
124
  model_class = build_strategy.model_class
125
+ attributes = parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes, transient_attributes)
126
+ record = build_strategy.create_record(attributes)
127
+ remember_record_names(world, record, attributes)
128
+ record
129
+ end
130
+
131
+ def parse_update(world, raw_model, raw_name, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
132
+ model_class = CucumberFactory::BuildStrategy.parse_model_class(raw_model)
133
+ attributes = parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes)
134
+ record = resolve_associated_value(world, model_class, model_class, model_class, raw_name)
135
+ CucumberFactory::UpdateStrategy.new(record).assign_attributes(attributes)
136
+ remember_record_names(world, record, attributes)
137
+ record
138
+ end
139
+
140
+ def parse_attributes(world, model_class, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil, transient_attributes = [])
105
141
  attributes = {}
106
142
  if raw_attributes.try(:strip).present?
107
143
  raw_attribute_fragment_regex = /(?:the |and |with |but |,| )+(.*?) (#{VALUE_SCALAR}|#{VALUE_ARRAY}|#{VALUE_LAST_RECORD})/
108
144
  raw_attributes.scan(raw_attribute_fragment_regex).each do |fragment|
109
145
  attribute = attribute_name_from_prose(fragment[0])
110
146
  value = fragment[1]
111
- attributes[attribute] = attribute_value(world, model_class, attribute, value)
147
+ attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
112
148
  end
113
149
  unused_raw_attributes = raw_attributes.gsub(raw_attribute_fragment_regex, '')
114
150
  if unused_raw_attributes.present?
@@ -126,49 +162,74 @@ module CucumberFactory
126
162
  # DocString e.g. "first name: Jane\nlast name: Jenny\n"
127
163
  if raw_multiline_attributes.is_a?(String)
128
164
  raw_multiline_attributes.split("\n").each do |fragment|
129
- raw_attribute, value = fragment.split(': ')
165
+ raw_attribute, value = fragment.split(': ', 2)
130
166
  attribute = attribute_name_from_prose(raw_attribute)
131
- value = "\"#{value}\"" unless matches_fully?(value, VALUE_ARRAY)
132
- attributes[attribute] = attribute_value(world, model_class, attribute, value)
167
+ value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/)
168
+ attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
133
169
  end
134
170
  # DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]]
135
171
  else
136
172
  raw_multiline_attributes.raw.each do |raw_attribute, value|
137
173
  attribute = attribute_name_from_prose(raw_attribute)
138
- value = "\"#{value}\"" unless matches_fully?(value, VALUE_ARRAY)
139
- attributes[attribute] = attribute_value(world, model_class, attribute, value)
174
+ value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/)
175
+ attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
140
176
  end
141
177
  end
142
178
  end
143
- record = build_strategy.create_record(attributes)
144
- remember_record_names(world, record, attributes)
145
- record
179
+ attributes
146
180
  end
147
181
 
148
- def attribute_value(world, model_class, attribute, value)
149
- association = model_class.respond_to?(:reflect_on_association) ? model_class.reflect_on_association(attribute) : nil
150
-
151
- if matches_fully?(value, VALUE_ARRAY)
152
- elements_str = unquote(value)
153
- value = elements_str.scan(VALUE_SCALAR).map { |v| attribute_value(world, model_class, attribute, v) }
154
- elsif association.present?
155
- if matches_fully?(value, VALUE_LAST_RECORD)
156
- value = CucumberFactory::Switcher.find_last(association.klass) or raise Error, "There is no last #{attribute}"
157
- elsif matches_fully?(value, VALUE_STRING)
158
- value = unquote(value)
159
- value = get_named_record(world, value) || transform_value(world, value)
160
- elsif matches_fully?(value, VALUE_INTEGER)
161
- value = value.to_s
162
- value = get_named_record(world, value) || transform_value(world, value)
163
- else
164
- raise Error, "Cannot set association #{model_class}##{attribute} to #{value}."
165
- end
182
+ def attribute_value(world, model_class, transient_attributes, attribute, value)
183
+ associated, association_class = resolve_association(attribute, model_class, transient_attributes)
184
+
185
+ value = if matches_fully?(value, VALUE_ARRAY)
186
+ array_values = unquote(value).scan(VALUE_SCALAR)
187
+ array_values.map { |v| attribute_value(world, model_class, transient_attributes, attribute, v) }
188
+ elsif associated
189
+ resolve_associated_value(world, model_class, association_class, attribute, value)
166
190
  else
167
- value = resolve_scalar_value(world, model_class, attribute, value)
191
+ resolve_scalar_value(world, model_class, attribute, value)
168
192
  end
169
193
  value
170
194
  end
171
195
 
196
+ def resolve_association(attribute, model_class, transient_attributes)
197
+ return unless model_class.respond_to?(:reflect_on_association)
198
+
199
+ association = model_class.reflect_on_association(attribute)
200
+ association_class = nil
201
+
202
+ if association
203
+ association_class = association.klass unless association.polymorphic?
204
+ associated = true
205
+ elsif transient_attributes.include?(attribute.to_sym)
206
+ klass_name = attribute.to_s.camelize
207
+ if Object.const_defined?(klass_name)
208
+ association_class = klass_name.constantize
209
+ associated = true
210
+ end
211
+ else
212
+ associated = false
213
+ end
214
+ [associated, association_class]
215
+ end
216
+
217
+ def resolve_associated_value(world, model_class, association_class, attribute, value)
218
+ if matches_fully?(value, VALUE_LAST_RECORD)
219
+ raise(Error, "Cannot set last #{model_class}##{attribute} for polymorphic associations") unless association_class.present?
220
+
221
+ CucumberFactory::Switcher.find_last(association_class) || raise(Error, "There is no last #{attribute}")
222
+ elsif matches_fully?(value, VALUE_STRING)
223
+ value = unquote(value)
224
+ get_named_record(world, value) || transform_value(world, value)
225
+ elsif matches_fully?(value, VALUE_INTEGER)
226
+ value = value.to_s
227
+ get_named_record(world, value) || transform_value(world, value)
228
+ else
229
+ raise Error, "Cannot set association #{model_class}##{attribute} to #{value}."
230
+ end
231
+ end
232
+
172
233
  def resolve_scalar_value(world, model_class, attribute, value)
173
234
  if matches_fully?(value, VALUE_STRING)
174
235
  value = unquote(value)
@@ -177,6 +238,9 @@ module CucumberFactory
177
238
  value = value.to_i
178
239
  elsif matches_fully?(value, VALUE_DECIMAL)
179
240
  value = BigDecimal(value)
241
+ elsif matches_fully?(value, VALUE_FILE)
242
+ path = File.path("./#{file_value_to_path(value)}")
243
+ value = File.new(path)
180
244
  else
181
245
  raise Error, "Cannot set attribute #{model_class}##{attribute} to #{value}."
182
246
  end
@@ -184,11 +248,20 @@ module CucumberFactory
184
248
  end
185
249
 
186
250
  def unquote(string)
251
+ # This method removes quotes or brackets from the start and end from a string
252
+ # Examples: 'single' => single, "double" => double, [1, 2, 3] => 1, 2, 3
187
253
  string[1, string.length - 2]
188
254
  end
189
255
 
256
+ def file_value_to_path(string)
257
+ # file paths are marked with a special keyword and enclosed with quotes.
258
+ # Example: file:"/path/image.png"
259
+ # This will extract the path (/path/image.png) from the text fragment above
260
+ unquote string.sub(/\Afile:/, '')
261
+ end
262
+
190
263
  def full_regexp(partial_regexp)
191
- Regexp.new("\\A" + partial_regexp.source + "\\z", partial_regexp.options)
264
+ Regexp.new('\\A(?:' + partial_regexp.source + ')\\z', partial_regexp.options)
192
265
  end
193
266
 
194
267
  def matches_fully?(string, partial_regexp)
@@ -208,7 +281,7 @@ module CucumberFactory
208
281
  end
209
282
 
210
283
  def attribute_name_from_prose(prose)
211
- prose.downcase.gsub(" ", "_").to_sym
284
+ prose.downcase.gsub(' ', '_').to_sym
212
285
  end
213
286
 
214
287
  def remember_record_names(world, record, attributes)
@@ -0,0 +1,30 @@
1
+ module CucumberFactory
2
+
3
+ class UpdateStrategy
4
+
5
+ def initialize(record)
6
+ @record = record
7
+ end
8
+
9
+ def assign_attributes(attributes)
10
+ active_record_strategy(attributes) ||
11
+ ruby_object_strategy(attributes)
12
+ end
13
+
14
+ private
15
+
16
+ def active_record_strategy(attributes)
17
+ return unless @record.respond_to?(:save!)
18
+
19
+ CucumberFactory::Switcher.assign_attributes(@record, attributes)
20
+ @record.save!
21
+ end
22
+
23
+ def ruby_object_strategy(attributes)
24
+ attributes.each do |name, value|
25
+ @record.send("#{name}=".to_sym, value)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module CucumberFactory
2
- VERSION = '2.0.2'
2
+ VERSION = '2.3.1'
3
3
  end
@@ -0,0 +1 @@
1
+ This is a test file.
@@ -0,0 +1 @@
1
+ This is the second test file.
@@ -0,0 +1 @@
1
+ spec/assets/file.txt
@@ -2,47 +2,45 @@ require 'spec_helper'
2
2
 
3
3
  describe CucumberFactory::BuildStrategy do
4
4
 
5
- subject { CucumberFactory::BuildStrategy }
6
-
7
5
  # most of the behaviour is integration tested in steps_spec.rb
8
6
 
9
7
  describe '.from_prose' do
10
8
 
11
9
  context 'when describing a factory_bot factory' do
12
-
13
- it 'returns a strategy corresponding to the factories model' do
14
- FactoryBot.stub_factories :job_offer => JobOffer
15
- strategy = subject.from_prose('job offer', nil)
10
+ it 'returns a strategy and transient attributes corresponding to the factories model' do
11
+ strategy, transient_attributes = described_class.from_prose('job offer', nil)
16
12
 
17
13
  strategy.should be_a(described_class)
18
14
  strategy.model_class.should == JobOffer
15
+ transient_attributes.should == [:my_transient_attribute]
19
16
  end
20
17
 
21
18
  it 'uses the variant for the factory name if present' do
22
- FactoryBot.stub_factories :job_offer => JobOffer
23
- strategy = subject.from_prose('foo', '(job offer)')
19
+ strategy, transient_attributes = described_class.from_prose('job offer', '(tempting_job_offer)')
24
20
 
25
21
  strategy.should be_a(described_class)
26
22
  strategy.model_class.should == JobOffer
23
+ transient_attributes.should == [:my_transient_attribute, :other_transient_attribute]
27
24
  end
28
-
29
25
  end
30
26
 
31
27
  context 'when describing a non factory_bot model' do
28
+ before do
29
+ hide_const("FactoryBot")
30
+ end
32
31
 
33
32
  it "should return a strategy for the class matching a natural language expression" do
34
- subject.from_prose("movie", nil).model_class.should == Movie
35
- subject.from_prose("job offer", nil).model_class.should == JobOffer
33
+ described_class.from_prose("movie", nil).first.model_class.should == Movie
34
+ described_class.from_prose("job offer", nil).first.model_class.should == JobOffer
36
35
  end
37
36
 
38
37
  it "should ignore variants for the class name" do
39
- subject.from_prose("movie", "(job offer)").model_class.should == Movie
38
+ described_class.from_prose("movie", "(job offer)").first.model_class.should == Movie
40
39
  end
41
40
 
42
41
  it "should allow namespaced models" do
43
- subject.from_prose("people/actor", nil).model_class.should == People::Actor
42
+ described_class.from_prose("people/actor", nil).first.model_class.should == People::Actor
44
43
  end
45
-
46
44
  end
47
45
 
48
46
  end
@@ -1,490 +1,659 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'steps provided by cucumber_factory' do
3
+ TRANSFORMS_SUPPORTED = Cucumber::VERSION < '3'
4
4
 
5
+ describe 'steps provided by cucumber_factory' do
5
6
  before(:each) do
6
7
  prepare_cucumber_example
7
8
  end
8
9
 
9
- TRANSFORMS_SUPPORTED = Cucumber::VERSION < '3'
10
+ context 'FactoryBot' do
11
+ it "should create ActiveRecord models by calling #new and #save!" do
12
+ movie = Movie.new
13
+ Movie.should_receive(:new).with(no_args).and_return(movie)
14
+ movie.should_receive(:save!)
15
+ invoke_cucumber_step("there is a movie")
16
+ end
10
17
 
11
- it "should create ActiveRecord models by calling #new and #save!" do
12
- movie = Movie.new
13
- Movie.should_receive(:new).with(no_args).and_return(movie)
14
- movie.should_receive(:save!)
15
- invoke_cucumber_step("there is a movie")
16
- end
18
+ it "should create models that have a factory_bot factory by calling #FactoryBot.create(:model_name)" do
19
+ FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesome job" })
20
+ invoke_cucumber_step('there is a job offer with the title "Awesome job"')
21
+ end
17
22
 
18
- it "should create models that have a machinist blueprint by calling #make" do
19
- MachinistModel.should_receive(:make).with({ :attribute => "foo"})
20
- invoke_cucumber_step('there is a machinist model with the attribute "foo"')
21
- end
23
+ it "should create model variants that have a factory_bot factory by calling #FactoryBot.create(:variant)" do
24
+ FactoryBot.should_receive(:create).with(:job_offer, :tempting_job_offer, { :title => "Awesomafiablyfantasmic job" })
25
+ invoke_cucumber_step('there is a job offer (tempting job offer) with the title "Awesomafiablyfantasmic job"')
26
+ end
22
27
 
23
- it "should be able to step_match machinist blueprint variants" do
24
- MachinistModel.should_receive(:make).with(:variant, { :attribute => "foo"})
25
- invoke_cucumber_step('there is a machinist model (variant) with the attribute "foo"')
26
- end
28
+ it "should create model variants that have a factory_bot trait by calling #FactoryBot.create(:factory, :trait1, :trait2)" do
29
+ FactoryBot.should_receive(:create).with(:job_offer, :risky, :lucrative, { :title => "Awesomafiablyfantasmic job" })
30
+ invoke_cucumber_step('there is a job offer (risky, lucrative) with the title "Awesomafiablyfantasmic job"')
31
+ end
27
32
 
28
- it "should be able to step_match machinist blueprint variants containing spaces or uppercase characters in prose" do
29
- MachinistModel.should_receive(:make).with(:variant_mark_two, { :attribute => "foo"})
30
- invoke_cucumber_step('there is a machinist model (Variant Mark Two) with the attribute "foo"')
31
- end
33
+ it "should create model variants that have a factory_bot factory by using the model name as a factory name" do
34
+ FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesomafiablyfantasmic job" })
35
+ invoke_cucumber_step('there is a job offer with the title "Awesomafiablyfantasmic job"')
36
+ end
32
37
 
33
- it "should create models that have a factory_bot factory by calling #FactoryBot.create(:model_name)" do
34
- FactoryBot.stub_factories :job_offer => JobOffer
35
- FactoryBot.should_receive(:create).with(:job_offer, { :title => "Awesome job" })
36
- invoke_cucumber_step('there is a job offer with the title "Awesome job"')
37
- end
38
+ it "should instantiate classes with multiple words in their name" do
39
+ job_offer = JobOffer.new
40
+ JobOffer.should_receive(:new).with(no_args).and_return(job_offer)
41
+ invoke_cucumber_step("there is a job offer")
42
+ end
38
43
 
39
- it "should create model variants that have a factory_bot factory by calling #FactoryBot.create(:variant)" do
40
- FactoryBot.stub_factories :tempting_job_offer => JobOffer
41
- FactoryBot.should_receive(:create).with(:tempting_job_offer, { :title => "Awesomafiablyfantasmic job" })
42
- invoke_cucumber_step('there is a job offer (tempting job offer) with the title "Awesomafiablyfantasmic job"')
43
- end
44
+ it "should instantiate classes with uppercase characters in their name" do
45
+ user = User.new
46
+ User.should_receive(:new).and_return(user)
47
+ invoke_cucumber_step("there is a User")
48
+ end
44
49
 
45
- it "should create model variants that have a factory_bot trait by calling #FactoryBot.create(:factory, :trait1, :trait2)" do
46
- FactoryBot.stub_factories :tempting_job_offer => JobOffer
47
- FactoryBot.should_receive(:create).with(:tempting_job_offer, :risky, :lucrative, { :title => "Awesomafiablyfantasmic job" })
48
- invoke_cucumber_step('there is a tempting job offer (risky, lucrative) with the title "Awesomafiablyfantasmic job"')
49
- end
50
+ it "should allow either 'a' or 'an' for the article" do
51
+ opera = Opera.new
52
+ Opera.should_receive(:new).with(no_args).and_return(opera)
53
+ invoke_cucumber_step("there is an opera")
54
+ end
50
55
 
51
- it "should create model variants that have a factory_bot factory by using the model name as a factory name" do
52
- FactoryBot.stub_factories :tempting_job_offer => JobOffer
53
- FactoryBot.should_receive(:create).with(:tempting_job_offer, { :title => "Awesomafiablyfantasmic job" })
54
- invoke_cucumber_step('there is a tempting job offer with the title "Awesomafiablyfantasmic job"')
55
- end
56
+ it "should create records with attributes" do
57
+ movie = Movie.new
58
+ Movie.stub(:new => movie)
59
+ invoke_cucumber_step('there is a movie with the title "Sunshine" and the year "2007"')
60
+ movie.title.should == "Sunshine"
61
+ movie.year.should == 2007
62
+ end
56
63
 
57
- it "should instantiate plain ruby classes by calling #new" do
58
- PlainRubyClass.should_receive(:new).with({})
59
- invoke_cucumber_step("there is a plain ruby class")
60
- end
64
+ it "should allow to join attribute lists with 'and's, commas and 'but's" do
65
+ movie = Movie.new
66
+ Movie.stub(:new => movie)
67
+ invoke_cucumber_step('there is a movie with the title "Sunshine", the year "2007" but with the box office result "32000000"')
68
+ movie.title.should == "Sunshine"
69
+ movie.year.should == 2007
70
+ movie.box_office_result.should == 32000000
71
+ end
61
72
 
62
- it "should instantiate classes with multiple words in their name" do
63
- JobOffer.should_receive(:new).with({})
64
- invoke_cucumber_step("there is a job offer")
65
- end
73
+ if TRANSFORMS_SUPPORTED
74
+ it "should apply Cucumber transforms to attribute values" do
75
+ movie = Movie.new
76
+ Movie.stub(:new => movie)
77
+ @main.instance_eval do
78
+ Transform /^(value)$/ do |value|
79
+ 'transformed value'
80
+ end
81
+ end
82
+ invoke_cucumber_step('there is a movie with the title "value"')
83
+ movie.title.should == "transformed value"
84
+ end
85
+ end
66
86
 
67
- it "should instantiate classes with uppercase characters in their name" do
68
- user = User.new
69
- User.should_receive(:new).and_return(user)
70
- invoke_cucumber_step("there is a User")
71
- end
87
+ it "should create records with attributes containing spaces" do
88
+ movie = Movie.new
89
+ Movie.stub(:new => movie)
90
+ invoke_cucumber_step('there is a movie with the box office result "99999999"')
91
+ movie.box_office_result.should == 99999999
92
+ end
72
93
 
73
- it "should instantiate namespaced classes" do
74
- actor = People::Actor.new
75
- People::Actor.should_receive(:new).and_return(actor)
76
- invoke_cucumber_step("there is a people/actor")
77
- end
94
+ it "should create records with attributes containing uppercase characters" do
95
+ user = User.new
96
+ User.stub(:new => user)
97
+ invoke_cucumber_step('there is a User with the Name "Susanne"')
98
+ user.name.should == "Susanne"
99
+ end
78
100
 
79
- it "should allow either 'a' or 'an' for the article" do
80
- Opera.should_receive(:new).with({})
81
- invoke_cucumber_step("there is an opera")
82
- end
101
+ it "should override attr_accessible protection" do
102
+ invoke_cucumber_step('there is a payment with the amount "120" and the comment "Thanks for lending"')
103
+ payment = Payment.last
104
+ payment.amount.should == 120
105
+ payment.comment.should == 'Thanks for lending'
106
+ end
83
107
 
84
- it "should create records with attributes" do
85
- movie = Movie.new
86
- Movie.stub(:new => movie)
87
- invoke_cucumber_step('there is a movie with the title "Sunshine" and the year "2007"')
88
- movie.title.should == "Sunshine"
89
- movie.year.should == 2007
90
- end
108
+ it "should allow to set an explicit primary key" do
109
+ invoke_cucumber_step('there is a payment with the ID 2')
110
+ payment = Payment.last
111
+ payment.id.should == 2
112
+ end
91
113
 
92
- it "should allow to join attribute lists with 'and's, commas and 'but's" do
93
- movie = Movie.new
94
- Movie.stub(:new => movie)
95
- invoke_cucumber_step('there is a movie with the title "Sunshine", the year "2007" but with the box office result "32000000"')
96
- movie.title.should == "Sunshine"
97
- movie.year.should == 2007
98
- movie.box_office_result.should == 32000000
99
- end
114
+ it "should allow to name records and set a belongs_to association to that record by referring to that name" do
115
+ invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"')
116
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
117
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
118
+ movie = Movie.find_by_title!('Before Sunset')
119
+ prequel = Movie.find_by_title!('Before Sunrise')
120
+ movie.prequel.should == prequel
121
+ end
100
122
 
101
- if TRANSFORMS_SUPPORTED
102
- it "should apply Cucumber transforms to attribute values" do
103
- movie = Movie.new
104
- Movie.stub(:new => movie)
105
- @main.instance_eval do
106
- Transform /^(value)$/ do |value|
107
- 'transformed value'
123
+ it "should allow to set a belongs_to association to a previously created record by referring to any string attribute of that record" do
124
+ invoke_cucumber_step('there is a movie with the title "Before Sunrise"')
125
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
126
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"')
127
+ movie = Movie.find_by_title!('Before Sunset')
128
+ prequel = Movie.find_by_title!('Before Sunrise')
129
+ movie.prequel.should == prequel
130
+ end
131
+
132
+ it "should allow to set a belongs_to association to a previously updated record by referring to any string attribute used when updating" do
133
+ invoke_cucumber_step('there is a movie')
134
+ invoke_cucumber_step('the movie above has the title "Before Sunrise"')
135
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
136
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"')
137
+ movie = Movie.find_by_title!('Before Sunset')
138
+ prequel = Movie.find_by_title!('Before Sunrise')
139
+ movie.prequel.should == prequel
140
+ end
141
+
142
+ it "should allow to set a belongs_to association to a previously created record by referring to their explicitely set primary keys" do
143
+ invoke_cucumber_step('there is a movie with the ID 123')
144
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel 123')
145
+ movie = Movie.find_by_title!('Before Sunset')
146
+ prequel = Movie.find(123)
147
+ movie.prequel.should == prequel
148
+ end
149
+
150
+ it "should allow to set a belongs_to association to a previously created record by saying 'above'" do
151
+ invoke_cucumber_step('there is a user with the name "Jane"')
152
+ invoke_cucumber_step('there is a user with the name "John"')
153
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
154
+ invoke_cucumber_step('there is a movie with the title "Before Sunrise"')
155
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above')
156
+ before_sunset = Movie.find_by_title!("Before Sunset")
157
+ before_sunset.prequel.title.should == "Before Sunrise"
158
+ before_sunset.reviewer.name.should == "John"
159
+ end
160
+
161
+ if TRANSFORMS_SUPPORTED
162
+ it "should fallback to using transforms when no named record is found" do
163
+ user = User.create!(:name => 'Me')
164
+ @main.instance_eval do
165
+ Transform(/^(me)$/) do |value|
166
+ user
167
+ end
108
168
  end
169
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "me"')
170
+ before_sunset = Movie.find_by_title!("Before Sunset")
171
+ before_sunset.reviewer.should == user
109
172
  end
110
- invoke_cucumber_step('there is a movie with the title "value"')
111
- movie.title.should == "transformed value"
112
173
  end
113
- end
114
174
 
115
- it "should create records with attributes containing spaces" do
116
- movie = Movie.new
117
- Movie.stub(:new => movie)
118
- invoke_cucumber_step('there is a movie with the box office result "99999999"')
119
- movie.box_office_result.should == 99999999
120
- end
175
+ it "should give created_at precedence over id when saying 'above' if the primary key is not numeric" do
176
+ invoke_cucumber_step('there is a uuid user with the name "Jane" and the id "jane"')
177
+ invoke_cucumber_step('there is a uuid user with the name "John" and the id "john"')
178
+ UuidUser.find_by_name("John").update_attributes!(:created_at => 1.day.ago)
179
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the uuid reviewer above')
180
+ before_sunset = Movie.find_by_title!("Before Sunset")
181
+ before_sunset.uuid_reviewer.name.should == "Jane"
182
+ end
121
183
 
122
- it "should create records with attributes containing uppercase characters" do
123
- user = User.new
124
- User.stub(:new => user)
125
- invoke_cucumber_step('there is a User with the Name "Susanne"')
126
- user.name.should == "Susanne"
127
- end
184
+ it "should ignore created_at if the primary key is numeric" do
185
+ invoke_cucumber_step('there is a user with the name "Jane"')
186
+ invoke_cucumber_step('there is a user with the name "John"')
187
+ User.find_by_name("John").update_attributes!(:created_at => 1.day.ago)
188
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above')
189
+ before_sunset = Movie.find_by_title!("Before Sunset")
190
+ before_sunset.reviewer.name.should == "John"
191
+ end
128
192
 
129
- it "should override attr_accessible protection" do
130
- invoke_cucumber_step('there is a payment with the amount "120" and the comment "Thanks for lending"')
131
- payment = Payment.last
132
- payment.amount.should == 120
133
- payment.comment.should == 'Thanks for lending'
134
- end
193
+ it "should raise a proper error if there is no previous record when saying 'above'" do
194
+ lambda do
195
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above')
196
+ end.should raise_error(/There is no last reviewer/i)
197
+ end
135
198
 
136
- it "should allow to set an explicit primary key" do
137
- invoke_cucumber_step('there is a payment with the ID 2')
138
- payment = Payment.last
139
- payment.id.should == 2
140
- end
199
+ it "should reload an object assigned to a belongs_to before assigning" do
200
+ invoke_cucumber_step('"Jane" is a user who is deleted')
201
+ User.last.update_attributes(:deleted => false)
202
+ proc { invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "Jane"') }.should_not raise_error
203
+ end
141
204
 
142
- it "should allow to name records and set a belongs_to association to that record by refering to that name" do
143
- invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"')
144
- invoke_cucumber_step('there is a movie with the title "Limitless"')
145
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
146
- movie = Movie.find_by_title!('Before Sunset')
147
- prequel = Movie.find_by_title!('Before Sunrise')
148
- movie.prequel.should == prequel
149
- end
205
+ it "should allow to set positive boolean attributes with 'who' after the attribute list" do
206
+ user = User.new
207
+ User.stub(:new => user)
208
+ invoke_cucumber_step('there is a user with the name "Jane" who is deleted')
209
+ user.name.should == "Jane"
210
+ user.deleted.should == true
211
+ end
150
212
 
151
- it "should allow to set a belongs_to association to a previously created record by refering to any string attribute of that record" do
152
- invoke_cucumber_step('there is a movie with the title "Before Sunrise"')
153
- invoke_cucumber_step('there is a movie with the title "Limitless"')
154
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Before Sunrise"')
155
- movie = Movie.find_by_title!('Before Sunset')
156
- prequel = Movie.find_by_title!('Before Sunrise')
157
- movie.prequel.should == prequel
158
- end
213
+ it "should allow to name records and set a belongs_to association to that record by referring to that name" do
214
+ invoke_cucumber_step('"Some Prequel" is a movie with the title "Before Sunrise"')
215
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
216
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
217
+ movie = Movie.find_by_title!('Before Sunset')
218
+ prequel = Movie.find_by_title!('Before Sunrise')
219
+ movie.prequel.should == prequel
220
+ end
159
221
 
160
- it "should allow to set a belongs_to association to a previously created record by refering to their explicitely set primary keys" do
161
- invoke_cucumber_step('there is a movie with the ID 123')
162
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel 123')
163
- movie = Movie.find_by_title!('Before Sunset')
164
- prequel = Movie.find(123)
165
- movie.prequel.should == prequel
166
- end
222
+ it "should allow to set positive boolean attributes with 'which' after the attribute list" do
223
+ user = User.new
224
+ User.stub(:new => user)
225
+ invoke_cucumber_step('there is a user with the name "Jane" which is deleted')
226
+ user.name.should == "Jane"
227
+ user.deleted.should == true
228
+ end
167
229
 
168
- it "should allow to set a belongs_to association to a previously created record by saying 'above'" do
169
- invoke_cucumber_step('there is a user with the name "Jane"')
170
- invoke_cucumber_step('there is a user with the name "John"')
171
- invoke_cucumber_step('there is a movie with the title "Limitless"')
172
- invoke_cucumber_step('there is a movie with the title "Before Sunrise"')
173
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above')
174
- before_sunset = Movie.find_by_title!("Before Sunset")
175
- before_sunset.prequel.title.should == "Before Sunrise"
176
- before_sunset.reviewer.name.should == "John"
177
- end
230
+ it "should allow to set positive boolean attributes with 'that' after the attribute list" do
231
+ user = User.new
232
+ User.stub(:new => user)
233
+ invoke_cucumber_step('there is a user with the name "Jane" that is deleted')
234
+ user.name.should == "Jane"
235
+ user.deleted.should == true
236
+ end
178
237
 
179
- if TRANSFORMS_SUPPORTED
180
- it "should fallback to using transforms when no named record is found" do
181
- user = User.create!(:name => 'Me')
182
- @main.instance_eval do
183
- Transform(/^(me)$/) do |value|
184
- user
185
- end
186
- end
187
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "me"')
188
- before_sunset = Movie.find_by_title!("Before Sunset")
189
- before_sunset.reviewer.should == user
238
+ it "should allow to set boolean attributes without regular attributes preceding them" do
239
+ user = User.new
240
+ User.stub(:new => user)
241
+ invoke_cucumber_step('there is a user who is deleted')
242
+ user.deleted.should == true
190
243
  end
191
- end
192
244
 
193
- it "should give created_at precedence over id when saying 'above' if the primary key is not numeric" do
194
- invoke_cucumber_step('there is a uuid user with the name "Jane" and the id "jane"')
195
- invoke_cucumber_step('there is a uuid user with the name "John" and the id "john"')
196
- UuidUser.find_by_name("John").update_attributes!(:created_at => 1.day.ago)
197
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the uuid reviewer above')
198
- before_sunset = Movie.find_by_title!("Before Sunset")
199
- before_sunset.uuid_reviewer.name.should == "Jane"
200
- end
245
+ it "should allow to set negative boolean attribute" do
246
+ user = User.new
247
+ User.stub(:new => user)
248
+ invoke_cucumber_step('there is a user who is not deleted')
249
+ user.deleted.should == false
250
+ end
201
251
 
202
- it "should ignore created_at if the primary key is numeric" do
203
- invoke_cucumber_step('there is a user with the name "Jane"')
204
- invoke_cucumber_step('there is a user with the name "John"')
205
- User.find_by_name("John").update_attributes!(:created_at => 1.day.ago)
206
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above')
207
- before_sunset = Movie.find_by_title!("Before Sunset")
208
- before_sunset.reviewer.name.should == "John"
209
- end
252
+ it "should allow to set multiple boolean attributes" do
253
+ user = User.new
254
+ User.stub(:new => user)
255
+ invoke_cucumber_step('there is a user who is locked and not deleted and subscribed')
256
+ user.locked.should == true
257
+ user.deleted.should == false
258
+ user.subscribed.should == true
259
+ end
210
260
 
211
- it "should raise a proper error if there is no previous record when saying 'above'" do
212
- lambda do
213
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer above and the prequel above')
214
- end.should raise_error(/There is no last reviewer/i)
215
- end
261
+ it "should allow to set boolean attributes that are named from multiple words" do
262
+ user = User.new
263
+ User.stub(:new => user)
264
+ invoke_cucumber_step('there is a user who is locked and not scared and scared by spiders and deleted')
265
+ user.locked.should == true
266
+ user.scared.should == false
267
+ user.scared_by_spiders.should == true
268
+ user.deleted.should == true
269
+ end
216
270
 
217
- it "should reload an object assigned to a belongs_to before assigning" do
218
- invoke_cucumber_step('"Jane" is a user who is deleted')
219
- User.last.update_attributes(:deleted => false)
220
- proc { invoke_cucumber_step('there is a movie with the title "Before Sunset" and the reviewer "Jane"') }.should_not raise_error
221
- end
271
+ it "should allow to join boolean attribute lists with 'and's, commas and 'but's" do
272
+ user = User.new
273
+ User.stub(:new => user)
274
+ invoke_cucumber_step('there is a user who is locked, scared, but scared by spiders and deleted')
275
+ user.locked.should == true
276
+ user.scared.should == true
277
+ user.scared_by_spiders.should == true
278
+ user.deleted.should == true
279
+ end
222
280
 
223
- it "should allow to set positive boolean attributes with 'who' after the attribute list" do
224
- user = User.new
225
- User.stub(:new => user)
226
- invoke_cucumber_step('there is a user with the name "Jane" who is deleted')
227
- user.name.should == "Jane"
228
- user.deleted.should == true
229
- end
281
+ it "should allow to set a has_many association by referring to multiple named records in square brackets" do
282
+ invoke_cucumber_step('there is a movie with the title "Sunshine"')
283
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
284
+ invoke_cucumber_step('there is a user with the reviewed movies ["Sunshine" and "Limitless"]')
285
+ user = User.last
286
+ reviewed_movie_titles = user.reviewed_movies.map(&:title)
287
+ reviewed_movie_titles.should =~ ['Sunshine', 'Limitless']
288
+ end
230
289
 
231
- it "should allow to set positive boolean attributes with 'which' after the attribute list" do
232
- user = User.new
233
- User.stub(:new => user)
234
- invoke_cucumber_step('there is a user with the name "Jane" which is deleted')
235
- user.name.should == "Jane"
236
- user.deleted.should == true
237
- end
290
+ it 'allow associations for transient attributes if they are named after the associated model' do
291
+ invoke_cucumber_step('there is a movie with the title "Sunshine"')
292
+ invoke_cucumber_step('there is a user with the movie "Sunshine"')
293
+ user = User.last
294
+ user.reviewed_movies.count.should == 1
295
+ user.reviewed_movies.first.title.should == 'Sunshine'
296
+ end
238
297
 
239
- it "should allow to set positive boolean attributes with 'that' after the attribute list" do
240
- user = User.new
241
- User.stub(:new => user)
242
- invoke_cucumber_step('there is a user with the name "Jane" that is deleted')
243
- user.name.should == "Jane"
244
- user.deleted.should == true
245
- end
298
+ it "should allow to set attributes via doc string" do
299
+ user = User.new
300
+ User.stub(:new => user)
246
301
 
247
- it "should allow to set boolean attributes without regular attributes preceding them" do
248
- user = User.new
249
- User.stub(:new => user)
250
- invoke_cucumber_step('there is a user who is deleted')
251
- user.deleted.should == true
252
- end
302
+ invoke_cucumber_step('there is a user with these attributes:', <<-DOC_STRING)
303
+ name: Jane
304
+ locked: true
305
+ DOC_STRING
253
306
 
254
- it "should allow to set negative boolean attribute" do
255
- user = User.new
256
- User.stub(:new => user)
257
- invoke_cucumber_step('there is a user who is not deleted')
258
- user.deleted.should == false
259
- end
307
+ user.name.should == "Jane"
308
+ user.locked.should == true
309
+ end
260
310
 
261
- it "should allow to set multiple boolean attributes" do
262
- user = User.new
263
- User.stub(:new => user)
264
- invoke_cucumber_step('there is a user who is locked and not deleted and subscribed')
265
- user.locked.should == true
266
- user.deleted.should == false
267
- user.subscribed.should == true
268
- end
311
+ it "should allow to set attributes via additional doc string" do
312
+ user = User.new
313
+ User.stub(:new => user)
269
314
 
270
- it "should allow to set boolean attributes that are named from multiple words" do
271
- user = User.new
272
- User.stub(:new => user)
273
- invoke_cucumber_step('there is a user who is locked and not scared and scared by spiders and deleted')
274
- user.locked.should == true
275
- user.scared.should == false
276
- user.scared_by_spiders.should == true
277
- user.deleted.should == true
278
- end
315
+ invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', <<-DOC_STRING)
316
+ name: Jane
317
+ DOC_STRING
279
318
 
280
- it "should allow to join boolean attribute lists with 'and's, commas and 'but's" do
281
- user = User.new
282
- User.stub(:new => user)
283
- invoke_cucumber_step('there is a user who is locked, scared, but scared by spiders and deleted')
284
- user.locked.should == true
285
- user.scared.should == true
286
- user.scared_by_spiders.should == true
287
- user.deleted.should == true
288
- end
319
+ user.name.should == "Jane"
320
+ user.email.should == "test@invalid.com"
321
+ end
289
322
 
290
- it "should allow to set integer attributes without surrounding quotes" do
291
- invoke_cucumber_step('there is a plain Ruby class with the amount 123 and the total 456')
292
- obj = PlainRubyClass.last
293
- obj.attributes[:amount].should == 123
294
- obj.attributes[:total].should == 456
295
- end
323
+ it 'should allow named records when setting attributes via doc string' do
324
+ invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', <<-DOC_STRING)
325
+ title: Before Sunrise
326
+ DOC_STRING
327
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
328
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
329
+ movie = Movie.find_by_title!('Before Sunset')
330
+ prequel = Movie.find_by_title!('Before Sunrise')
331
+ movie.prequel.should == prequel
332
+ end
296
333
 
297
- it "should allow to set decimal attributes without surrounding quotes" do
298
- invoke_cucumber_step('there is a plain Ruby class with the amount 1.23 and the total 45.6')
299
- obj = PlainRubyClass.last
300
- obj.attributes[:amount].should be_a(BigDecimal)
301
- obj.attributes[:amount].to_s.should == "1.23"
302
- obj.attributes[:total].should be_a(BigDecimal)
303
- obj.attributes[:total].to_s.should == "45.6"
304
- end
334
+ it "should allow to set attributes via data table" do
335
+ user = User.new
336
+ User.stub(:new => user)
305
337
 
306
- it "should allow set an array of strings with square brackets" do
307
- invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar"] and the list ["bam", "baz"]')
308
- obj = PlainRubyClass.last
309
- obj.attributes[:tags].should == ['foo', 'bar']
310
- obj.attributes[:list].should == ['bam', 'baz']
311
- end
338
+ invoke_cucumber_step('there is a user with these attributes:', nil, <<-DATA_TABLE)
339
+ | name | Jane |
340
+ | locked | true |
341
+ DATA_TABLE
312
342
 
313
- it "should allow set an array of numbers with square brackets" do
314
- invoke_cucumber_step('there is a plain Ruby class with the integers [1, 2] and the decimals [3.4, 4.5]')
315
- obj = PlainRubyClass.last
316
- obj.attributes[:integers].should == [1, 2]
317
- obj.attributes[:decimals].should == [BigDecimal('3.4'), BigDecimal('4.5')]
318
- end
343
+ user.name.should == "Jane"
344
+ user.locked.should == true
345
+ end
319
346
 
320
- it 'should allow to set an empty array' do
321
- invoke_cucumber_step('there is a plain Ruby class with the tags []')
322
- obj = PlainRubyClass.last
323
- obj.attributes[:tags].should == []
324
- end
347
+ it "should allow to set attributes via additional data table" do
348
+ user = User.new
349
+ User.stub(:new => user)
325
350
 
326
- it 'should allow to separate array values with either a comma or "and"' do
327
- invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar" and "baz"] and the list ["bam", "baz" and "qux"]')
328
- obj = PlainRubyClass.last
329
- obj.attributes[:tags].should == ['foo', 'bar', 'baz']
330
- obj.attributes[:list].should == ['bam', 'baz', 'qux']
331
- end
351
+ invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', nil, <<-DATA_TABLE)
352
+ | name | Jane |
353
+ DATA_TABLE
332
354
 
333
- it 'should allow to separate array values with an Oxford comma' do
334
- invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar", and "baz"] and the list ["bam", "baz", and "qux"]')
335
- obj = PlainRubyClass.last
336
- obj.attributes[:tags].should == ['foo', 'bar', 'baz']
337
- obj.attributes[:list].should == ['bam', 'baz', 'qux']
338
- end
355
+ user.name.should == "Jane"
356
+ user.email.should == "test@invalid.com"
357
+ end
339
358
 
340
- it "should allow to set a has_many association by refering to multiple named records in square brackets" do
341
- invoke_cucumber_step('there is a movie with the title "Sunshine"')
342
- invoke_cucumber_step('there is a movie with the title "Limitless"')
343
- invoke_cucumber_step('there is a user with the reviewed movies ["Sunshine" and "Limitless"]')
344
- user = User.last
345
- reviewed_movie_titles = user.reviewed_movies.map(&:title)
346
- reviewed_movie_titles.should =~ ['Sunshine', 'Limitless']
347
- end
359
+ it 'should allow named records when setting attributes via data table' do
360
+ invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', nil, <<-DATA_TABLE)
361
+ | title | Before Sunrise |
362
+ DATA_TABLE
363
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
364
+ invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
365
+ movie = Movie.find_by_title!('Before Sunset')
366
+ prequel = Movie.find_by_title!('Before Sunrise')
367
+ movie.prequel.should == prequel
368
+ end
348
369
 
349
- it "should allow attribute names starting with 'the'" do
350
- PlainRubyClass.should_receive(:new).with({:theme => 'Sci-fi'})
351
- invoke_cucumber_step('there is a plain ruby class with the theme "Sci-fi"')
352
- end
370
+ it "should allow mixed single quotes for model names" do
371
+ invoke_cucumber_step("'Some Prequel' is a movie with the title \"Before Sunrise\"")
372
+ invoke_cucumber_step('there is a movie with the title "Limitless"')
373
+ invoke_cucumber_step('there is a movie with the title \'Before Sunset\' and the prequel "Some Prequel"')
374
+ movie = Movie.find_by_title!('Before Sunset')
375
+ prequel = Movie.find_by_title!('Before Sunrise')
376
+ movie.prequel.should == prequel
377
+ end
353
378
 
354
- it "should allow attribute names starting with 'and'" do
355
- PlainRubyClass.should_receive(:new).with({:android => 'Paranoid'})
356
- invoke_cucumber_step('there is a plain ruby class with the android "Paranoid"')
357
- end
379
+ it 'supports named associations with polymorphic associations' do
380
+ invoke_cucumber_step('"my opera" is an opera')
381
+ invoke_cucumber_step('there is a movie with the premiere site "my opera"')
382
+ end
358
383
 
359
- it "should allow attribute names starting with 'with'" do
360
- PlainRubyClass.should_receive(:new).with({:withdrawal => 'bank_account'})
361
- invoke_cucumber_step('there is a plain ruby class with the withdrawal "bank_account"')
362
- end
384
+ it 'does not support last record references with polymorphic associations as the target class cannot be guessed' do
385
+ invoke_cucumber_step('there is an opera')
386
+ expect {
387
+ invoke_cucumber_step('there is a movie with the premiere site above')
388
+ }.to raise_error(CucumberFactory::Factory::Error, 'Cannot set last Movie#premiere_site for polymorphic associations')
389
+ end
363
390
 
364
- it "should allow attribute names starting with 'but'" do
365
- PlainRubyClass.should_receive(:new).with({:butt => 'pear-shaped'})
366
- invoke_cucumber_step('there is a plain ruby class with the butt "pear-shaped"')
367
- end
391
+ it 'supports carrierwave uploaders' do
392
+ invoke_cucumber_step('there is a payment with the attachment file:"spec/assets/file.txt"')
393
+ payment = Payment.last
394
+ expect(payment.attachment.file.read).to eq "This is a test file.\n"
395
+ end
368
396
 
369
- it "should allow to set attributes via doc string" do
370
- user = User.new
371
- User.stub(:new => user)
397
+ it 'supports single quote for carrierwave uploaders' do
398
+ invoke_cucumber_step("there is a payment with the attachment file:'spec/assets/file.txt'")
399
+ payment = Payment.last
400
+ expect(payment.attachment.file.read).to eq "This is a test file.\n"
401
+ end
372
402
 
373
- invoke_cucumber_step('there is a user with these attributes:', <<-DOC_STRING)
374
- name: Jane
375
- locked: true
376
- DOC_STRING
403
+ it 'is able to read symlinked files' do
404
+ invoke_cucumber_step("there is a payment with the attachment file:'spec/assets/symlink.txt'")
405
+ payment = Payment.last
406
+ expect(payment.attachment.file.read).to eq "This is a test file.\n"
407
+ end
377
408
 
378
- user.name.should == "Jane"
379
- user.locked.should == true
380
- end
409
+ it 'supports table syntax for carrierwave uploaders' do
410
+ invoke_cucumber_step('there is a payment with these attributes:', nil, <<-DATA_TABLE)
411
+ | attachment | file:"spec/assets/file.txt" |
412
+ DATA_TABLE
413
+ payment = Payment.last
414
+ expect(payment.attachment.file.read).to eq "This is a test file.\n"
415
+ end
381
416
 
382
- it "should allow to set attributes via additional doc string" do
383
- user = User.new
384
- User.stub(:new => user)
417
+ it 'supports doc string syntax for carrierwave uploaders' do
418
+ invoke_cucumber_step('there is a payment with these attributes:', <<-DOC_STRING)
419
+ attachment: file:"spec/assets/file.txt"
420
+ DOC_STRING
421
+ payment = Payment.last
422
+ payment.save!
423
+ expect(payment.attachment.file.read).to eq "This is a test file.\n"
424
+ end
385
425
 
386
- invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', <<-DOC_STRING)
387
- name: Jane
388
- DOC_STRING
426
+ it 'allows updating a carrierwave attribute' do
427
+ invoke_cucumber_step('there is a payment with the attachment file:"spec/assets/file.txt"')
428
+ payment = Payment.last
429
+ expect(payment.attachment.file.read).to eq "This is a test file.\n"
430
+ invoke_cucumber_step('the payment above has the attachment file:"spec/assets/file2.txt"')
431
+ payment.reload
432
+ expect(payment.attachment.file.read).to eq "This is the second test file.\n"
433
+ end
389
434
 
390
- user.name.should == "Jane"
391
- user.email.should == "test@invalid.com"
392
- end
435
+ it 'works with nested factories (BUGFIX)' do
436
+ invoke_cucumber_step('there is a subgenre movie')
437
+ end
393
438
 
394
- it "should allow to set array attributes via doc string" do
395
- invoke_cucumber_step('there is a plain Ruby class with these attributes:', <<-DOC_STRING)
396
- tags: ["foo", "bar"]
397
- DOC_STRING
439
+ describe 'references to named records are tracked correctly' do
440
+ # CucumberFactory keeps track of all created records by their attributes.
441
+ # For example, if you create a user with the name "Peter" you can reference him later on as "Peter".
398
442
 
399
- obj = PlainRubyClass.last
400
- obj.attributes[:tags].should == ['foo', 'bar']
401
- end
443
+ it 'does not overwrite existing references when associating new records with another reference (BUGFIX)' do
444
+ invoke_cucumber_step('"reviewer" is a user with the id "1"')
445
+ movie1 = invoke_cucumber_step('there is a movie with the reviewer "reviewer" and the id "2"') # <- should not overwrite the 'reviewer' reference
446
+ movie2 = invoke_cucumber_step('there is a movie with the reviewer "reviewer" and the id "3"')
402
447
 
403
- it 'should allow named records when setting attributes via doc string' do
404
- invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', <<-DOC_STRING)
405
- title: Before Sunrise
406
- DOC_STRING
407
- invoke_cucumber_step('there is a movie with the title "Limitless"')
408
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
409
- movie = Movie.find_by_title!('Before Sunset')
410
- prequel = Movie.find_by_title!('Before Sunrise')
411
- movie.prequel.should == prequel
448
+ expect(movie1.reviewer_id).to eq(1)
449
+ expect(movie2.reviewer_id).to eq(1)
450
+ end
451
+
452
+ it 'does not set new references when setting primary keys of an association (BUGFIX)' do
453
+ movie1 = invoke_cucumber_step('there is a movie with the id "123" and the user id "456"') # <- should not set a '456' reference
454
+ movie2 = invoke_cucumber_step('there is a movie with the user id "456"')
455
+
456
+ expect(movie1.reviewer_id).to eq(456)
457
+ expect(movie2.reviewer_id).to eq(456)
458
+ end
459
+ end
412
460
  end
413
461
 
414
- it "should allow to set attributes via data table" do
415
- user = User.new
416
- User.stub(:new => user)
462
+ context 'without FactoryBot' do
463
+ before do
464
+ hide_const("FactoryBot")
465
+ end
417
466
 
418
- invoke_cucumber_step('there is a user with these attributes:', nil, <<-DATA_TABLE)
419
- | name | Jane |
420
- | locked | true |
421
- DATA_TABLE
467
+ it "should instantiate plain ruby classes by calling #new" do
468
+ PlainRubyClass.should_receive(:new).with({})
469
+ invoke_cucumber_step("there is a plain ruby class")
470
+ end
422
471
 
423
- user.name.should == "Jane"
424
- user.locked.should == true
425
- end
472
+ it "should instantiate namespaced classes" do
473
+ actor = People::Actor.new
474
+ People::Actor.should_receive(:new).and_return(actor)
475
+ invoke_cucumber_step("there is a people/actor")
476
+ end
426
477
 
427
- it "should allow to set attributes via additional data table" do
428
- user = User.new
429
- User.stub(:new => user)
478
+ it "should allow to set integer attributes without surrounding quotes" do
479
+ invoke_cucumber_step('there is a plain Ruby class with the amount 123 and the total 456')
480
+ obj = PlainRubyClass.last
481
+ obj.attributes[:amount].should == 123
482
+ obj.attributes[:total].should == 456
483
+ end
430
484
 
431
- invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', nil, <<-DATA_TABLE)
432
- | name | Jane |
433
- DATA_TABLE
485
+ it "should allow to set decimal attributes without surrounding quotes" do
486
+ invoke_cucumber_step('there is a plain Ruby class with the amount 1.23 and the total 45.6')
487
+ obj = PlainRubyClass.last
488
+ obj.attributes[:amount].should be_a(BigDecimal)
489
+ obj.attributes[:amount].to_s.should == "1.23"
490
+ obj.attributes[:total].should be_a(BigDecimal)
491
+ obj.attributes[:total].to_s.should == "45.6"
492
+ end
434
493
 
435
- user.name.should == "Jane"
436
- user.email.should == "test@invalid.com"
437
- end
494
+ it "should allow to set file attributes with the file:'path' syntax" do
495
+ invoke_cucumber_step('there is a plain Ruby class with the file file:"spec/assets/file.txt"')
496
+ obj = PlainRubyClass.last
497
+ obj.attributes[:file].should be_a(File)
498
+ obj.attributes[:file].read.should == "This is a test file.\n"
499
+ end
438
500
 
439
- it "should allow to set array attributes via data table" do
440
- invoke_cucumber_step('there is a plain Ruby class with these attributes:', nil, <<-DATA_TABLE)
501
+ it "should allow set an array of strings with square brackets" do
502
+ invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar"] and the list ["bam", "baz"]')
503
+ obj = PlainRubyClass.last
504
+ obj.attributes[:tags].should == ['foo', 'bar']
505
+ obj.attributes[:list].should == ['bam', 'baz']
506
+ end
507
+
508
+ it "should allow set an array of numbers with square brackets" do
509
+ invoke_cucumber_step('there is a plain Ruby class with the integers [1, 2] and the decimals [3.4, 4.5]')
510
+ obj = PlainRubyClass.last
511
+ obj.attributes[:integers].should == [1, 2]
512
+ obj.attributes[:decimals].should == [BigDecimal('3.4'), BigDecimal('4.5')]
513
+ end
514
+
515
+ it 'should allow to set an empty array' do
516
+ invoke_cucumber_step('there is a plain Ruby class with the tags []')
517
+ obj = PlainRubyClass.last
518
+ obj.attributes[:tags].should == []
519
+ end
520
+
521
+ it 'should allow to separate array values with either a comma or "and"' do
522
+ invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar" and "baz"] and the list ["bam", "baz" and "qux"]')
523
+ obj = PlainRubyClass.last
524
+ obj.attributes[:tags].should == ['foo', 'bar', 'baz']
525
+ obj.attributes[:list].should == ['bam', 'baz', 'qux']
526
+ end
527
+
528
+ it 'should allow to separate array values with an Oxford comma' do
529
+ invoke_cucumber_step('there is a plain Ruby class with the tags ["foo", "bar", and "baz"] and the list ["bam", "baz", and "qux"]')
530
+ obj = PlainRubyClass.last
531
+ obj.attributes[:tags].should == ['foo', 'bar', 'baz']
532
+ obj.attributes[:list].should == ['bam', 'baz', 'qux']
533
+ end
534
+
535
+ it "should allow attribute names starting with 'the'" do
536
+ PlainRubyClass.should_receive(:new).with({:theme => 'Sci-fi'})
537
+ invoke_cucumber_step('there is a plain ruby class with the theme "Sci-fi"')
538
+ end
539
+
540
+ it "should allow attribute names starting with 'and'" do
541
+ PlainRubyClass.should_receive(:new).with({:android => 'Paranoid'})
542
+ invoke_cucumber_step('there is a plain ruby class with the android "Paranoid"')
543
+ end
544
+
545
+ it "should allow attribute names starting with 'with'" do
546
+ PlainRubyClass.should_receive(:new).with({:withdrawal => 'bank_account'})
547
+ invoke_cucumber_step('there is a plain ruby class with the withdrawal "bank_account"')
548
+ end
549
+
550
+ it "should allow attribute names starting with 'but'" do
551
+ PlainRubyClass.should_receive(:new).with({:butt => 'pear-shaped'})
552
+ invoke_cucumber_step('there is a plain ruby class with the butt "pear-shaped"')
553
+ end
554
+
555
+ it "should allow to set array attributes via doc string" do
556
+ invoke_cucumber_step('there is a plain Ruby class with these attributes:', <<-DOC_STRING)
557
+ tags: ["foo", "bar"]
558
+ DOC_STRING
559
+
560
+ obj = PlainRubyClass.last
561
+ obj.attributes[:tags].should == ['foo', 'bar']
562
+ end
563
+
564
+ it "should allow to set array attributes via data table" do
565
+ invoke_cucumber_step('there is a plain Ruby class with these attributes:', nil, <<-DATA_TABLE)
441
566
  | tags | ["foo", "bar"] |
442
- DATA_TABLE
567
+ DATA_TABLE
443
568
 
444
- obj = PlainRubyClass.last
445
- obj.attributes[:tags].should == ['foo', 'bar']
446
- end
569
+ obj = PlainRubyClass.last
570
+ obj.attributes[:tags].should == ['foo', 'bar']
571
+ end
447
572
 
448
- it 'should allow named records when setting attributes via data table' do
449
- invoke_cucumber_step('"Some Prequel" is a movie with these attributes:', nil, <<-DATA_TABLE)
450
- | title | Before Sunrise |
451
- DATA_TABLE
452
- invoke_cucumber_step('there is a movie with the title "Limitless"')
453
- invoke_cucumber_step('there is a movie with the title "Before Sunset" and the prequel "Some Prequel"')
454
- movie = Movie.find_by_title!('Before Sunset')
455
- prequel = Movie.find_by_title!('Before Sunrise')
456
- movie.prequel.should == prequel
457
- end
573
+ it "should create models that have a machinist blueprint by calling #make" do
574
+ MachinistModel.should_receive(:make).with({ :attribute => "foo"})
575
+ invoke_cucumber_step('there is a machinist model with the attribute "foo"')
576
+ end
458
577
 
459
- it "should allow single quote for attribute values" do
460
- MachinistModel.should_receive(:make).with({ :attribute => "foo"})
461
- invoke_cucumber_step("there is a machinist model with the attribute 'foo'")
462
- end
578
+ it "should be able to step_match machinist blueprint variants" do
579
+ MachinistModel.should_receive(:make).with(:variant, { :attribute => "foo"})
580
+ invoke_cucumber_step('there is a machinist model (variant) with the attribute "foo"')
581
+ end
463
582
 
464
- it "should allow mixed single and double quotes for different attribute values" do
465
- MachinistModel.should_receive(:make).with({ :attribute => "foo", :other_attribute => "bar" })
466
- invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the other attribute \"bar\"")
467
- end
583
+ it "should be able to step_match machinist blueprint variants containing spaces or uppercase characters in prose" do
584
+ MachinistModel.should_receive(:make).with(:variant_mark_two, { :attribute => "foo"})
585
+ invoke_cucumber_step('there is a machinist model (Variant Mark Two) with the attribute "foo"')
586
+ end
468
587
 
469
- it "should allow mixed single quotes for model names" do
470
- invoke_cucumber_step("'Some Prequel' is a movie with the title \"Before Sunrise\"")
471
- invoke_cucumber_step('there is a movie with the title "Limitless"')
472
- invoke_cucumber_step('there is a movie with the title \'Before Sunset\' and the prequel "Some Prequel"')
473
- movie = Movie.find_by_title!('Before Sunset')
474
- prequel = Movie.find_by_title!('Before Sunrise')
475
- movie.prequel.should == prequel
476
- end
588
+ it "should allow single quote for attribute values" do
589
+ MachinistModel.should_receive(:make).with({ :attribute => "foo"})
590
+ invoke_cucumber_step("there is a machinist model with the attribute 'foo'")
591
+ end
477
592
 
478
- it 'should not raise an error for a blank instance name' do
479
- MachinistModel.should_receive(:make).with({ :attribute => 'foo' })
480
- invoke_cucumber_step("'' is a machinist model with the attribute 'foo'")
481
- end
593
+ it "should allow mixed single and double quotes for different attribute values" do
594
+ MachinistModel.should_receive(:make).with({ :attribute => "foo", :other_attribute => "bar" })
595
+ invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the other attribute \"bar\"")
596
+ end
482
597
 
483
- it 'should warn if there are unused fragments' do
484
- MachinistModel.should_not_receive(:make)
485
- lambda { invoke_cucumber_step("there is a machinist model with the attribute NOQUOTES") }.should raise_error(ArgumentError, 'Unable to parse attributes " with the attribute NOQUOTES".')
486
- lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the ") }.should raise_error(ArgumentError, 'Unable to parse attributes " and the ".')
487
- lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo'. the other_attribute 'bar' and the third attribute 'baz'") }.should raise_error(ArgumentError, 'Unable to parse attributes ".".')
488
- end
598
+ it 'should not raise an error for a blank instance name' do
599
+ MachinistModel.should_receive(:make).with({ :attribute => 'foo' })
600
+ invoke_cucumber_step("'' is a machinist model with the attribute 'foo'")
601
+ end
602
+
603
+ it 'should warn if there are unused fragments' do
604
+ MachinistModel.should_not_receive(:make)
605
+ lambda { invoke_cucumber_step("there is a machinist model with the attribute NOQUOTES") }.should raise_error(ArgumentError, 'Unable to parse attributes " with the attribute NOQUOTES".')
606
+ lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo' and the ") }.should raise_error(ArgumentError, 'Unable to parse attributes " and the ".')
607
+ lambda { invoke_cucumber_step("there is a machinist model with the attribute 'foo'. the other_attribute 'bar' and the third attribute 'baz'") }.should raise_error(ArgumentError, 'Unable to parse attributes ".".')
608
+ end
489
609
 
610
+ it "should allow to update the last record of a type" do
611
+ invoke_cucumber_step('there is a user with the name "Bar" and the email "foo@example.com" who is not subscribed')
612
+ invoke_cucumber_step('the user above has the name "Baz", the email "foobar@example.com", and is subscribed')
613
+ user = User.last
614
+ user.name.should == 'Baz'
615
+ user.email.should == 'foobar@example.com'
616
+ user.subscribed.should == true
617
+ end
618
+
619
+ it "should be able to update a record with a given name" do
620
+ invoke_cucumber_step('"Foo" is a user with the name "Bar"')
621
+ invoke_cucumber_step('the user "Foo" has the email "foo@example.com"')
622
+ invoke_cucumber_step("the user 'Foo' is subscribed")
623
+ user = User.last
624
+ user.name.should == 'Bar'
625
+ user.email.should == 'foo@example.com'
626
+ user.subscribed.should == true
627
+ end
628
+
629
+ it "should be able to update a record with a Cucumber table expression" do
630
+ invoke_cucumber_step('"Foo" is a user with the name "Bar"')
631
+ invoke_cucumber_step('the user above has these attributes:', nil, <<-DATA_TABLE)
632
+ | name | Jane |
633
+ | locked | true |
634
+ DATA_TABLE
635
+ user = User.last
636
+ user.name.should == "Jane"
637
+ user.locked.should == true
638
+ end
639
+
640
+ it "should allow to update a record via additional data table" do
641
+ invoke_cucumber_step('"Foo" is a user with the name "Bar"')
642
+ invoke_cucumber_step('the user above has the email "test@invalid.com", is subscribed, and has these attributes:', nil, <<-DATA_TABLE)
643
+ | name | Jane |
644
+ DATA_TABLE
645
+ user = User.last
646
+ user.name.should == "Jane"
647
+ user.email.should == "test@invalid.com"
648
+ user.subscribed.should == true
649
+ end
650
+
651
+ it "should allow to update an association" do
652
+ invoke_cucumber_step('there is a movie with the title "Before Sunrise"')
653
+ invoke_cucumber_step('there is a user with the name "John"')
654
+ invoke_cucumber_step('the movie above has the reviewer above')
655
+ movie = Movie.last
656
+ movie.reviewer.name.should == "John"
657
+ end
658
+ end
490
659
  end