cucumber_factory 2.1.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +60 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +46 -0
- data/Gemfile.cucumber-1.3 +2 -1
- data/Gemfile.cucumber-1.3.lock +12 -4
- data/Gemfile.cucumber-2.4 +2 -1
- data/Gemfile.cucumber-2.4.lock +12 -4
- data/Gemfile.cucumber-3.0 +2 -1
- data/Gemfile.cucumber-3.0.lock +12 -4
- data/Gemfile.cucumber-3.1 +2 -1
- data/Gemfile.cucumber-3.1.lock +12 -4
- data/Gemfile.cucumber-4.1 +20 -0
- data/Gemfile.cucumber-4.1.lock +140 -0
- data/Gemfile.cucumber-5.3 +20 -0
- data/Gemfile.cucumber-5.3.lock +140 -0
- data/README.md +36 -6
- data/lib/cucumber_factory.rb +1 -0
- data/lib/cucumber_factory/build_strategy.rb +6 -1
- data/lib/cucumber_factory/factory.rb +97 -37
- data/lib/cucumber_factory/update_strategy.rb +30 -0
- data/lib/cucumber_factory/version.rb +1 -1
- data/spec/assets/file.txt +1 -0
- data/spec/assets/file2.txt +1 -0
- data/spec/assets/symlink.txt +1 -0
- data/spec/cucumber_factory/steps_spec.rb +173 -7
- data/spec/spec_helper.rb +3 -0
- data/spec/support/database.github.yml +6 -0
- data/spec/support/database.rb +3 -0
- data/spec/support/database.sample.yml +3 -3
- data/spec/support/factories/factories.rb +15 -1
- data/spec/support/models/movie.rb +1 -0
- data/spec/support/models/opera.rb +1 -1
- data/spec/support/models/payment.rb +13 -8
- data/spec/support/uploaders/attachment_uploader.rb +3 -0
- metadata +14 -5
- data/.travis.yml +0 -32
- data/spec/support/database.travis.yml +0 -4
@@ -0,0 +1,20 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Runtime dependencies
|
4
|
+
gem 'cucumber', '~> 5.3.0'
|
5
|
+
gem 'activesupport', '~> 6.1.0'
|
6
|
+
gem 'activerecord', '~> 6.1.0'
|
7
|
+
gem 'pg'
|
8
|
+
|
9
|
+
# Development dependencies
|
10
|
+
gem 'rspec', '~> 3.0'
|
11
|
+
gem 'rake'
|
12
|
+
gem 'database_cleaner'
|
13
|
+
gem 'gemika'
|
14
|
+
|
15
|
+
# Test dependencies
|
16
|
+
gem 'factory_bot'
|
17
|
+
gem 'carrierwave'
|
18
|
+
|
19
|
+
# Gem under test
|
20
|
+
gem 'cucumber_factory', :path => '.'
|
@@ -0,0 +1,140 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cucumber_factory (2.4.0)
|
5
|
+
activerecord
|
6
|
+
activesupport
|
7
|
+
cucumber
|
8
|
+
cucumber_priority (>= 0.2.0)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activemodel (6.1.3.1)
|
14
|
+
activesupport (= 6.1.3.1)
|
15
|
+
activerecord (6.1.3.1)
|
16
|
+
activemodel (= 6.1.3.1)
|
17
|
+
activesupport (= 6.1.3.1)
|
18
|
+
activesupport (6.1.3.1)
|
19
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
20
|
+
i18n (>= 1.6, < 2)
|
21
|
+
minitest (>= 5.1)
|
22
|
+
tzinfo (~> 2.0)
|
23
|
+
zeitwerk (~> 2.3)
|
24
|
+
addressable (2.7.0)
|
25
|
+
public_suffix (>= 2.0.2, < 5.0)
|
26
|
+
builder (3.2.4)
|
27
|
+
carrierwave (2.2.1)
|
28
|
+
activemodel (>= 5.0.0)
|
29
|
+
activesupport (>= 5.0.0)
|
30
|
+
addressable (~> 2.6)
|
31
|
+
image_processing (~> 1.1)
|
32
|
+
marcel (~> 1.0.0)
|
33
|
+
mini_mime (>= 0.1.3)
|
34
|
+
ssrf_filter (~> 1.0)
|
35
|
+
concurrent-ruby (1.1.8)
|
36
|
+
cucumber (5.3.0)
|
37
|
+
builder (~> 3.2, >= 3.2.4)
|
38
|
+
cucumber-core (~> 8.0, >= 8.0.1)
|
39
|
+
cucumber-create-meta (~> 2.0, >= 2.0.2)
|
40
|
+
cucumber-cucumber-expressions (~> 10.3, >= 10.3.0)
|
41
|
+
cucumber-gherkin (~> 15.0, >= 15.0.2)
|
42
|
+
cucumber-html-formatter (~> 9.0, >= 9.0.0)
|
43
|
+
cucumber-messages (~> 13.1, >= 13.1.0)
|
44
|
+
cucumber-wire (~> 4.0, >= 4.0.1)
|
45
|
+
diff-lcs (~> 1.4, >= 1.4.4)
|
46
|
+
multi_test (~> 0.1, >= 0.1.2)
|
47
|
+
sys-uname (~> 1.2, >= 1.2.1)
|
48
|
+
cucumber-core (8.0.1)
|
49
|
+
cucumber-gherkin (~> 15.0, >= 15.0.2)
|
50
|
+
cucumber-messages (~> 13.0, >= 13.0.1)
|
51
|
+
cucumber-tag-expressions (~> 2.0, >= 2.0.4)
|
52
|
+
cucumber-create-meta (2.0.4)
|
53
|
+
cucumber-messages (~> 13.1, >= 13.1.0)
|
54
|
+
sys-uname (~> 1.2, >= 1.2.1)
|
55
|
+
cucumber-cucumber-expressions (10.3.0)
|
56
|
+
cucumber-gherkin (15.0.2)
|
57
|
+
cucumber-messages (~> 13.0, >= 13.0.1)
|
58
|
+
cucumber-html-formatter (9.0.0)
|
59
|
+
cucumber-messages (~> 13.0, >= 13.0.1)
|
60
|
+
cucumber-messages (13.2.1)
|
61
|
+
protobuf-cucumber (~> 3.10, >= 3.10.8)
|
62
|
+
cucumber-tag-expressions (2.0.4)
|
63
|
+
cucumber-wire (4.0.1)
|
64
|
+
cucumber-core (~> 8.0, >= 8.0.1)
|
65
|
+
cucumber-cucumber-expressions (~> 10.3, >= 10.3.0)
|
66
|
+
cucumber-messages (~> 13.0, >= 13.0.1)
|
67
|
+
cucumber_priority (0.3.2)
|
68
|
+
cucumber
|
69
|
+
database_cleaner (2.0.1)
|
70
|
+
database_cleaner-active_record (~> 2.0.0)
|
71
|
+
database_cleaner-active_record (2.0.0)
|
72
|
+
activerecord (>= 5.a)
|
73
|
+
database_cleaner-core (~> 2.0.0)
|
74
|
+
database_cleaner-core (2.0.1)
|
75
|
+
diff-lcs (1.4.4)
|
76
|
+
factory_bot (6.1.0)
|
77
|
+
activesupport (>= 5.0.0)
|
78
|
+
ffi (1.15.0)
|
79
|
+
gemika (0.5.0)
|
80
|
+
i18n (1.8.9)
|
81
|
+
concurrent-ruby (~> 1.0)
|
82
|
+
image_processing (1.12.1)
|
83
|
+
mini_magick (>= 4.9.5, < 5)
|
84
|
+
ruby-vips (>= 2.0.17, < 3)
|
85
|
+
marcel (1.0.0)
|
86
|
+
middleware (0.1.0)
|
87
|
+
mini_magick (4.11.0)
|
88
|
+
mini_mime (1.0.3)
|
89
|
+
minitest (5.14.4)
|
90
|
+
multi_test (0.1.2)
|
91
|
+
pg (1.2.3)
|
92
|
+
protobuf-cucumber (3.10.8)
|
93
|
+
activesupport (>= 3.2)
|
94
|
+
middleware
|
95
|
+
thor
|
96
|
+
thread_safe
|
97
|
+
public_suffix (4.0.6)
|
98
|
+
rake (13.0.3)
|
99
|
+
rspec (3.10.0)
|
100
|
+
rspec-core (~> 3.10.0)
|
101
|
+
rspec-expectations (~> 3.10.0)
|
102
|
+
rspec-mocks (~> 3.10.0)
|
103
|
+
rspec-core (3.10.1)
|
104
|
+
rspec-support (~> 3.10.0)
|
105
|
+
rspec-expectations (3.10.1)
|
106
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
107
|
+
rspec-support (~> 3.10.0)
|
108
|
+
rspec-mocks (3.10.2)
|
109
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
110
|
+
rspec-support (~> 3.10.0)
|
111
|
+
rspec-support (3.10.2)
|
112
|
+
ruby-vips (2.1.0)
|
113
|
+
ffi (~> 1.12)
|
114
|
+
ssrf_filter (1.0.7)
|
115
|
+
sys-uname (1.2.2)
|
116
|
+
ffi (~> 1.1)
|
117
|
+
thor (1.1.0)
|
118
|
+
thread_safe (0.3.6)
|
119
|
+
tzinfo (2.0.4)
|
120
|
+
concurrent-ruby (~> 1.0)
|
121
|
+
zeitwerk (2.4.2)
|
122
|
+
|
123
|
+
PLATFORMS
|
124
|
+
ruby
|
125
|
+
|
126
|
+
DEPENDENCIES
|
127
|
+
activerecord (~> 6.1.0)
|
128
|
+
activesupport (~> 6.1.0)
|
129
|
+
carrierwave
|
130
|
+
cucumber (~> 5.3.0)
|
131
|
+
cucumber_factory!
|
132
|
+
database_cleaner
|
133
|
+
factory_bot
|
134
|
+
gemika
|
135
|
+
pg
|
136
|
+
rake
|
137
|
+
rspec (~> 3.0)
|
138
|
+
|
139
|
+
BUNDLED WITH
|
140
|
+
2.1.4
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
cucumber_factory [](https://github.com/makandra/cucumber_factory/actions?query=branch:master)
|
2
2
|
================
|
3
3
|
|
4
4
|
Create ActiveRecord objects without step definitions
|
@@ -24,6 +24,13 @@ Quoted strings and numbers denote attribute values:
|
|
24
24
|
Given there is a movie with the title "Sunshine" and the year 2007
|
25
25
|
```
|
26
26
|
|
27
|
+
To update an existing record, specify the record and the changes:
|
28
|
+
```
|
29
|
+
Given the movie above has the title "Sunset" and the year 2008
|
30
|
+
Given the movie "Sunrise" has the year 2009
|
31
|
+
```
|
32
|
+
A record can be specified by the `above` keyword, which uses the last created record of this class, or by any string that was used during its creation.
|
33
|
+
|
27
34
|
Setting boolean attributes
|
28
35
|
--------------------------
|
29
36
|
|
@@ -42,6 +49,13 @@ Given there is a movie which is awesome, popular and successful but not science
|
|
42
49
|
And there is a director with the income "500000" but with the account balance "-30000"
|
43
50
|
```
|
44
51
|
|
52
|
+
To update boolean attributes use the keyword `is`:
|
53
|
+
|
54
|
+
```cucumber
|
55
|
+
Given the movie above is awesome but not popular
|
56
|
+
Given the movie above has the year 1979 but is not science fiction
|
57
|
+
```
|
58
|
+
|
45
59
|
|
46
60
|
Setting many attributes with a table
|
47
61
|
------------------------------------
|
@@ -62,6 +76,14 @@ Given there is a movie with these attributes:
|
|
62
76
|
| comedy | false |
|
63
77
|
```
|
64
78
|
|
79
|
+
```cucumber
|
80
|
+
Given the movie above has these attributes:
|
81
|
+
"""
|
82
|
+
name: Sunshine
|
83
|
+
comedy: false
|
84
|
+
"""
|
85
|
+
```
|
86
|
+
|
65
87
|
Setting associations
|
66
88
|
--------------------
|
67
89
|
|
@@ -129,7 +151,16 @@ Given there is a movie with the tags ["comedy", "drama" and "action"]
|
|
129
151
|
```
|
130
152
|
|
131
153
|
|
154
|
+
Setting file attributes
|
155
|
+
-----------------------
|
156
|
+
|
157
|
+
You can set an attribute to a file object with the following syntax:
|
158
|
+
|
159
|
+
```cucumber
|
160
|
+
Given there is a movie with the image file:'path/to/image.jpg'
|
161
|
+
```
|
132
162
|
|
163
|
+
All paths are relative to the project root, absolute paths are not supported. Please note that file attributes must follow the syntax `file:"PATH"`, both single and double quotes are allowed.
|
133
164
|
|
134
165
|
Using named factories and traits
|
135
166
|
--------------------------------
|
@@ -191,23 +222,22 @@ Development
|
|
191
222
|
There are tests in `spec`. We only accept PRs with tests. To run tests:
|
192
223
|
|
193
224
|
- Install the Ruby version stated in `.ruby-version`
|
194
|
-
- Create a local
|
225
|
+
- Create a local PostgreSQL database:
|
195
226
|
```
|
196
|
-
$
|
197
|
-
> create database cucumber_factory_test;
|
227
|
+
$ sudo -u postgres psql -c 'create database cucumber_factory_test;'
|
198
228
|
```
|
199
229
|
|
200
230
|
- Copy `spec/support/database.sample.yml` to `spec/support/database.yml` and enter your local credentials for the test databases
|
201
231
|
- Install development dependencies using `bundle install`
|
202
232
|
- Run tests with the default symlinked Gemfile using `bundle exec rspec` or explicit with `BUNDLE_GEMFILE=Gemfile.cucumber-x.x bundle exec rspec spec`
|
203
233
|
|
204
|
-
We recommend to test large changes against multiple versions of Ruby and multiple dependency sets. Supported combinations are configured in
|
234
|
+
We recommend to test large changes against multiple versions of Ruby and multiple dependency sets. Supported combinations are configured in .github/workflows/test.yml. We provide some rake tasks to help with this:
|
205
235
|
|
206
236
|
For each ruby version do (you need to change it manually):
|
207
237
|
- Install development dependencies using `rake matrix:install`
|
208
238
|
- Run tests using `rake matrix:spec`
|
209
239
|
|
210
|
-
Note that we have configured
|
240
|
+
Note that we have configured GitHub Actions to automatically run tests in all supported Ruby versions and dependency sets after each push. We will only merge pull requests after a green workflow build.
|
211
241
|
|
212
242
|
If you would like to contribute:
|
213
243
|
|
data/lib/cucumber_factory.rb
CHANGED
@@ -11,6 +11,7 @@ module CucumberFactory
|
|
11
11
|
factory = factory_bot_factory(model_prose, variants)
|
12
12
|
|
13
13
|
if factory
|
14
|
+
factory.compile # Required to load inherited traits!
|
14
15
|
strategy = factory_bot_strategy(factory, model_prose, variants)
|
15
16
|
transient_attributes = factory_bot_transient_attributes(factory, variants)
|
16
17
|
else
|
@@ -21,6 +22,10 @@ module CucumberFactory
|
|
21
22
|
[strategy, transient_attributes]
|
22
23
|
end
|
23
24
|
|
25
|
+
def parse_model_class(model_prose)
|
26
|
+
underscored_model_name(model_prose).camelize.constantize
|
27
|
+
end
|
28
|
+
|
24
29
|
private
|
25
30
|
|
26
31
|
def variants_from_prose(variant_prose)
|
@@ -84,7 +89,7 @@ module CucumberFactory
|
|
84
89
|
end
|
85
90
|
|
86
91
|
def alternative_strategy(model_prose, variants)
|
87
|
-
model_class =
|
92
|
+
model_class = parse_model_class(model_prose)
|
88
93
|
machinist_strategy(model_class, variants) ||
|
89
94
|
active_record_strategy(model_class) ||
|
90
95
|
ruby_object_strategy(model_class)
|
@@ -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
|
-
|
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,8 +120,24 @@ 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, transient_attributes = 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})/
|
@@ -126,59 +162,71 @@ 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)
|
167
|
+
value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/)
|
132
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)
|
174
|
+
value = "\"#{value}\"" unless matches_fully?(value, /#{VALUE_ARRAY}|#{VALUE_FILE}/)
|
139
175
|
attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
|
140
176
|
end
|
141
177
|
end
|
142
178
|
end
|
143
|
-
|
144
|
-
remember_record_names(world, record, attributes)
|
145
|
-
record
|
179
|
+
attributes
|
146
180
|
end
|
147
181
|
|
148
182
|
def attribute_value(world, model_class, transient_attributes, attribute, value)
|
149
|
-
association_class =
|
150
|
-
|
151
|
-
if matches_fully?(value, VALUE_ARRAY)
|
152
|
-
|
153
|
-
|
154
|
-
elsif
|
155
|
-
|
156
|
-
value = CucumberFactory::Switcher.find_last(association_class) 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
|
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
|
-
|
191
|
+
resolve_scalar_value(world, model_class, attribute, value)
|
168
192
|
end
|
169
193
|
value
|
170
194
|
end
|
171
195
|
|
172
|
-
def
|
196
|
+
def resolve_association(attribute, model_class, transient_attributes)
|
173
197
|
return unless model_class.respond_to?(:reflect_on_association)
|
174
198
|
|
175
|
-
|
176
|
-
|
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
|
177
205
|
elsif transient_attributes.include?(attribute.to_sym)
|
178
206
|
klass_name = attribute.to_s.camelize
|
179
|
-
|
207
|
+
if Object.const_defined?(klass_name)
|
208
|
+
association_class = klass_name.constantize
|
209
|
+
associated = true
|
210
|
+
end
|
180
211
|
else
|
181
|
-
|
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}."
|
182
230
|
end
|
183
231
|
end
|
184
232
|
|
@@ -190,6 +238,9 @@ module CucumberFactory
|
|
190
238
|
value = value.to_i
|
191
239
|
elsif matches_fully?(value, VALUE_DECIMAL)
|
192
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)
|
193
244
|
else
|
194
245
|
raise Error, "Cannot set attribute #{model_class}##{attribute} to #{value}."
|
195
246
|
end
|
@@ -197,11 +248,20 @@ module CucumberFactory
|
|
197
248
|
end
|
198
249
|
|
199
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
|
200
253
|
string[1, string.length - 2]
|
201
254
|
end
|
202
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
|
+
|
203
263
|
def full_regexp(partial_regexp)
|
204
|
-
Regexp.new(
|
264
|
+
Regexp.new('\\A(?:' + partial_regexp.source + ')\\z', partial_regexp.options)
|
205
265
|
end
|
206
266
|
|
207
267
|
def matches_fully?(string, partial_regexp)
|
@@ -221,7 +281,7 @@ module CucumberFactory
|
|
221
281
|
end
|
222
282
|
|
223
283
|
def attribute_name_from_prose(prose)
|
224
|
-
prose.downcase.gsub(
|
284
|
+
prose.downcase.gsub(' ', '_').to_sym
|
225
285
|
end
|
226
286
|
|
227
287
|
def remember_record_names(world, record, attributes)
|