Skip to content

Commit

Permalink
Support multi line attribute assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
denzelem committed Apr 25, 2018
1 parent 10ee488 commit 36b4ded
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 25 deletions.
38 changes: 35 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ Given there is a movie which is awesome, popular and successful but not science
And there is a director with the income "500000" but with the account balance "-30000"
```

If you have many attribute assignments you can use doc string or data table:

```cucumber
Given there is a movie with these attributes:
"""
name: Sunshine
comedy: false
"""
```

```cucumber
Given there is a movie with these attributes:
| name | Sunshine |
| comedy | false |
```

Setting associations
--------------------
Expand Down Expand Up @@ -70,6 +85,22 @@ And there is a movie with the prequel "Before Sunrise"

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.

It is not possible to define associations in doc string or data table, but you can combine them in one
step:

```cucumber
Given there is a movie with the prequel above and these attributes:
"""
name: Sunshine
comedy: false
"""
```

```cucumber
Given there is a movie with the prequel above and these attributes:
| name | Sunshine |
| comedy | false |
```

Support for popular factory gems
--------------------------------
Expand Down Expand Up @@ -134,12 +165,13 @@ There are tests in `spec`. We only accept PRs with tests. To run tests:
- Create a local MySQL database `cucumber_factory_test`
- Copy `spec/support/database.sample.yml` to `spec/support/database.yml` and enter your local credentials for the test databases
- Install development dependencies using `bundle install`
- Run tests using `bundle exec rspec`
- 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`

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:

- Install development dependencies using `bundle matrix:install`
- Run tests using `bundle matrix:spec`
For each ruby version do (you need to change it manually):
- Install development dependencies using `rake matrix:install`
- Run tests using `rake matrix:spec`

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.

Expand Down
4 changes: 2 additions & 2 deletions gemfiles/Gemfile.cucumber-1.3.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
cucumber_factory (1.11.9)
cucumber_factory (1.12.0)
activerecord
activesupport
cucumber
Expand All @@ -20,7 +20,7 @@ GEM
gherkin (~> 2.12)
multi_json (>= 1.7.5, < 2.0)
multi_test (>= 0.1.2)
cucumber_priority (0.3.0)
cucumber_priority (0.3.1)
cucumber
database_cleaner (1.0.1)
diff-lcs (1.2.5)
Expand Down
4 changes: 2 additions & 2 deletions gemfiles/Gemfile.cucumber-2.4.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
cucumber_factory (1.11.9)
cucumber_factory (1.12.0)
activerecord
activesupport
cucumber
Expand Down Expand Up @@ -36,7 +36,7 @@ GEM
cucumber-core (1.5.0)
gherkin (~> 4.0)
cucumber-wire (0.0.1)
cucumber_priority (0.3.0)
cucumber_priority (0.3.1)
cucumber
database_cleaner (1.6.2)
diff-lcs (1.3)
Expand Down
4 changes: 2 additions & 2 deletions gemfiles/Gemfile.cucumber-3.0.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
cucumber_factory (1.11.9)
cucumber_factory (1.12.0)
activerecord
activesupport
cucumber
Expand Down Expand Up @@ -41,7 +41,7 @@ GEM
cucumber-expressions (4.0.4)
cucumber-tag_expressions (1.1.1)
cucumber-wire (0.0.1)
cucumber_priority (0.3.0)
cucumber_priority (0.3.1)
cucumber
database_cleaner (1.6.2)
diff-lcs (1.3)
Expand Down
4 changes: 2 additions & 2 deletions gemfiles/Gemfile.cucumber-3.1.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
cucumber_factory (1.11.9)
cucumber_factory (1.12.0)
activerecord
activesupport
cucumber
Expand Down Expand Up @@ -41,7 +41,7 @@ GEM
cucumber-expressions (5.0.13)
cucumber-tag_expressions (1.1.1)
cucumber-wire (0.0.1)
cucumber_priority (0.3.0)
cucumber_priority (0.3.1)
cucumber
database_cleaner (1.6.2)
diff-lcs (1.3)
Expand Down
54 changes: 46 additions & 8 deletions lib/cucumber/factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module Cucumber
class Factory

ATTRIBUTES_PATTERN = '( with the .+?)?( (?:which|who|that) is .+?)?'
TEXT_ATTRIBUTES_PATTERN = ' (?:with|and) these attributes:'

RECORD_PATTERN = 'there is an? (.+?)( \(.+?\))?'
NAMED_RECORD_PATTERN = '"([^\"]*)" is an? (.+?)( \(.+?\))?'

NAMED_RECORDS_VARIABLE = :'@named_cucumber_factory_records'

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

# We cannot use vararg blocks in the descriptors in Ruby 1.8, as explained by
# Aslak: http://www.ruby-forum.com/topic/182927. We use different descriptors and cucumber priority to work around
# it.

NAMED_CREATION_STEP_DESCRIPTOR = {
:kind => :Given,
:pattern => /^"([^\"]*)" is an? (.+?)( \(.+?\))?#{ATTRIBUTES_PATTERN}?$/,
# we cannot use vararg blocks here in Ruby 1.8, as explained by Aslak: http://www.ruby-forum.com/topic/182927
:pattern => /^#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}?$/,
:block => lambda { |a1, a2, a3, a4, a5| Cucumber::Factory.send(:parse_named_creation, self, a1, a2, a3, a4, a5) }
}

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

NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = {
:kind => :Given,
:pattern => /^"#{NAMED_RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}?$/,
:block => lambda { |a1, a2, a3, a4, a5, a6| Cucumber::Factory.send(:parse_named_creation, self, a1, a2, a3, a4, a5, a6) },
:priority => true
}

CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES = {
:kind => :Given,
:pattern => /^#{RECORD_PATTERN}#{ATTRIBUTES_PATTERN}#{TEXT_ATTRIBUTES_PATTERN}$/,
:block => lambda { |a1, a2, a3, a4, a5| Cucumber::Factory.send(:parse_creation, self, a1, a2, a3, a4, a5) },
:priority => true
}

class << self

def add_steps(main)
add_step(main, CREATION_STEP_DESCRIPTOR)
add_step(main, NAMED_CREATION_STEP_DESCRIPTOR)
add_step(main, CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
add_step(main, NAMED_CREATION_STEP_DESCRIPTOR_WITH_TEXT_ATTRIBUTES)
add_step(main, CLEAR_NAMED_RECORDS_STEP_DESCRIPTOR)
end

Expand All @@ -40,7 +62,7 @@ def add_step(main, descriptor)
main.instance_eval {
kind = descriptor[:kind]
object = send(kind, *[descriptor[:pattern]].compact, &descriptor[:block])
object.overridable if kind != :Before
object.overridable(:priority => descriptor[:priority] ? 1 : 0) if kind != :Before
object
}
end
Expand All @@ -64,12 +86,12 @@ def set_named_record(world, name, record)
named_records(world)[name] = record
end

def parse_named_creation(world, name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes)
record = parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes)
def parse_named_creation(world, name, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
record = parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes)
set_named_record(world, name, record)
end

def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes)
def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_attributes, raw_multiline_attributes = nil)
build_strategy = BuildStrategy.from_prose(raw_model, raw_variant)
model_class = build_strategy.model_class
attributes = {}
Expand All @@ -88,6 +110,22 @@ def parse_creation(world, raw_model, raw_variant, raw_attributes, raw_boolean_at
attributes[attribute] = flag
end
end
if raw_multiline_attributes.present?
# DocString e.g. "first name: Jane\nlast name: Jenny\n"
if raw_multiline_attributes.is_a?(String)
raw_multiline_attributes.split("\n").each do |fragment|
raw_attribute, value = fragment.split(': ')
attribute = attribute_name_from_prose(raw_attribute)
attributes[attribute] = value
end
# DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]]
else
raw_multiline_attributes.raw.each do |raw_attribute, value|
attribute = attribute_name_from_prose(raw_attribute)
attributes[attribute] = value
end
end
end
record = build_strategy.create_record(attributes)
remember_record_names(world, record, attributes)
record
Expand Down
50 changes: 50 additions & 0 deletions spec/cucumber_factory/steps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,54 @@
invoke_cucumber_step('there is a plain ruby class with the butt "pear-shaped"')
end

it "should allow to set attributes via doc string" do
user = User.new
User.stub(:new => user)

invoke_cucumber_step('there is a user with these attributes:', <<-DOC_STRING)
name: Jane
locked: true
DOC_STRING

user.name.should == "Jane"
user.locked.should == true
end

it "should allow to set attributes via additional doc string" do
user = User.new
User.stub(:new => user)

invoke_cucumber_step('there is a user with the email "[email protected]" and these attributes:', <<-DOC_STRING)
name: Jane
DOC_STRING

user.name.should == "Jane"
user.email.should == "[email protected]"
end

it "should allow to set attributes via data table" do
user = User.new
User.stub(:new => user)

invoke_cucumber_step('there is a user with these attributes:', nil, <<-DATA_TABLE)
| name | Jane |
| locked | true |
DATA_TABLE

user.name.should == "Jane"
user.locked.should == true
end

it "should allow to set attributes via additional data table" do
user = User.new
User.stub(:new => user)

invoke_cucumber_step('there is a user with the email "[email protected]" and these attributes:', nil, <<-DATA_TABLE)
| name | Jane |
DATA_TABLE

user.name.should == "Jane"
user.email.should == "[email protected]"
end

end
29 changes: 23 additions & 6 deletions spec/support/cucumber_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,30 @@ def prepare_cucumber_example
Cucumber::Factory.add_steps(@main)
end

def invoke_cucumber_step(step)
multiline_argument = begin
Cucumber::MultilineArgument::None.new # Cucumber 2+
rescue NameError
nil # Cucumber 1
def invoke_cucumber_step(step, doc_string = nil, data_table = nil)
if Cucumber::VERSION >= '2'
multiline_argument = Cucumber::MultilineArgument::None.new

if doc_string.present?
multiline_argument = Cucumber::MultilineArgument::DocString.new(doc_string)
end

if data_table.present?
multiline_argument = Cucumber::MultilineArgument::DataTable.from(data_table)
end
else
multiline_argument = nil

if doc_string.present?
multiline_argument = Cucumber::Ast::DocString.new(doc_string, '')
end

if data_table.present?
multiline_argument = Cucumber::Ast::Table.parse(data_table, nil, nil)
end
end
first_step_match(step).invoke(multiline_argument) # nil means no multiline args

first_step_match(step).invoke(multiline_argument)
end

def support_code
Expand Down

0 comments on commit 36b4ded

Please sign in to comment.