Skip to content

Commit 36b4ded

Browse files
committed
Support multi line attribute assignment
1 parent 10ee488 commit 36b4ded

File tree

8 files changed

+162
-25
lines changed

8 files changed

+162
-25
lines changed

README.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ Given there is a movie which is awesome, popular and successful but not science
3939
And there is a director with the income "500000" but with the account balance "-30000"
4040
```
4141

42+
If you have many attribute assignments you can use doc string or data table:
43+
44+
```cucumber
45+
Given there is a movie with these attributes:
46+
"""
47+
name: Sunshine
48+
comedy: false
49+
"""
50+
```
51+
52+
```cucumber
53+
Given there is a movie with these attributes:
54+
| name | Sunshine |
55+
| comedy | false |
56+
```
4257

4358
Setting associations
4459
--------------------
@@ -70,6 +85,22 @@ And there is a movie with the prequel "Before Sunrise"
7085

7186
Note that in the example above, "Before Sunrise" is only a name you can use to refer to the record. The name is not actually used for the movie title, or any other attribute value.
7287

88+
It is not possible to define associations in doc string or data table, but you can combine them in one
89+
step:
90+
91+
```cucumber
92+
Given there is a movie with the prequel above and these attributes:
93+
"""
94+
name: Sunshine
95+
comedy: false
96+
"""
97+
```
98+
99+
```cucumber
100+
Given there is a movie with the prequel above and these attributes:
101+
| name | Sunshine |
102+
| comedy | false |
103+
```
73104

74105
Support for popular factory gems
75106
--------------------------------
@@ -134,12 +165,13 @@ There are tests in `spec`. We only accept PRs with tests. To run tests:
134165
- Create a local MySQL database `cucumber_factory_test`
135166
- Copy `spec/support/database.sample.yml` to `spec/support/database.yml` and enter your local credentials for the test databases
136167
- Install development dependencies using `bundle install`
137-
- Run tests using `bundle exec rspec`
168+
- Run tests with the default symlinked Gemfile using `bundle exec rspec` or explicit with `BUNDLE_GEMFILE=gemfiles/Gemfile.cucumber-x.x bundle exec rspec spec`
138169

139170
We recommend to test large changes against multiple versions of Ruby and multiple dependency sets. Supported combinations are configured in `.travis.yml`. We provide some rake tasks to help with this:
140171

141-
- Install development dependencies using `bundle matrix:install`
142-
- Run tests using `bundle matrix:spec`
172+
For each ruby version do (you need to change it manually):
173+
- Install development dependencies using `rake matrix:install`
174+
- Run tests using `rake matrix:spec`
143175

144176
Note that we have configured Travis CI to automatically run tests in all supported Ruby versions and dependency sets after each push. We will only merge pull requests after a green Travis build.
145177

gemfiles/Gemfile.cucumber-1.3.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ..
33
specs:
4-
cucumber_factory (1.11.9)
4+
cucumber_factory (1.12.0)
55
activerecord
66
activesupport
77
cucumber
@@ -20,7 +20,7 @@ GEM
2020
gherkin (~> 2.12)
2121
multi_json (>= 1.7.5, < 2.0)
2222
multi_test (>= 0.1.2)
23-
cucumber_priority (0.3.0)
23+
cucumber_priority (0.3.1)
2424
cucumber
2525
database_cleaner (1.0.1)
2626
diff-lcs (1.2.5)

gemfiles/Gemfile.cucumber-2.4.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ..
33
specs:
4-
cucumber_factory (1.11.9)
4+
cucumber_factory (1.12.0)
55
activerecord
66
activesupport
77
cucumber
@@ -36,7 +36,7 @@ GEM
3636
cucumber-core (1.5.0)
3737
gherkin (~> 4.0)
3838
cucumber-wire (0.0.1)
39-
cucumber_priority (0.3.0)
39+
cucumber_priority (0.3.1)
4040
cucumber
4141
database_cleaner (1.6.2)
4242
diff-lcs (1.3)

gemfiles/Gemfile.cucumber-3.0.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ..
33
specs:
4-
cucumber_factory (1.11.9)
4+
cucumber_factory (1.12.0)
55
activerecord
66
activesupport
77
cucumber
@@ -41,7 +41,7 @@ GEM
4141
cucumber-expressions (4.0.4)
4242
cucumber-tag_expressions (1.1.1)
4343
cucumber-wire (0.0.1)
44-
cucumber_priority (0.3.0)
44+
cucumber_priority (0.3.1)
4545
cucumber
4646
database_cleaner (1.6.2)
4747
diff-lcs (1.3)

gemfiles/Gemfile.cucumber-3.1.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ..
33
specs:
4-
cucumber_factory (1.11.9)
4+
cucumber_factory (1.12.0)
55
activerecord
66
activesupport
77
cucumber
@@ -41,7 +41,7 @@ GEM
4141
cucumber-expressions (5.0.13)
4242
cucumber-tag_expressions (1.1.1)
4343
cucumber-wire (0.0.1)
44-
cucumber_priority (0.3.0)
44+
cucumber_priority (0.3.1)
4545
cucumber
4646
database_cleaner (1.6.2)
4747
diff-lcs (1.3)

lib/cucumber/factory.rb

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ module Cucumber
44
class Factory
55

66
ATTRIBUTES_PATTERN = '( with the .+?)?( (?:which|who|that) is .+?)?'
7+
TEXT_ATTRIBUTES_PATTERN = ' (?:with|and) these attributes:'
8+
9+
RECORD_PATTERN = 'there is an? (.+?)( \(.+?\))?'
10+
NAMED_RECORD_PATTERN = '"([^\"]*)" is an? (.+?)( \(.+?\))?'
711

812
NAMED_RECORDS_VARIABLE = :'@named_cucumber_factory_records'
913

@@ -12,25 +16,43 @@ class Factory
1216
:block => proc { Cucumber::Factory.send(:reset_named_records, self) }
1317
}
1418

19+
# We cannot use vararg blocks in the descriptors in Ruby 1.8, as explained by
20+
# Aslak: http://www.ruby-forum.com/topic/182927. We use different descriptors and cucumber priority to work around
21+
# it.
22+
1523
NAMED_CREATION_STEP_DESCRIPTOR = {
1624
:kind => :Given,
17-
:pattern => /^"([^\"]*)" is an? (.+?)( \(.+?\))?#{ATTRIBUTES_PATTERN}?$/,
18-
# we cannot use vararg blocks here in Ruby 1.8, as explained by Aslak: http://www.ruby-forum.com/topic/182927
25+
:pattern => /^#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}?$/,
1926
:block => lambda { |a1, a2, a3, a4, a5| Cucumber::Factory.send(:parse_named_creation, self, a1, a2, a3, a4, a5) }
2027
}
2128

2229
CREATION_STEP_DESCRIPTOR = {
2330
:kind => :Given,
24-
:pattern => /^there is an? (.+?)( \(.+?\))?#{ATTRIBUTES_PATTERN}$/,
25-
# we cannot use vararg blocks here in Ruby 1.8, as explained by Aslak: http://www.ruby-forum.com/topic/182927
31+
:pattern => /^#{RECORD_PATTERN}#{ATTRIBUTES_PATTERN}$/,
2632
:block => lambda { |a1, a2, a3, a4| Cucumber::Factory.send(:parse_creation, self, a1, a2, a3, a4) }
2733
}
2834

35+
NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = {
36+
:kind => :Given,
37+
:pattern => /^"#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}?$/,
38+
:block => lambda { |a1, a2, a3, a4, a5, a6| Cucumber::Factory.send(:parse_named_creation, self, a1, a2, a3, a4, a5, a6) },
39+
:priority => true
40+
}
41+
42+
CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = {
43+
:kind => :Given,
44+
:pattern => /^#{RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}$/,
45+
:block => lambda { |a1, a2, a3, a4, a5| Cucumber::Factory.send(:parse_creation, self, a1, a2, a3, a4, a5) },
46+
:priority => true
47+
}
48+
2949
class << self
3050

3151
def add_steps(main)
3252
add_step(main, CREATION_STEP_DESCRIPTOR)
3353
add_step(main, NAMED_CREATION_STEP_DESCRIPTOR)
54+
add_step(main, CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
55+
add_step(main, NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
3456
add_step(main, CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR)
3557
end
3658

@@ -40,7 +62,7 @@ def add_step(main, descriptor)
4062
main.instance_eval {
4163
kind = descriptor[:kind]
4264
object = send(kind, *[descriptor[:pattern]].compact, &descriptor[:block])
43-
object.overridable if kind != :Before
65+
object.overridable(:priority => descriptor[:priority] ? 1 : 0) if kind != :Before
4466
object
4567
}
4668
end
@@ -64,12 +86,12 @@ def set_named_record(world, name, record)
6486
named_records(world)[name] = record
6587
end
6688

67-
def parse_named_creation(world, name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes)
68-
record = parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes)
89+
def parse_named_creation(world, name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
90+
record = parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes)
6991
set_named_record(world, name, record)
7092
end
7193

72-
def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes)
94+
def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
7395
build_strategy = BuildStrategy.from_prose(raw_model, raw_variant)
7496
model_class = build_strategy.model_class
7597
attributes = {}
@@ -88,6 +110,22 @@ def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_at
88110
attributes[attribute] = flag
89111
end
90112
end
113+
if raw_multiline_attributes.present?
114+
# DocString e.g. "first name: Jane\nlast name: Jenny\n"
115+
if raw_multiline_attributes.is_a?(String)
116+
raw_multiline_attributes.split("\n").each do |fragment|
117+
raw_attribute, value = fragment.split(': ')
118+
attribute = attribute_name_from_prose(raw_attribute)
119+
attributes[attribute] = value
120+
end
121+
# DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]]
122+
else
123+
raw_multiline_attributes.raw.each do |raw_attribute, value|
124+
attribute = attribute_name_from_prose(raw_attribute)
125+
attributes[attribute] = value
126+
end
127+
end
128+
end
91129
record = build_strategy.create_record(attributes)
92130
remember_record_names(world, record, attributes)
93131
record

spec/cucumber_factory/steps_spec.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,54 @@
294294
invoke_cucumber_step('there is a plain ruby class with the butt "pear-shaped"')
295295
end
296296

297+
it "should allow to set attributes via doc string" do
298+
user = User.new
299+
User.stub(:new => user)
300+
301+
invoke_cucumber_step('there is a user with these attributes:', <<-DOC_STRING)
302+
name: Jane
303+
locked: true
304+
DOC_STRING
305+
306+
user.name.should == "Jane"
307+
user.locked.should == true
308+
end
309+
310+
it "should allow to set attributes via additional doc string" do
311+
user = User.new
312+
User.stub(:new => user)
313+
314+
invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', <<-DOC_STRING)
315+
name: Jane
316+
DOC_STRING
317+
318+
user.name.should == "Jane"
319+
user.email.should == "test@invalid.com"
320+
end
321+
322+
it "should allow to set attributes via data table" do
323+
user = User.new
324+
User.stub(:new => user)
325+
326+
invoke_cucumber_step('there is a user with these attributes:', nil, <<-DATA_TABLE)
327+
| name | Jane |
328+
| locked | true |
329+
DATA_TABLE
330+
331+
user.name.should == "Jane"
332+
user.locked.should == true
333+
end
334+
335+
it "should allow to set attributes via additional data table" do
336+
user = User.new
337+
User.stub(:new => user)
338+
339+
invoke_cucumber_step('there is a user with the email "test@invalid.com" and these attributes:', nil, <<-DATA_TABLE)
340+
| name | Jane |
341+
DATA_TABLE
342+
343+
user.name.should == "Jane"
344+
user.email.should == "test@invalid.com"
345+
end
346+
297347
end

spec/support/cucumber_helper.rb

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,30 @@ def prepare_cucumber_example
2020
Cucumber::Factory.add_steps(@main)
2121
end
2222

23-
def invoke_cucumber_step(step)
24-
multiline_argument = begin
25-
Cucumber::MultilineArgument::None.new # Cucumber 2+
26-
rescue NameError
27-
nil # Cucumber 1
23+
def invoke_cucumber_step(step, doc_string = nil, data_table = nil)
24+
if Cucumber::VERSION >= '2'
25+
multiline_argument = Cucumber::MultilineArgument::None.new
26+
27+
if doc_string.present?
28+
multiline_argument = Cucumber::MultilineArgument::DocString.new(doc_string)
29+
end
30+
31+
if data_table.present?
32+
multiline_argument = Cucumber::MultilineArgument::DataTable.from(data_table)
33+
end
34+
else
35+
multiline_argument = nil
36+
37+
if doc_string.present?
38+
multiline_argument = Cucumber::Ast::DocString.new(doc_string, '')
39+
end
40+
41+
if data_table.present?
42+
multiline_argument = Cucumber::Ast::Table.parse(data_table, nil, nil)
43+
end
2844
end
29-
first_step_match(step).invoke(multiline_argument) # nil means no multiline args
45+
46+
first_step_match(step).invoke(multiline_argument)
3047
end
3148

3249
def support_code

0 commit comments

Comments
 (0)