cucumber_factory 2.0.2 → 2.1.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/CHANGELOG.md +13 -0
- data/Gemfile.cucumber-1.3 +3 -0
- data/Gemfile.cucumber-1.3.lock +4 -1
- data/Gemfile.cucumber-2.4 +3 -0
- data/Gemfile.cucumber-2.4.lock +4 -1
- data/Gemfile.cucumber-3.0 +3 -0
- data/Gemfile.cucumber-3.0.lock +4 -1
- data/Gemfile.cucumber-3.1 +3 -0
- data/Gemfile.cucumber-3.1.lock +4 -1
- data/README.md +1 -1
- data/lib/cucumber_factory/build_strategy.rb +94 -48
- data/lib/cucumber_factory/factory.rb +22 -9
- data/lib/cucumber_factory/version.rb +1 -1
- data/spec/cucumber_factory/factory/build_strategy_spec.rb +12 -14
- data/spec/cucumber_factory/steps_spec.rb +417 -405
- data/spec/spec_helper.rb +3 -0
- data/spec/support/database.rb +6 -0
- data/spec/support/factories/factories.rb +32 -0
- data/spec/support/models/job_offer.rb +1 -3
- data/spec/support/models/opera.rb +2 -3
- metadata +4 -4
- data/spec/support/factory_bot_mock.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3153238ae9f4c30fd21e6dd493621e361d4bc6b508521effc93fbb4cb4be8af2
|
4
|
+
data.tar.gz: c152087fd0231027a94abf15f2abff5c3b3857c714212f3c1eefa67902b32b3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48b39f6ebaedb5bca521d07579384cbcffea01eb4638787de026ce683b1db2abc70fd91227f272cd88a75cb3a15cd8e780989b99a3429313aa6f9e0f6a8575c9
|
7
|
+
data.tar.gz: 5b16cb2d4e73c1fde061b12e31a23944a3d6889316002805cd0f0bb7d0355cf520d01cb587c5a0ab58648633f83189ee2bba9597b2bda7217315db5fc48ca218
|
data/CHANGELOG.md
CHANGED
@@ -13,6 +13,19 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
|
|
13
13
|
|
14
14
|
### Compatible changes
|
15
15
|
|
16
|
+
-
|
17
|
+
|
18
|
+
|
19
|
+
## 2.1.0 - 2020-03-09
|
20
|
+
|
21
|
+
### Compatible changes
|
22
|
+
|
23
|
+
- Allow associations to be set for [transient attributes](https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#transient-attributes) if they are named after the model. For example, when there is a `Role` model and the`user` factory has a transient attribute `role`, the following steps are now valid:
|
24
|
+
```
|
25
|
+
Given there is a role
|
26
|
+
And there is a user with the role above
|
27
|
+
```
|
28
|
+
|
16
29
|
## 2.0.2 - 2020-03-26
|
17
30
|
|
18
31
|
### Compatible changes
|
data/Gemfile.cucumber-1.3
CHANGED
data/Gemfile.cucumber-1.3.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cucumber_factory (2.0
|
4
|
+
cucumber_factory (2.1.0)
|
5
5
|
activerecord
|
6
6
|
activesupport
|
7
7
|
cucumber
|
@@ -35,6 +35,8 @@ GEM
|
|
35
35
|
cucumber
|
36
36
|
database_cleaner (1.0.1)
|
37
37
|
diff-lcs (1.3)
|
38
|
+
factory_bot (4.11.1)
|
39
|
+
activesupport (>= 3.0.0)
|
38
40
|
gemika (0.4.0)
|
39
41
|
gherkin (2.12.2)
|
40
42
|
multi_json (~> 1.3)
|
@@ -71,6 +73,7 @@ DEPENDENCIES
|
|
71
73
|
cucumber (~> 1.3.20)
|
72
74
|
cucumber_factory!
|
73
75
|
database_cleaner (~> 1.0.0)
|
76
|
+
factory_bot (< 5)
|
74
77
|
gemika
|
75
78
|
mysql2
|
76
79
|
rake (>= 10.0.4)
|
data/Gemfile.cucumber-2.4
CHANGED
data/Gemfile.cucumber-2.4.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cucumber_factory (2.0
|
4
|
+
cucumber_factory (2.1.0)
|
5
5
|
activerecord
|
6
6
|
activesupport
|
7
7
|
cucumber
|
@@ -40,6 +40,8 @@ GEM
|
|
40
40
|
cucumber
|
41
41
|
database_cleaner (1.6.2)
|
42
42
|
diff-lcs (1.3)
|
43
|
+
factory_bot (4.11.1)
|
44
|
+
activesupport (>= 3.0.0)
|
43
45
|
gemika (0.3.2)
|
44
46
|
gherkin (4.1.3)
|
45
47
|
i18n (0.9.5)
|
@@ -75,6 +77,7 @@ DEPENDENCIES
|
|
75
77
|
cucumber (~> 2.4.0)
|
76
78
|
cucumber_factory!
|
77
79
|
database_cleaner
|
80
|
+
factory_bot (< 5)
|
78
81
|
gemika
|
79
82
|
mysql2
|
80
83
|
rake
|
data/Gemfile.cucumber-3.0
CHANGED
data/Gemfile.cucumber-3.0.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cucumber_factory (2.0
|
4
|
+
cucumber_factory (2.1.0)
|
5
5
|
activerecord
|
6
6
|
activesupport
|
7
7
|
cucumber
|
@@ -45,6 +45,8 @@ GEM
|
|
45
45
|
cucumber
|
46
46
|
database_cleaner (1.6.2)
|
47
47
|
diff-lcs (1.3)
|
48
|
+
factory_bot (5.1.1)
|
49
|
+
activesupport (>= 4.2.0)
|
48
50
|
gemika (0.3.2)
|
49
51
|
gherkin (4.1.3)
|
50
52
|
i18n (0.9.5)
|
@@ -80,6 +82,7 @@ DEPENDENCIES
|
|
80
82
|
cucumber (~> 3.0.0)
|
81
83
|
cucumber_factory!
|
82
84
|
database_cleaner
|
85
|
+
factory_bot
|
83
86
|
gemika
|
84
87
|
mysql2
|
85
88
|
rake
|
data/Gemfile.cucumber-3.1
CHANGED
data/Gemfile.cucumber-3.1.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cucumber_factory (2.0
|
4
|
+
cucumber_factory (2.1.0)
|
5
5
|
activerecord
|
6
6
|
activesupport
|
7
7
|
cucumber
|
@@ -45,6 +45,8 @@ GEM
|
|
45
45
|
cucumber
|
46
46
|
database_cleaner (1.6.2)
|
47
47
|
diff-lcs (1.3)
|
48
|
+
factory_bot (5.1.1)
|
49
|
+
activesupport (>= 4.2.0)
|
48
50
|
gemika (0.3.2)
|
49
51
|
gherkin (5.0.0)
|
50
52
|
i18n (0.9.5)
|
@@ -80,6 +82,7 @@ DEPENDENCIES
|
|
80
82
|
cucumber (~> 3.1.0)
|
81
83
|
cucumber_factory!
|
82
84
|
database_cleaner
|
85
|
+
factory_bot
|
83
86
|
gemika
|
84
87
|
mysql2
|
85
88
|
rake
|
data/README.md
CHANGED
@@ -65,7 +65,7 @@ Given there is a movie with these attributes:
|
|
65
65
|
Setting associations
|
66
66
|
--------------------
|
67
67
|
|
68
|
-
You can set `belongs_to` associations by referring to the last created record of as `above`:
|
68
|
+
You can set `belongs_to` and `transient` associations by referring to the last created record of as `above`:
|
69
69
|
|
70
70
|
```cucumber
|
71
71
|
Given there is a movie with the title "Before Sunrise"
|
@@ -7,79 +7,110 @@ module CucumberFactory
|
|
7
7
|
class << self
|
8
8
|
|
9
9
|
def from_prose(model_prose, variant_prose)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
variants = variants_from_prose(variant_prose)
|
11
|
+
factory = factory_bot_factory(model_prose, variants)
|
12
|
+
|
13
|
+
if factory
|
14
|
+
strategy = factory_bot_strategy(factory, model_prose, variants)
|
15
|
+
transient_attributes = factory_bot_transient_attributes(factory, variants)
|
15
16
|
else
|
16
|
-
|
17
|
+
strategy = alternative_strategy(model_prose, variants)
|
18
|
+
transient_attributes = []
|
17
19
|
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
+
[strategy, transient_attributes]
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def variants_from_prose(variant_prose)
|
27
|
+
if variant_prose.present?
|
28
|
+
variants = /\((.*?)\)/.match(variant_prose)[1].split(/\s*,\s*/)
|
29
|
+
variants.collect { |variant| variant.downcase.gsub(" ", "_").to_sym }
|
21
30
|
else
|
22
|
-
|
23
|
-
machinist_strategy(model_class, variants) ||
|
24
|
-
active_record_strategy(model_class) ||
|
25
|
-
ruby_object_strategy(model_class)
|
31
|
+
[]
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
29
|
-
|
35
|
+
def factory_bot_factory(model_prose, variants)
|
36
|
+
return unless factory_bot_class
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
factory_class ||= ::FactoryGirl if defined?(FactoryGirl)
|
34
|
-
return unless factory_class
|
38
|
+
factory_name = factory_name_from_prose(model_prose)
|
39
|
+
factory = factory_bot_class.factories[factory_name]
|
35
40
|
|
36
|
-
|
37
|
-
|
41
|
+
if factory.nil? && variants.present?
|
42
|
+
factory = factory_bot_class.factories[variants[0]]
|
43
|
+
end
|
44
|
+
|
45
|
+
factory
|
46
|
+
end
|
38
47
|
|
39
|
-
|
48
|
+
def factory_bot_strategy(factory, model_prose, variants)
|
49
|
+
return unless factory
|
40
50
|
|
41
|
-
|
51
|
+
factory_name = factory_name_from_prose(model_prose)
|
52
|
+
if factory_bot_class.factories[factory_name].nil? && variants.present?
|
42
53
|
factory_name, *variants = variants
|
43
54
|
end
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
new(factory.build_class) do |attributes|
|
57
|
+
# Cannot have additional scalar args after a varargs
|
58
|
+
# argument in Ruby 1.8 and 1.9
|
59
|
+
args = []
|
60
|
+
args += variants
|
61
|
+
args << attributes
|
62
|
+
factory_bot_class.create(factory_name, *args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def factory_bot_transient_attributes(factory, variants)
|
67
|
+
return [] unless factory
|
68
|
+
|
69
|
+
factory_attributes = factory_bot_attributes(factory, variants)
|
70
|
+
class_attributes = factory.build_class.attribute_names.map(&:to_sym)
|
71
|
+
|
72
|
+
factory_attributes - class_attributes
|
73
|
+
end
|
74
|
+
|
75
|
+
def factory_bot_attributes(factory, variants)
|
76
|
+
traits = factory_bot_traits(factory, variants)
|
77
|
+
factory.with_traits(traits.map(&:name)).definition.attributes.names
|
78
|
+
end
|
79
|
+
|
80
|
+
def factory_bot_traits(factory, variants)
|
81
|
+
factory.definition.defined_traits.select do |trait|
|
82
|
+
variants.include?(trait.name.to_sym)
|
54
83
|
end
|
84
|
+
end
|
55
85
|
|
86
|
+
def alternative_strategy(model_prose, variants)
|
87
|
+
model_class = underscored_model_name(model_prose).camelize.constantize
|
88
|
+
machinist_strategy(model_class, variants) ||
|
89
|
+
active_record_strategy(model_class) ||
|
90
|
+
ruby_object_strategy(model_class)
|
56
91
|
end
|
57
92
|
|
58
93
|
def machinist_strategy(model_class, variants)
|
59
|
-
|
60
|
-
|
61
|
-
new(model_class) do |attributes|
|
62
|
-
if variants.present?
|
63
|
-
variants.size == 1 or raise 'Machinist only supports a single variant per blueprint'
|
64
|
-
model_class.make(variants.first.to_sym, attributes)
|
65
|
-
else
|
66
|
-
model_class.make(attributes)
|
67
|
-
end
|
68
|
-
end
|
94
|
+
return unless model_class.respond_to?(:make)
|
69
95
|
|
96
|
+
new(model_class) do |attributes|
|
97
|
+
if variants.present?
|
98
|
+
variants.size == 1 or raise 'Machinist only supports a single variant per blueprint'
|
99
|
+
model_class.make(variants.first, attributes)
|
100
|
+
else
|
101
|
+
model_class.make(attributes)
|
102
|
+
end
|
70
103
|
end
|
71
104
|
end
|
72
105
|
|
73
106
|
def active_record_strategy(model_class)
|
74
|
-
|
75
|
-
|
76
|
-
new(model_class) do |attributes|
|
77
|
-
model = model_class.new
|
78
|
-
CucumberFactory::Switcher.assign_attributes(model, attributes)
|
79
|
-
model.save!
|
80
|
-
model
|
81
|
-
end
|
107
|
+
return unless model_class.respond_to?(:create!)
|
82
108
|
|
109
|
+
new(model_class) do |attributes|
|
110
|
+
model = model_class.new
|
111
|
+
CucumberFactory::Switcher.assign_attributes(model, attributes)
|
112
|
+
model.save!
|
113
|
+
model
|
83
114
|
end
|
84
115
|
end
|
85
116
|
|
@@ -89,6 +120,21 @@ module CucumberFactory
|
|
89
120
|
end
|
90
121
|
end
|
91
122
|
|
123
|
+
def factory_bot_class
|
124
|
+
factory_class = ::FactoryBot if defined?(FactoryBot)
|
125
|
+
factory_class ||= ::FactoryGirl if defined?(FactoryGirl)
|
126
|
+
factory_class
|
127
|
+
end
|
128
|
+
|
129
|
+
def factory_name_from_prose(model_prose)
|
130
|
+
underscored_model_name(model_prose).to_s.underscore.gsub('/', '_').to_sym
|
131
|
+
end
|
132
|
+
|
133
|
+
def underscored_model_name(model_prose)
|
134
|
+
# don't use \w which depends on the system locale
|
135
|
+
model_prose.gsub(/[^A-Za-z0-9_\/]+/, "_")
|
136
|
+
end
|
137
|
+
|
92
138
|
end
|
93
139
|
|
94
140
|
|
@@ -100,7 +100,7 @@ module CucumberFactory
|
|
100
100
|
end
|
101
101
|
|
102
102
|
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)
|
103
|
+
build_strategy, transient_attributes = BuildStrategy.from_prose(raw_model, raw_variant)
|
104
104
|
model_class = build_strategy.model_class
|
105
105
|
attributes = {}
|
106
106
|
if raw_attributes.try(:strip).present?
|
@@ -108,7 +108,7 @@ module CucumberFactory
|
|
108
108
|
raw_attributes.scan(raw_attribute_fragment_regex).each do |fragment|
|
109
109
|
attribute = attribute_name_from_prose(fragment[0])
|
110
110
|
value = fragment[1]
|
111
|
-
attributes[attribute] = attribute_value(world, model_class, attribute, value)
|
111
|
+
attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
|
112
112
|
end
|
113
113
|
unused_raw_attributes = raw_attributes.gsub(raw_attribute_fragment_regex, '')
|
114
114
|
if unused_raw_attributes.present?
|
@@ -129,14 +129,14 @@ module CucumberFactory
|
|
129
129
|
raw_attribute, value = fragment.split(': ')
|
130
130
|
attribute = attribute_name_from_prose(raw_attribute)
|
131
131
|
value = "\"#{value}\"" unless matches_fully?(value, VALUE_ARRAY)
|
132
|
-
attributes[attribute] = attribute_value(world, model_class, attribute, value)
|
132
|
+
attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
|
133
133
|
end
|
134
134
|
# DataTable e.g. in raw [["first name", "Jane"], ["last name", "Jenny"]]
|
135
135
|
else
|
136
136
|
raw_multiline_attributes.raw.each do |raw_attribute, value|
|
137
137
|
attribute = attribute_name_from_prose(raw_attribute)
|
138
138
|
value = "\"#{value}\"" unless matches_fully?(value, VALUE_ARRAY)
|
139
|
-
attributes[attribute] = attribute_value(world, model_class, attribute, value)
|
139
|
+
attributes[attribute] = attribute_value(world, model_class, transient_attributes, attribute, value)
|
140
140
|
end
|
141
141
|
end
|
142
142
|
end
|
@@ -145,15 +145,15 @@ module CucumberFactory
|
|
145
145
|
record
|
146
146
|
end
|
147
147
|
|
148
|
-
def attribute_value(world, model_class, attribute, value)
|
149
|
-
|
148
|
+
def attribute_value(world, model_class, transient_attributes, attribute, value)
|
149
|
+
association_class = resolve_association_class(attribute, model_class, transient_attributes)
|
150
150
|
|
151
151
|
if matches_fully?(value, VALUE_ARRAY)
|
152
152
|
elements_str = unquote(value)
|
153
|
-
value = elements_str.scan(VALUE_SCALAR).map { |v| attribute_value(world, model_class, attribute, v) }
|
154
|
-
elsif
|
153
|
+
value = elements_str.scan(VALUE_SCALAR).map { |v| attribute_value(world, model_class, transient_attributes, attribute, v) }
|
154
|
+
elsif association_class.present?
|
155
155
|
if matches_fully?(value, VALUE_LAST_RECORD)
|
156
|
-
value = CucumberFactory::Switcher.find_last(
|
156
|
+
value = CucumberFactory::Switcher.find_last(association_class) or raise Error, "There is no last #{attribute}"
|
157
157
|
elsif matches_fully?(value, VALUE_STRING)
|
158
158
|
value = unquote(value)
|
159
159
|
value = get_named_record(world, value) || transform_value(world, value)
|
@@ -169,6 +169,19 @@ module CucumberFactory
|
|
169
169
|
value
|
170
170
|
end
|
171
171
|
|
172
|
+
def resolve_association_class(attribute, model_class, transient_attributes)
|
173
|
+
return unless model_class.respond_to?(:reflect_on_association)
|
174
|
+
|
175
|
+
klass = if model_class.reflect_on_association(attribute)
|
176
|
+
model_class.reflect_on_association(attribute).klass
|
177
|
+
elsif transient_attributes.include?(attribute.to_sym)
|
178
|
+
klass_name = attribute.to_s.camelize
|
179
|
+
klass_name.constantize if Object.const_defined?(klass_name)
|
180
|
+
else
|
181
|
+
nil
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
172
185
|
def resolve_scalar_value(world, model_class, attribute, value)
|
173
186
|
if matches_fully?(value, VALUE_STRING)
|
174
187
|
value = unquote(value)
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
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
|