cucumber_factory 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|