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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eeed19e5c878629c3f87a038821a955401c40c19ca323577d7e84e3ae019f764
4
- data.tar.gz: 2d3b57e9b739be6e69d28240c7553eec3882b2e200ca64c402a8cc646bcdc169
3
+ metadata.gz: 3153238ae9f4c30fd21e6dd493621e361d4bc6b508521effc93fbb4cb4be8af2
4
+ data.tar.gz: c152087fd0231027a94abf15f2abff5c3b3857c714212f3c1eefa67902b32b3e
5
5
  SHA512:
6
- metadata.gz: 752a2cbe68fa53254741a792770ab0252f320a218fb3c39fe85679bcf6d8e1289fdee354042bb342d96789266c6f8ac02819393ee375d628def84679de281cad
7
- data.tar.gz: aa2eb84a898e7d52f10b7272ad8f7c04a9d1dea4096d0a7fab04d9158a887f46839e7d36b83f9aec945b13b18f0b4550de19fe9df0897ccc1235f22f990b2b59
6
+ metadata.gz: 48b39f6ebaedb5bca521d07579384cbcffea01eb4638787de026ce683b1db2abc70fd91227f272cd88a75cb3a15cd8e780989b99a3429313aa6f9e0f6a8575c9
7
+ data.tar.gz: 5b16cb2d4e73c1fde061b12e31a23944a3d6889316002805cd0f0bb7d0355cf520d01cb587c5a0ab58648633f83189ee2bba9597b2bda7217315db5fc48ca218
@@ -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
@@ -12,5 +12,8 @@ gem 'rake', '>=10.0.4'
12
12
  gem 'database_cleaner', '~>1.0.0'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot', '< 5'
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
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)
@@ -12,5 +12,8 @@ gem 'rake'
12
12
  gem 'database_cleaner'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot', '< 5' # 5.0 requires Ruby >= 2.3
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
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
@@ -12,5 +12,8 @@ gem 'rake'
12
12
  gem 'database_cleaner'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot'
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
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
@@ -12,5 +12,8 @@ gem 'rake'
12
12
  gem 'database_cleaner'
13
13
  gem 'gemika'
14
14
 
15
+ # Test dependencies
16
+ gem 'factory_bot'
17
+
15
18
  # Gem under test
16
19
  gem 'cucumber_factory', :path => '.'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cucumber_factory (2.0.2)
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
- # don't use \w which depends on the system locale
11
- underscored_model_name = model_prose.gsub(/[^A-Za-z0-9_\/]+/, "_")
12
- if variant_prose.present?
13
- variants = /\((.*?)\)/.match(variant_prose)[1].split(/\s*,\s*/)
14
- variants = variants.collect { |variant| variant.downcase.gsub(" ", "_") }
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
- variants = []
17
+ strategy = alternative_strategy(model_prose, variants)
18
+ transient_attributes = []
17
19
  end
18
20
 
19
- if factory_bot_strategy = factory_bot_strategy(underscored_model_name, variants)
20
- factory_bot_strategy
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
- model_class = underscored_model_name.camelize.constantize
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
- private
35
+ def factory_bot_factory(model_prose, variants)
36
+ return unless factory_bot_class
30
37
 
31
- def factory_bot_strategy(factory_name, variants)
32
- factory_class = ::FactoryBot if defined?(FactoryBot)
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
- variants = variants.map(&:to_sym)
37
- factory_name = factory_name.to_s.underscore.gsub('/', '_').to_sym
41
+ if factory.nil? && variants.present?
42
+ factory = factory_bot_class.factories[variants[0]]
43
+ end
44
+
45
+ factory
46
+ end
38
47
 
39
- factory = factory_class.factories[factory_name]
48
+ def factory_bot_strategy(factory, model_prose, variants)
49
+ return unless factory
40
50
 
41
- if factory.nil? && variants.present? && factory = factory_class.factories[variants[0]]
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
- if factory
46
- new(factory.build_class) do |attributes|
47
- # Cannot have additional scalar args after a varargs
48
- # argument in Ruby 1.8 and 1.9
49
- args = []
50
- args += variants
51
- args << attributes
52
- factory_class.create(factory_name, *args)
53
- end
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
- if model_class.respond_to?(:make)
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
- if model_class.respond_to?(:create!)
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
- association = model_class.respond_to?(:reflect_on_association) ? model_class.reflect_on_association(attribute) : nil
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 association.present?
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(association.klass) or raise Error, "There is no last #{attribute}"
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)
@@ -1,3 +1,3 @@
1
1
  module CucumberFactory
2
- VERSION = '2.0.2'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -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
- it 'returns a strategy corresponding to the factories model' do
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
- FactoryBot.stub_factories :job_offer => JobOffer
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
- subject.from_prose("movie", nil).model_class.should == Movie
35
- subject.from_prose("job offer", nil).model_class.should == JobOffer
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
- subject.from_prose("movie", "(job offer)").model_class.should == Movie
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
- subject.from_prose("people/actor", nil).model_class.should == People::Actor
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