shoulda-matchers 2.6.0 → 2.6.1.rc1

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.
Files changed (52) hide show
  1. data/Gemfile.lock +1 -1
  2. data/NEWS.md +34 -0
  3. data/README.md +14 -0
  4. data/features/activemodel_integration.feature +15 -0
  5. data/features/step_definitions/activemodel_steps.rb +21 -0
  6. data/gemfiles/3.0.gemfile.lock +1 -1
  7. data/gemfiles/3.1.gemfile.lock +1 -1
  8. data/gemfiles/3.2.gemfile.lock +1 -1
  9. data/gemfiles/4.0.0.gemfile.lock +1 -1
  10. data/gemfiles/4.0.1.gemfile.lock +1 -1
  11. data/gemfiles/4.1.gemfile.lock +1 -1
  12. data/lib/shoulda/matchers.rb +1 -0
  13. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +11 -6
  14. data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +59 -95
  15. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -18
  16. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +10 -0
  17. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +60 -18
  18. data/lib/shoulda/matchers/active_model/errors.rb +9 -7
  19. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +4 -0
  20. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +24 -5
  21. data/lib/shoulda/matchers/doublespeak.rb +27 -0
  22. data/lib/shoulda/matchers/doublespeak/double.rb +74 -0
  23. data/lib/shoulda/matchers/doublespeak/double_collection.rb +54 -0
  24. data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +27 -0
  25. data/lib/shoulda/matchers/doublespeak/object_double.rb +32 -0
  26. data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +30 -0
  27. data/lib/shoulda/matchers/doublespeak/structs.rb +8 -0
  28. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +34 -0
  29. data/lib/shoulda/matchers/doublespeak/world.rb +38 -0
  30. data/lib/shoulda/matchers/independent/delegate_matcher.rb +112 -61
  31. data/lib/shoulda/matchers/integrations/test_unit.rb +8 -6
  32. data/lib/shoulda/matchers/rails_shim.rb +16 -0
  33. data/lib/shoulda/matchers/version.rb +1 -1
  34. data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +22 -19
  35. data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +174 -65
  36. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +14 -0
  37. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +553 -211
  38. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +6 -0
  39. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +22 -0
  40. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +23 -4
  41. data/spec/shoulda/matchers/doublespeak/double_collection_spec.rb +102 -0
  42. data/spec/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +21 -0
  43. data/spec/shoulda/matchers/doublespeak/double_spec.rb +144 -0
  44. data/spec/shoulda/matchers/doublespeak/object_double_spec.rb +77 -0
  45. data/spec/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +40 -0
  46. data/spec/shoulda/matchers/doublespeak/stub_implementation_spec.rb +88 -0
  47. data/spec/shoulda/matchers/doublespeak/world_spec.rb +88 -0
  48. data/spec/shoulda/matchers/doublespeak_spec.rb +19 -0
  49. data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +105 -39
  50. data/spec/support/controller_builder.rb +18 -9
  51. data/spec/support/rails_versions.rb +4 -0
  52. metadata +34 -8
@@ -1,320 +1,662 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Shoulda::Matchers::ActiveModel::EnsureInclusionOfMatcher do
4
- context 'with no validations' do
5
- it 'rejects an array which does not have a validator defined' do
6
- expect(define_model(:example, attr: :string).new).
7
- not_to ensure_inclusion_of(:attr).in_array(%w(Yes No))
4
+ shared_context 'for a generic attribute' do
5
+ def self.testing_values_of_option(option_name, &block)
6
+ [nil, true, false].each do |option_value|
7
+ context_name = "+ #{option_name}"
8
+ option_args = []
9
+ matches_or_not = ['matches', 'does not match']
10
+ to_or_not_to = [:to, :not_to]
11
+
12
+ unless option_value == nil
13
+ context_name << "(#{option_value})"
14
+ option_args = [option_value]
15
+ end
16
+
17
+ if option_value == false
18
+ matches_or_not.reverse!
19
+ to_or_not_to.reverse!
20
+ end
21
+
22
+ end
8
23
  end
9
- end
10
24
 
11
- context 'with an integer column' do
12
- it 'can verify a zero in the array' do
13
- model = define_model(:example, attr: :integer) do
14
- validates_inclusion_of :attr, in: [0, 1, 2]
15
- end.new
25
+ context 'against an integer attribute' do
26
+ it_behaves_like 'it supports in_array',
27
+ possible_values: (1..5).to_a,
28
+ zero: 0,
29
+ reserved_outside_value: described_class::ARBITRARY_OUTSIDE_FIXNUM
30
+
31
+ it_behaves_like 'it supports in_range',
32
+ possible_values: 1..5,
33
+ zero: 0
34
+
35
+ context 'when attribute validates a range of values via custom validation' do
36
+ it 'matches ensuring the correct range and messages' do
37
+ expect_to_match_ensuring_range_and_messages(2..5, 2, 5)
38
+ expect_to_match_ensuring_range_and_messages(2...5, 2, 4)
39
+ end
40
+ end
41
+
42
+ def build_object(options = {}, &block)
43
+ build_object_with_generic_attribute(
44
+ options.merge(column_type: :integer, value: 1),
45
+ &block
46
+ )
47
+ end
16
48
 
17
- expect(model).to ensure_inclusion_of(:attr).in_array([0,1,2])
49
+ def add_outside_value_to(values)
50
+ values + [values.last + 1]
51
+ end
18
52
  end
19
- end
20
53
 
21
- context 'with an decimal column' do
22
- it 'can verify decimal values' do
23
- model = define_model(:example, attr: :decimal) do
24
- validates_inclusion_of :attr, in: [0.0, 0.1]
25
- end.new
54
+ context "against a float attribute" do
55
+ it_behaves_like 'it supports in_array',
56
+ possible_values: [1.0, 2.0, 3.0, 4.0, 5.0],
57
+ zero: 0.0,
58
+ reserved_outside_value: described_class::ARBITRARY_OUTSIDE_FIXNUM
59
+
60
+ it_behaves_like 'it supports in_range',
61
+ possible_values: 1.0..5.0,
62
+ zero: 0.0
63
+
64
+ def build_object(options = {}, &block)
65
+ build_object_with_generic_attribute(
66
+ options.merge(column_type: :float, value: 1.0),
67
+ &block
68
+ )
69
+ end
26
70
 
27
- expect(model).to ensure_inclusion_of(:attr).in_array([0.0, 0.1])
71
+ def add_outside_value_to(values)
72
+ values + [values.last + 1]
73
+ end
74
+ end
75
+
76
+ context "against a decimal attribute" do
77
+ it_behaves_like 'it supports in_array',
78
+ possible_values: [1.0, 2.0, 3.0, 4.0, 5.0].map { |number|
79
+ BigDecimal.new(number.to_s)
80
+ },
81
+ zero: BigDecimal.new('0.0'),
82
+ reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DECIMAL
83
+
84
+ it_behaves_like 'it supports in_range',
85
+ possible_values: BigDecimal.new('1.0') .. BigDecimal.new('5.0'),
86
+ zero: BigDecimal.new('0.0')
87
+
88
+ def build_object(options = {}, &block)
89
+ build_object_with_generic_attribute(
90
+ options.merge(column_type: :decimal, value: BigDecimal.new('1.0')),
91
+ &block
92
+ )
93
+ end
94
+
95
+ def add_outside_value_to(values)
96
+ values + [values.last + 1]
97
+ end
98
+ end
99
+
100
+ context 'against a string attribute' do
101
+ it_behaves_like 'it supports in_array',
102
+ possible_values: %w(foo bar baz),
103
+ reserved_outside_value: described_class::ARBITRARY_OUTSIDE_STRING
104
+
105
+ def build_object(options = {}, &block)
106
+ build_object_with_generic_attribute(
107
+ options.merge(column_type: :string),
108
+ &block
109
+ )
110
+ end
111
+
112
+ def add_outside_value_to(values)
113
+ values + %w(qux)
114
+ end
28
115
  end
29
116
  end
30
117
 
31
- context 'with true/false values' do
32
- it 'can verify outside values to ensure the negative case' do
33
- expect(define_model(:example, attr: :string).new).
34
- not_to ensure_inclusion_of(:attr).in_array([true, false])
118
+ shared_examples_for 'it supports allow_nil' do |args|
119
+ valid_values = args.fetch(:valid_values)
120
+
121
+ testing_values_of_option 'allow_nil' do |option_args, matches_or_not, to_or_not_to|
122
+ it "#{matches_or_not[0]} when the validation specifies allow_nil" do
123
+ builder = build_object_allowing(valid_values, allow_nil: true)
124
+
125
+ __send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
126
+ matcher.allow_nil(*option_args)
127
+ end
128
+ end
129
+
130
+ it "#{matches_or_not[1]} when the validation does not specify allow_nil" do
131
+ builder = build_object_allowing(valid_values)
132
+
133
+ __send__("expect_#{to_or_not_to[1]}_match_on_values", builder, valid_values) do |matcher|
134
+ matcher.allow_nil(*option_args)
135
+ end
136
+ end
35
137
  end
36
138
  end
37
139
 
38
- context 'where we cannot determine a value outside the array' do
39
- it 'raises a custom exception' do
40
- model = define_model(:example, attr: :string).new
140
+ shared_examples_for 'it supports allow_blank' do |args|
141
+ valid_values = args.fetch(:valid_values)
142
+
143
+ testing_values_of_option 'allow_blank' do |option_args, matches_or_not, to_or_not_to|
144
+ it "#{matches_or_not[0]} when the validation specifies allow_blank" do
145
+ builder = build_object_allowing(valid_values, allow_blank: true)
146
+
147
+ __send__("expect_#{to_or_not_to[0]}_match_on_values", builder, valid_values) do |matcher|
148
+ matcher.allow_blank(*option_args)
149
+ end
150
+ end
151
+
152
+ it "#{matches_or_not[1]} when the validation does not specify allow_blank" do
153
+ builder = build_object_allowing(valid_values)
41
154
 
42
- arbitrary_string = described_class::ARBITRARY_OUTSIDE_STRING
43
- expect { expect(model).to ensure_inclusion_of(:attr).in_array([arbitrary_string]) }.to raise_error Shoulda::Matchers::ActiveModel::CouldNotDetermineValueOutsideOfArray
155
+ __send__("expect_#{to_or_not_to[1]}_match_on_values", builder, valid_values) do |matcher|
156
+ matcher.allow_blank(*option_args)
157
+ end
158
+ end
44
159
  end
45
160
  end
46
161
 
47
- context 'an attribute which must be included in a range' do
48
- it 'accepts ensuring the correct range' do
49
- expect(validating_inclusion(in: 2..5)).
50
- to ensure_inclusion_of(:attr).in_range(2..5)
162
+ shared_examples_for 'it supports with_message' do |args|
163
+ valid_values = args.fetch(:valid_values)
164
+
165
+ context 'given a string' do
166
+ it 'matches when validation uses given message' do
167
+ builder = build_object_allowing(valid_values, message: 'a message')
168
+
169
+ expect_to_match_on_values(builder, valid_values) do |matcher|
170
+ matcher.with_message('a message')
171
+ end
172
+ end
173
+
174
+ it 'does not match when validation uses the default message instead of given message' do
175
+ pending 'does not work'
176
+
177
+ builder = build_object_allowing(valid_values)
178
+
179
+ expect_not_to_match_on_values(builder, valid_values) do |matcher|
180
+ matcher.with_message('a message')
181
+ end
182
+ end
183
+
184
+ it 'does not match when validation uses a message but it is not same as given' do
185
+ pending 'does not work'
186
+
187
+ builder = build_object_allowing(valid_values, message: 'a different message')
188
+
189
+ expect_not_to_match_on_values(builder, valid_values) do |matcher|
190
+ matcher.with_message('a message')
191
+ end
192
+ end
51
193
  end
52
194
 
53
- it 'rejects ensuring a lower minimum value' do
54
- expect(validating_inclusion(in: 2..5)).
55
- not_to ensure_inclusion_of(:attr).in_range(1..5)
195
+ context 'given a regex' do
196
+ it 'matches when validation uses a message that matches the regex' do
197
+ builder = build_object_allowing(valid_values, message: 'this is a message')
198
+
199
+ expect_to_match_on_values(builder, valid_values) do |matcher|
200
+ matcher.with_message(/a message/)
201
+ end
202
+ end
203
+
204
+ it 'does not match when validation uses the default message instead of given message' do
205
+ pending 'does not work'
206
+
207
+ builder = build_object_allowing(valid_values)
208
+
209
+ expect_not_to_match_on_values(builder, valid_values) do |matcher|
210
+ matcher.with_message(/a message/)
211
+ end
212
+ end
213
+
214
+ it 'does not match when validation uses a message but it does not match regex' do
215
+ pending 'does not work'
216
+
217
+ builder = build_object_allowing(valid_values, message: 'a different message')
218
+
219
+ expect_not_to_match_on_values(builder, valid_values) do |matcher|
220
+ matcher.with_message(/a message/)
221
+ end
222
+ end
56
223
  end
57
224
 
58
- it 'rejects ensuring a higher minimum value' do
59
- expect(validating_inclusion(in: 2..5)).
60
- not_to ensure_inclusion_of(:attr).in_range(3..5)
225
+ context 'given nil' do
226
+ it 'is as if with_message had never been called' do
227
+ builder = build_object_allowing(valid_values)
228
+
229
+ expect_to_match_on_values(builder, valid_values) do |matcher|
230
+ matcher.with_message(nil)
231
+ end
232
+ end
61
233
  end
234
+ end
62
235
 
63
- it 'rejects ensuring a lower maximum value' do
64
- expect(validating_inclusion(in: 2..5)).
65
- not_to ensure_inclusion_of(:attr).in_range(2..4)
236
+ shared_examples_for 'it supports in_array' do |args|
237
+ possible_values = args.fetch(:possible_values)
238
+ zero = args[:zero]
239
+ reserved_outside_value = args.fetch(:reserved_outside_value)
240
+
241
+ it 'does not match a record with no validations' do
242
+ builder = build_object
243
+ expect_not_to_match_on_values(builder, possible_values)
66
244
  end
67
245
 
68
- it 'rejects ensuring a higher maximum value' do
69
- expect(validating_inclusion(in: 2..5)).
70
- not_to ensure_inclusion_of(:attr).in_range(2..6)
246
+ it 'matches given the same array of valid values' do
247
+ builder = build_object_allowing(possible_values)
248
+ expect_to_match_on_values(builder, possible_values)
71
249
  end
72
250
 
73
- it 'does not override the default message with a blank' do
74
- expect(validating_inclusion(in: 2..5)).
75
- to ensure_inclusion_of(:attr).in_range(2..5).with_message(nil)
251
+ it 'matches given a subset of the valid values' do
252
+ builder = build_object_allowing(possible_values)
253
+ expect_to_match_on_values(builder, possible_values[1..-1])
76
254
  end
77
- end
78
255
 
79
- context 'an attribute which must be included in a range with excluded end' do
80
- it 'accepts ensuring the correct range' do
81
- expect(validating_inclusion(in: 2...5)).
82
- to ensure_inclusion_of(:attr).in_range(2...5)
256
+ if zero
257
+ it 'matches when one of the given values is a 0' do
258
+ valid_values = possible_values + [zero]
259
+ builder = build_object_allowing(valid_values)
260
+ expect_to_match_on_values(builder, valid_values)
261
+ end
83
262
  end
84
263
 
85
- it 'rejects ensuring a lower maximum value' do
86
- expect(validating_inclusion(in: 2...5)).
87
- not_to ensure_inclusion_of(:attr).in_range(2...4)
264
+ it 'does not match when one of the given values is invalid' do
265
+ builder = build_object_allowing(possible_values)
266
+ expect_not_to_match_on_values(builder, add_outside_value_to(possible_values))
88
267
  end
89
- end
90
268
 
91
- context 'an attribute with a custom ranged value validation' do
92
- it 'accepts ensuring the correct range' do
93
- expect(validating_inclusion(in: 2..4, message: 'not good')).
94
- to ensure_inclusion_of(:attr).in_range(2..4).with_message(/not good/)
269
+ it 'raises an error when valid and given value is our test outside value' do
270
+ error_class = Shoulda::Matchers::ActiveModel::CouldNotDetermineValueOutsideOfArray
271
+ builder = build_object_allowing([reserved_outside_value])
272
+
273
+ expect { expect_to_match_on_values(builder, [reserved_outside_value]) }.
274
+ to raise_error(error_class)
95
275
  end
96
- end
97
276
 
98
- context 'an attribute with custom range validations' do
99
- it 'accepts ensuring the correct range and messages' do
100
- model = custom_validation do
101
- if attr < 2
102
- errors.add(:attr, 'too low')
103
- elsif attr > 5
104
- errors.add(:attr, 'too high')
277
+ it_behaves_like 'it supports allow_nil', valid_values: possible_values
278
+ it_behaves_like 'it supports allow_blank', valid_values: possible_values
279
+ it_behaves_like 'it supports with_message', valid_values: possible_values
280
+
281
+ if active_model_3_2?
282
+ context '+ strict' do
283
+ context 'when the validation specifies strict' do
284
+ it 'matches when the given values match the valid values' do
285
+ builder = build_object_allowing(possible_values, strict: true)
286
+
287
+ expect_to_match_on_values(builder, possible_values) do |matcher|
288
+ matcher.strict
289
+ end
290
+ end
291
+
292
+ it 'does not match when the given values do not match the valid values' do
293
+ builder = build_object_allowing(possible_values, strict: true)
294
+
295
+ values = add_outside_value_to(possible_values)
296
+ expect_not_to_match_on_values(builder, values) do |matcher|
297
+ matcher.strict
298
+ end
299
+ end
105
300
  end
106
- end
107
301
 
108
- expect(model).to ensure_inclusion_of(:attr).in_range(2..5).
109
- with_low_message(/low/).with_high_message(/high/)
302
+ context 'when the validation does not specify strict' do
303
+ it 'does not match' do
304
+ builder = build_object_allowing(possible_values)
110
305
 
111
- model = custom_validation do
112
- if attr < 2
113
- errors.add(:attr, 'too low')
114
- elsif attr > 4
115
- errors.add(:attr, 'too high')
306
+ expect_not_to_match_on_values(builder, possible_values) do |matcher|
307
+ matcher.strict
308
+ end
309
+ end
116
310
  end
117
311
  end
118
-
119
- expect(model).to ensure_inclusion_of(:attr).in_range(2...5).
120
- with_low_message(/low/).with_high_message(/high/)
121
312
  end
122
- end
123
313
 
124
- context 'an attribute which must be included in an array' do
125
- it 'accepts with correct array' do
126
- expect(validating_inclusion(in: %w(one two))).
127
- to ensure_inclusion_of(:attr).in_array(%w(one two))
314
+ def expect_to_match_on_values(builder, values, &block)
315
+ expect_to_match_in_array(builder, values, &block)
128
316
  end
129
317
 
130
- it 'rejects when only part of array matches' do
131
- expect(validating_inclusion(in: %w(one two))).
132
- not_to ensure_inclusion_of(:attr).in_array(%w(one wrong_value))
318
+ def expect_not_to_match_on_values(builder, values, &block)
319
+ expect_not_to_match_in_array(builder, values, &block)
133
320
  end
321
+ end
134
322
 
135
- it 'rejects when array does not match at all' do
136
- expect(validating_inclusion(in: %w(one two))).
137
- not_to ensure_inclusion_of(:attr).in_array(%w(cat dog))
138
- end
323
+ shared_examples_for 'it supports in_range' do |args|
324
+ possible_values = args[:possible_values]
139
325
 
140
- it 'has correct description' do
141
- expect(ensure_inclusion_of(:attr).in_array([true, "dog"]).description).
142
- to eq 'ensure inclusion of attr in [true, "dog"]'
326
+ it 'does not match a record with no validations' do
327
+ builder = build_object
328
+ expect_not_to_match_on_values(builder, possible_values)
143
329
  end
144
330
 
145
- it 'rejects allow_blank' do
146
- expect(validating_inclusion(in: %w(one two))).
147
- not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank(true)
331
+ it 'matches given a range that exactly matches the valid range' do
332
+ builder = build_object_allowing(possible_values)
333
+ expect_to_match_on_values(builder, possible_values)
148
334
  end
149
335
 
150
- it 'accepts allow_blank(false)' do
151
- expect(validating_inclusion(in: %w(one two))).
152
- to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank(false)
336
+ it 'does not match given a range whose start value falls outside valid range' do
337
+ builder = build_object_allowing(possible_values)
338
+ expect_not_to_match_on_values(builder,
339
+ Range.new(possible_values.first - 1, possible_values.last)
340
+ )
153
341
  end
154
342
 
155
- it 'rejects allow_nil' do
156
- expect(validating_inclusion(in: %w(one two))).
157
- not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(true)
343
+ it 'does not match given a range whose start value falls inside valid range' do
344
+ builder = build_object_allowing(possible_values)
345
+ expect_not_to_match_on_values(builder,
346
+ Range.new(possible_values.first + 1, possible_values.last)
347
+ )
158
348
  end
159
349
 
160
- it 'accepts allow_nil(false)' do
161
- expect(validating_inclusion(in: %w(one two))).
162
- to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(false)
350
+ it 'does not match given a range whose end value falls inside valid range' do
351
+ builder = build_object_allowing(possible_values)
352
+ expect_not_to_match_on_values(builder,
353
+ Range.new(possible_values.first, possible_values.last - 1)
354
+ )
163
355
  end
164
- end
165
356
 
166
- context 'with allowed blank and allowed nil' do
167
- it 'accepts allow_blank' do
168
- expect(validating_inclusion(in: %w(one two), allow_blank: true)).
169
- to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank
357
+ it 'does not match given a range whose end value falls outside valid range' do
358
+ builder = build_object_allowing(possible_values)
359
+ expect_not_to_match_on_values(builder,
360
+ Range.new(possible_values.first, possible_values.last + 1)
361
+ )
170
362
  end
171
363
 
172
- it 'rejects allow_blank(false)' do
173
- expect(validating_inclusion(in: %w(one two), allow_blank: true)).
174
- not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_blank(false)
175
- end
364
+ it_behaves_like 'it supports allow_nil', valid_values: possible_values
365
+ it_behaves_like 'it supports allow_blank', valid_values: possible_values
366
+ it_behaves_like 'it supports with_message', valid_values: possible_values
367
+
368
+ if active_model_3_2?
369
+ context '+ strict' do
370
+ context 'when the validation specifies strict' do
371
+ it 'matches when the given range matches the range in the validation' do
372
+ builder = build_object_allowing(possible_values, strict: true)
373
+
374
+ expect_to_match_on_values(builder, possible_values) do |matcher|
375
+ matcher.strict
376
+ end
377
+ end
176
378
 
177
- it 'accepts allow_nil' do
178
- expect(validating_inclusion(in: %w(one two), allow_nil: true)).
179
- to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil
379
+ it 'matches when the given range does not match the range in the validation' do
380
+ builder = build_object_allowing(possible_values, strict: true)
381
+
382
+ range = Range.new(possible_values.first, possible_values.last + 1)
383
+ expect_not_to_match_on_values(builder, range) do |matcher|
384
+ matcher.strict
385
+ end
386
+ end
387
+ end
388
+
389
+ context 'when the validation does not specify strict' do
390
+ it 'does not match' do
391
+ builder = build_object_allowing(possible_values)
392
+
393
+ expect_not_to_match_on_values(builder, possible_values) do |matcher|
394
+ matcher.strict
395
+ end
396
+ end
397
+ end
398
+ end
180
399
  end
181
400
 
182
- it 'rejects allow_nil' do
183
- expect(validating_inclusion(in: %w(one two), allow_nil: true)).
184
- not_to ensure_inclusion_of(:attr).in_array(%w(one two)).allow_nil(false)
401
+ def expect_to_match_on_values(builder, range, &block)
402
+ expect_to_match_in_range(builder, range, &block)
185
403
  end
186
- end
187
404
 
188
- context 'an attribute allowing some blank values but not others' do
189
- it 'rejects allow_blank' do
190
- expect(validating_inclusion(in: ['one', 'two', ''])).
191
- not_to ensure_inclusion_of(:attr).in_array(['one', 'two', '']).allow_blank(true)
405
+ def expect_not_to_match_on_values(builder, range, &block)
406
+ expect_not_to_match_in_range(builder, range, &block)
192
407
  end
193
408
  end
194
409
 
195
- if active_model_3_2?
196
- context 'a strict attribute which must be included in a range' do
197
- it 'accepts ensuring the correct range' do
198
- expect(validating_inclusion(in: 2..5, strict: true)).
199
- to ensure_inclusion_of(:attr).in_range(2..5).strict
410
+ shared_context 'against a boolean attribute for true and false' do
411
+ context 'when ensuring inclusion of true' do
412
+ it 'matches' do
413
+ valid_values = [true]
414
+ builder = build_object_allowing(valid_values)
415
+ expect_to_match_in_array(builder, valid_values)
200
416
  end
417
+ end
201
418
 
202
- it 'rejects ensuring another range' do
203
- expect(validating_inclusion(in: 2..5, strict: true)).
204
- not_to ensure_inclusion_of(:attr).in_range(2..6).strict
419
+ context 'when ensuring inclusion of false' do
420
+ it 'matches' do
421
+ valid_values = [false]
422
+ builder = build_object_allowing(valid_values)
423
+ expect_to_match_in_array(builder, valid_values)
205
424
  end
206
425
  end
207
- end
208
426
 
209
- context 'against a boolean attribute' do
210
- context 'which is nullable' do
211
- context 'when ensuring inclusion of true' do
212
- it "doesn't raise an error" do
213
- record = validating_inclusion_of_boolean_in(:attr, [true], null: true)
214
- expect(record).to ensure_inclusion_of(:attr).in_array([true])
427
+ context 'when ensuring inclusion of true and false' do
428
+ it 'matches' do
429
+ valid_values = [true, false]
430
+ builder = build_object_allowing(valid_values)
431
+ capture(:stderr) do
432
+ expect_to_match_in_array(builder, valid_values)
215
433
  end
216
434
  end
217
435
 
218
- context 'when ensuring inclusion of false' do
219
- it "doesn't raise an error" do
220
- record = validating_inclusion_of_boolean_in(:attr, [false], null: true)
221
- expect(record).to ensure_inclusion_of(:attr).in_array([false])
436
+ it 'prints a warning' do
437
+ valid_values = [true, false]
438
+ builder = build_object_allowing(valid_values)
439
+ message = 'You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones'
440
+
441
+ stderr = capture(:stderr) do
442
+ expect_to_match_in_array(builder, valid_values)
222
443
  end
444
+
445
+ expect(stderr.gsub(/\n+/, ' ')).to include(message)
223
446
  end
447
+ end
448
+ end
449
+
450
+ context 'for a database column' do
451
+ include_context 'for a generic attribute'
452
+
453
+ context 'against a boolean attribute' do
454
+ context 'which is nullable' do
455
+ include_context 'against a boolean attribute for true and false'
224
456
 
225
- context 'when ensuring inclusion of true and false' do
226
- it "doesn't raise an error" do
227
- record = validating_inclusion_of_boolean_in(:attr, [true, false], null: true)
228
- capture(:stderr) do
229
- expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
457
+ context 'when ensuring inclusion of nil' do
458
+ it 'matches' do
459
+ valid_values = [nil]
460
+ builder = build_object_allowing(valid_values)
461
+ capture(:stderr) do
462
+ expect_to_match_in_array(builder, valid_values)
463
+ end
230
464
  end
231
- end
232
465
 
233
- it 'prints a warning' do
234
- record = validating_inclusion_of_boolean_in(:attr, [true, false], null: true)
235
- stderr = capture(:stderr) do
236
- expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
466
+ it 'prints a warning' do
467
+ valid_values = [nil]
468
+ builder = build_object_allowing(valid_values)
469
+ message = 'You are using `ensure_inclusion_of` to assert that a boolean column allows nil'
470
+
471
+ stderr = capture(:stderr) do
472
+ expect_to_match_in_array(builder, valid_values)
473
+ end
474
+
475
+ expect(stderr.gsub(/\n+/, ' ')).to include(message)
237
476
  end
238
- expect(stderr.gsub(/\n+/, ' ')).
239
- to include('You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones')
477
+ end
478
+
479
+ def build_object(options = {}, &block)
480
+ super(options.merge(column_options: { null: true }, value: true))
240
481
  end
241
482
  end
242
483
 
243
- context 'when ensuring inclusion of nil' do
244
- it "doesn't raise an error" do
245
- record = validating_inclusion_of_boolean_in(:attr, [nil], null: true)
246
- capture(:stderr) do
247
- expect(record).to ensure_inclusion_of(:attr).in_array([nil])
484
+ context 'which is non-nullable' do
485
+ include_context 'against a boolean attribute for true and false'
486
+
487
+ context 'when ensuring inclusion of nil' do
488
+ it 'raises a specific error' do
489
+ valid_values = [nil]
490
+ builder = build_object_allowing(valid_values)
491
+ error_class = Shoulda::Matchers::ActiveModel::NonNullableBooleanError
492
+
493
+ expect {
494
+ expect_to_match_in_array(builder, valid_values)
495
+ }.to raise_error(error_class)
248
496
  end
249
497
  end
250
498
 
251
- it 'prints a warning' do
252
- record = validating_inclusion_of_boolean_in(:attr, [nil], null: true)
253
- stderr = capture(:stderr) do
254
- expect(record).to ensure_inclusion_of(:attr).in_array([nil])
255
- end
256
- expect(stderr.gsub(/\n+/, ' ')).
257
- to include('You are using `ensure_inclusion_of` to assert that a boolean column allows nil')
499
+ def build_object(options = {}, &block)
500
+ super(options.merge(column_options: { null: false }))
258
501
  end
259
502
  end
503
+
504
+ def build_object(options = {}, &block)
505
+ build_object_with_generic_attribute(
506
+ options.merge(column_type: :boolean),
507
+ &block
508
+ )
509
+ end
260
510
  end
261
511
 
262
- context 'which is non-nullable' do
263
- context 'when ensuring inclusion of true' do
264
- it "doesn't raise an error" do
265
- record = validating_inclusion_of_boolean_in(:attr, [true], null: false)
266
- expect(record).to ensure_inclusion_of(:attr).in_array([true])
267
- end
512
+
513
+ def build_object_with_generic_attribute(options = {}, &block)
514
+ attribute_name = :attr
515
+ column_type = options.fetch(:column_type)
516
+ column_options = {
517
+ type: column_type,
518
+ options: options.fetch(:column_options, {})
519
+ }
520
+ validation_options = options[:validation_options]
521
+ custom_validation = options[:custom_validation]
522
+
523
+ model = define_model :example, attribute_name => column_options
524
+ customize_model_class(
525
+ model,
526
+ attribute_name,
527
+ validation_options,
528
+ custom_validation
529
+ )
530
+
531
+ object = model.new
532
+
533
+ object_builder_class.new(attribute_name, object, validation_options)
534
+ end
535
+ end
536
+
537
+ context 'for a plain Ruby attribute' do
538
+ include_context 'for a generic attribute'
539
+
540
+ context 'against a boolean attribute (designated by true)' do
541
+ include_context 'against a boolean attribute for true and false'
542
+
543
+ def build_object(options = {}, &block)
544
+ build_object_with_generic_attribute(options.merge(value: true))
268
545
  end
546
+ end
269
547
 
270
- context 'when ensuring inclusion of false' do
271
- it "doesn't raise an error" do
272
- record = validating_inclusion_of_boolean_in(:attr, [false], null: false)
273
- expect(record).to ensure_inclusion_of(:attr).in_array([false])
274
- end
548
+ context 'against a boolean attribute (designated by false)' do
549
+ include_context 'against a boolean attribute for true and false'
550
+
551
+ def build_object(options = {}, &block)
552
+ build_object_with_generic_attribute(options.merge(value: false))
275
553
  end
554
+ end
276
555
 
277
- context 'when ensuring inclusion of true and false' do
278
- it "doesn't raise an error" do
279
- record = validating_inclusion_of_boolean_in(:attr, [true, false], null: false)
280
- capture(:stderr) do
281
- expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
282
- end
283
- end
556
+ def build_object_with_generic_attribute(options = {}, &block)
557
+ attribute_name = :attr
558
+ validation_options = options[:validation_options]
559
+ custom_validation = options[:custom_validation]
560
+ value = options[:value]
284
561
 
285
- it 'prints a warning' do
286
- record = validating_inclusion_of_boolean_in(:attr, [true, false], null: false)
287
- stderr = capture(:stderr) do
288
- expect(record).to ensure_inclusion_of(:attr).in_array([true, false])
289
- end
290
- expect(stderr.gsub(/\n+/, ' ')).
291
- to include('You are using `ensure_inclusion_of` to assert that a boolean column allows boolean values and disallows non-boolean ones')
292
- end
562
+ model = define_active_model_class :example, accessors: [attribute_name]
563
+ customize_model_class(
564
+ model,
565
+ attribute_name,
566
+ validation_options,
567
+ custom_validation
568
+ )
569
+
570
+ object = model.new
571
+ object.__send__("#{attribute_name}=", value)
572
+
573
+ object_builder_class.new(attribute_name, object, validation_options)
574
+ end
575
+ end
576
+
577
+ def object_builder_class
578
+ @_object_builder_class ||= Struct.new(:attribute, :object, :validation_options)
579
+ end
580
+
581
+ def customize_model_class(klass, attribute_name, validation_options, custom_validation)
582
+ klass.class_eval do
583
+ if validation_options
584
+ validates_inclusion_of attribute_name, validation_options
293
585
  end
294
586
 
295
- context 'when ensuring inclusion of nil' do
296
- it 'raises a specific error' do
297
- record = validating_inclusion_of_boolean_in(:attr, [nil], null: false)
298
- error_class = Shoulda::Matchers::ActiveModel::NonNullableBooleanError
299
- expect {
300
- expect(record).to ensure_inclusion_of(:attr).in_array([nil])
301
- }.to raise_error(error_class)
587
+ if custom_validation
588
+ define_method :custom_validation do
589
+ instance_exec(attribute_name, &custom_validation)
302
590
  end
591
+
592
+ validate :custom_validation
303
593
  end
304
594
  end
305
595
  end
306
596
 
307
- def validating_inclusion(options)
308
- define_model(:example, attr: :string) do
309
- validates_inclusion_of :attr, options
310
- end.new
597
+ def build_object_allowing(values, options = {})
598
+ build_object(validation_options: options.merge(in: values))
311
599
  end
312
600
 
313
- def validating_inclusion_of_boolean_in(attribute, values, options = {})
314
- null = options.fetch(:null, true)
315
- column_options = { type: :boolean, options: { null: null } }
316
- define_model(:example, attribute => column_options) do
317
- validates_inclusion_of attribute, in: values
318
- end.new
601
+ def expect_to_match(builder)
602
+ matcher = ensure_inclusion_of(builder.attribute)
603
+ yield matcher if block_given?
604
+ expect(builder.object).to(matcher)
605
+ end
606
+
607
+ def expect_not_to_match(builder)
608
+ matcher = ensure_inclusion_of(builder.attribute)
609
+ yield matcher if block_given?
610
+ expect(builder.object).not_to(matcher)
611
+ end
612
+
613
+ def expect_to_match_in_array(builder, array)
614
+ expect_to_match(builder) do |matcher|
615
+ matcher.in_array(array)
616
+ yield matcher if block_given?
617
+ end
618
+ end
619
+
620
+ def expect_not_to_match_in_array(builder, array)
621
+ expect_not_to_match(builder) do |matcher|
622
+ matcher.in_array(array)
623
+ yield matcher if block_given?
624
+ end
625
+ end
626
+
627
+ def expect_to_match_in_range(builder, range)
628
+ expect_to_match(builder) do |matcher|
629
+ matcher.in_range(range)
630
+ yield matcher if block_given?
631
+ end
632
+ end
633
+
634
+ def expect_not_to_match_in_range(builder, range)
635
+ expect_not_to_match(builder) do |matcher|
636
+ matcher.in_range(range)
637
+ yield matcher if block_given?
638
+ end
639
+ end
640
+
641
+ def expect_to_match_ensuring_range_and_messages(range, low_value, high_value)
642
+ low_message = 'too low'
643
+ high_message = 'too high'
644
+
645
+ builder = build_object custom_validation: ->(attribute) {
646
+ value = __send__(attribute)
647
+
648
+ if value < low_value
649
+ errors.add(attribute, low_message)
650
+ elsif value > high_value
651
+ errors.add(attribute, high_message)
652
+ end
653
+ }
654
+
655
+ expect_to_match(builder) do |matcher|
656
+ matcher.
657
+ in_range(range).
658
+ with_low_message(low_message).
659
+ with_high_message(high_message)
660
+ end
319
661
  end
320
662
  end