shoulda-matchers 3.0.1 → 3.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.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -3
  4. data/CONTRIBUTING.md +60 -28
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +15 -12
  7. data/NEWS.md +111 -0
  8. data/README.md +94 -6
  9. data/Rakefile +10 -8
  10. data/custom_plan.rb +88 -0
  11. data/gemfiles/4.0.0.gemfile +1 -0
  12. data/gemfiles/4.0.0.gemfile.lock +21 -18
  13. data/gemfiles/4.0.1.gemfile +1 -0
  14. data/gemfiles/4.0.1.gemfile.lock +21 -18
  15. data/gemfiles/4.1.gemfile +1 -0
  16. data/gemfiles/4.1.gemfile.lock +21 -18
  17. data/gemfiles/4.2.gemfile +1 -0
  18. data/gemfiles/4.2.gemfile.lock +24 -21
  19. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
  20. data/lib/shoulda/matchers/active_model.rb +10 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  26. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  27. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  29. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  30. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
  31. data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
  32. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
  37. data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
  38. data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  39. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  40. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
  41. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
  42. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
  43. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
  44. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
  45. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
  46. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
  47. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
  48. data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
  49. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
  50. data/lib/shoulda/matchers/active_model/validator.rb +71 -52
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
  53. data/lib/shoulda/matchers/util.rb +43 -0
  54. data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
  55. data/lib/shoulda/matchers/version.rb +1 -1
  56. data/script/update_gem_in_all_appraisals +1 -1
  57. data/script/update_gems_in_all_appraisals +1 -1
  58. data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
  59. data/spec/acceptance/rails_integration_spec.rb +6 -2
  60. data/spec/spec_helper.rb +1 -3
  61. data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
  62. data/spec/support/tests/current_bundle.rb +21 -7
  63. data/spec/support/unit/active_record/create_table.rb +54 -0
  64. data/spec/support/unit/attribute.rb +47 -0
  65. data/spec/support/unit/capture.rb +6 -0
  66. data/spec/support/unit/change_value.rb +111 -0
  67. data/spec/support/unit/create_model_arguments/basic.rb +135 -0
  68. data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
  69. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
  70. data/spec/support/unit/helpers/active_record_versions.rb +1 -1
  71. data/spec/support/unit/helpers/class_builder.rb +61 -47
  72. data/spec/support/unit/helpers/database_helpers.rb +5 -3
  73. data/spec/support/unit/helpers/model_builder.rb +77 -97
  74. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
  75. data/spec/support/unit/load_environment.rb +12 -0
  76. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
  77. data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
  78. data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
  79. data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
  80. data/spec/support/unit/model_creators.rb +19 -0
  81. data/spec/support/unit/model_creators/active_model.rb +39 -0
  82. data/spec/support/unit/model_creators/active_record.rb +43 -0
  83. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
  84. data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
  85. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
  86. data/spec/support/unit/model_creators/basic.rb +97 -0
  87. data/spec/support/unit/rails_application.rb +1 -1
  88. data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
  89. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
  90. data/spec/support/unit/validation_matcher_scenario.rb +62 -0
  91. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
  93. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
  94. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
  95. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
  96. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
  97. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
  98. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
  99. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
  100. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
  101. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
  102. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
  103. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
  104. data/spec/unit_spec_helper.rb +10 -22
  105. data/zeus.json +11 -0
  106. metadata +64 -23
  107. data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
  108. data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
  109. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
  110. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
  111. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
  112. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -0,0 +1,62 @@
1
+ require 'forwardable'
2
+
3
+ module UnitTests
4
+ class ValidationMatcherScenario
5
+ extend Forwardable
6
+
7
+ attr_reader :matcher
8
+
9
+ def initialize(arguments)
10
+ @arguments = arguments.dup
11
+ @matcher_proc = @arguments.delete(:matcher_proc)
12
+
13
+ @specified_model_creator = @arguments.delete(:model_creator) do
14
+ raise KeyError.new(<<-MESSAGE)
15
+ :model_creator is missing. You can either provide it as an option or as
16
+ a method.
17
+ MESSAGE
18
+ end
19
+
20
+ @model_creator = model_creator_class.new(@arguments)
21
+ end
22
+
23
+ def record
24
+ @_record ||= model.new.tap do |record|
25
+ attribute_default_values_by_name.each do |attribute_name, default_value|
26
+ record.public_send("#{attribute_name}=", default_value)
27
+ end
28
+ end
29
+ end
30
+
31
+ def model
32
+ @_model ||= model_creator.call
33
+ end
34
+
35
+ def matcher
36
+ @_matcher ||= matcher_proc.call(attribute_name)
37
+ end
38
+
39
+ protected
40
+
41
+ attr_reader(
42
+ :arguments,
43
+ :existing_value,
44
+ :matcher_proc,
45
+ :model_creator,
46
+ :specified_model_creator,
47
+ )
48
+
49
+ private
50
+
51
+ def_delegators(
52
+ :model_creator,
53
+ :attribute_name,
54
+ :attribute_default_values_by_name,
55
+ )
56
+
57
+ def model_creator_class
58
+ UnitTests::ModelCreators.retrieve(specified_model_creator) ||
59
+ specified_model_creator
60
+ end
61
+ end
62
+ end
@@ -108,4 +108,8 @@ describe Shoulda::Matchers::ActiveModel::AllowMassAssignmentOfMatcher, type: :mo
108
108
  end
109
109
  end
110
110
  end
111
+
112
+ def define_model(name, columns, &block)
113
+ super(name, columns, whitelist_attributes: false, &block)
114
+ end
111
115
  end
@@ -13,21 +13,24 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
13
13
  it 'describes itself with multiple values' do
14
14
  matcher = allow_value('foo', 'bar').for(:baz)
15
15
 
16
- expect(matcher.description).to eq 'allow baz to be set to any of ["foo", "bar"]'
16
+ expect(matcher.description).to eq(
17
+ 'allow :baz to be ‹"foo"› or ‹"bar"›'
18
+ )
17
19
  end
18
20
 
19
21
  it 'describes itself with a single value' do
20
22
  matcher = allow_value('foo').for(:baz)
21
23
 
22
- expect(matcher.description).to eq 'allow baz to be set to "foo"'
24
+ expect(matcher.description).to eq 'allow :baz to be "foo"'
23
25
  end
24
26
 
25
27
  if active_model_3_2?
26
28
  it 'describes itself with a strict validation' do
27
29
  strict_matcher = allow_value('xyz').for(:attr).strict
28
30
 
29
- expect(strict_matcher.description).
30
- to eq %q(doesn't raise when attr is set to "xyz")
31
+ expect(strict_matcher.description).to eq(
32
+ 'allow :attr to be ‹"xyz"›, raising a validation exception on failure'
33
+ )
31
34
  end
32
35
  end
33
36
  end
@@ -47,24 +50,192 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
47
50
  end
48
51
 
49
52
  context 'an attribute with a validation' do
50
- it 'allows a good value' do
51
- expect(validating_format(with: /abc/)).to allow_value('abcde').for(:attr)
53
+ context 'given one good value' do
54
+ context 'when used in the positive' do
55
+ it 'accepts' do
56
+ expect(validating_format(with: /abc/)).
57
+ to allow_value('abcde').for(:attr)
58
+ end
59
+ end
60
+
61
+ context 'when used in the negative' do
62
+ it 'rejects with an appropriate failure message' do
63
+ assertion = lambda do
64
+ expect(validating_format(with: /abc/)).
65
+ not_to allow_value('abcde').for(:attr)
66
+ end
67
+
68
+ message = <<-MESSAGE
69
+ After setting :attr to ‹"abcde"›, the matcher expected the Example to be
70
+ invalid, but it was valid instead.
71
+ MESSAGE
72
+
73
+ expect(&assertion).to fail_with_message(message)
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'given several good values' do
79
+ context 'when used in the positive' do
80
+ it 'accepts' do
81
+ expect(validating_format(with: /abc/)).
82
+ to allow_value('abcde', 'deabc').for(:attr)
83
+ end
84
+ end
85
+
86
+ context 'when used in the negative' do
87
+ it 'rejects with an appropriate failure message' do
88
+ assertion = lambda do
89
+ expect(validating_format(with: /abc/)).
90
+ not_to allow_value('abcde', 'deabc').for(:attr)
91
+ end
92
+
93
+ message = <<-MESSAGE
94
+ After setting :attr to ‹"abcde"›, the matcher expected the Example to be
95
+ invalid, but it was valid instead.
96
+ MESSAGE
97
+
98
+ expect(&assertion).to fail_with_message(message)
99
+ end
100
+ end
52
101
  end
53
102
 
54
- it 'rejects a bad value' do
55
- expect(validating_format(with: /abc/)).not_to allow_value('xyz').for(:attr)
103
+ context 'given one bad value' do
104
+ context 'when used in the positive' do
105
+ it 'rejects with an appropriate failure message' do
106
+ assertion = lambda do
107
+ expect(validating_format(with: /abc/)).
108
+ to allow_value('xyz').for(:attr)
109
+ end
110
+
111
+ message = <<-MESSAGE
112
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
113
+ valid, but it was invalid instead, producing these validation errors:
114
+
115
+ * attr: ["is invalid"]
116
+ MESSAGE
117
+
118
+ expect(&assertion).to fail_with_message(message)
119
+ end
120
+ end
121
+
122
+ context 'when used in the negative' do
123
+ it 'accepts' do
124
+ expect(validating_format(with: /abc/)).
125
+ not_to allow_value('xyz').for(:attr)
126
+ end
127
+ end
56
128
  end
57
129
 
58
- it 'allows several good values' do
59
- expect(validating_format(with: /abc/)).
60
- to allow_value('abcde', 'deabc').for(:attr)
130
+ context 'given several bad values' do
131
+ context 'when used in the positive' do
132
+ it 'rejects with an appropriate failure message' do
133
+ assertion = lambda do
134
+ expect(validating_format(with: /abc/)).
135
+ to allow_value('xyz', 'zyx', nil, []).
136
+ for(:attr).
137
+ ignoring_interference_by_writer
138
+ end
139
+
140
+ message = <<-MESSAGE
141
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
142
+ valid, but it was invalid instead, producing these validation errors:
143
+
144
+ * attr: ["is invalid"]
145
+ MESSAGE
146
+
147
+ expect(&assertion).to fail_with_message(message)
148
+ end
149
+ end
150
+
151
+ context 'when used in the negative' do
152
+ it 'accepts' do
153
+ expect(validating_format(with: /abc/)).
154
+ not_to allow_value('xyz', 'zyx', nil, []).
155
+ for(:attr).
156
+ ignoring_interference_by_writer
157
+ end
158
+ end
61
159
  end
62
160
 
63
- it 'rejects several bad values' do
64
- expect(validating_format(with: /abc/)).
65
- not_to allow_value('xyz', 'zyx', nil, []).
66
- for(:attr).
67
- ignoring_interference_by_writer
161
+ context 'given good values along with bad values' do
162
+ context 'when used in the positive' do
163
+ it 'rejects with an appropriate failure message' do
164
+ assertion = lambda do
165
+ expect(validating_format(with: /abc/)).
166
+ to allow_value('abc', 'xyz').
167
+ for(:attr).
168
+ ignoring_interference_by_writer
169
+ end
170
+
171
+ message = <<-MESSAGE
172
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
173
+ valid, but it was invalid instead, producing these validation errors:
174
+
175
+ * attr: ["is invalid"]
176
+ MESSAGE
177
+
178
+ expect(&assertion).to fail_with_message(message)
179
+ end
180
+ end
181
+
182
+ context 'when used in the negative' do
183
+ it 'rejects with an appropriate failure message' do
184
+ assertion = lambda do
185
+ expect(validating_format(with: /abc/)).
186
+ not_to allow_value('abc', 'xyz').
187
+ for(:attr).
188
+ ignoring_interference_by_writer
189
+ end
190
+
191
+ message = <<-MESSAGE
192
+ After setting :attr to ‹"abc"›, the matcher expected the Example to be
193
+ invalid, but it was valid instead.
194
+ MESSAGE
195
+
196
+ expect(&assertion).to fail_with_message(message)
197
+ end
198
+ end
199
+ end
200
+
201
+ context 'given bad values along with good values' do
202
+ context 'when used in the positive' do
203
+ it 'rejects with an appropriate failure message' do
204
+ assertion = lambda do
205
+ expect(validating_format(with: /abc/)).
206
+ to allow_value('xyz', 'abc').
207
+ for(:attr).
208
+ ignoring_interference_by_writer
209
+ end
210
+
211
+ message = <<-MESSAGE
212
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
213
+ valid, but it was invalid instead, producing these validation errors:
214
+
215
+ * attr: ["is invalid"]
216
+ MESSAGE
217
+
218
+ expect(&assertion).to fail_with_message(message)
219
+ end
220
+ end
221
+
222
+ context 'when used in the negative' do
223
+ it 'rejects with an appropriate failure message' do
224
+ assertion = lambda do
225
+ expect(validating_format(with: /abc/)).
226
+ not_to allow_value('xyz', 'abc').
227
+ for(:attr).
228
+ ignoring_interference_by_writer
229
+ end
230
+
231
+ message = <<-MESSAGE
232
+ After setting :attr to ‹"abc"›, the matcher expected the Example to be
233
+ invalid, but it was valid instead.
234
+ MESSAGE
235
+
236
+ expect(&assertion).to fail_with_message(message)
237
+ end
238
+ end
68
239
  end
69
240
  end
70
241
 
@@ -74,27 +245,95 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
74
245
  to allow_value('abcde').for(:attr).with_message(/bad/)
75
246
  end
76
247
 
77
- it 'rejects a bad value' do
78
- expect(validating_format(with: /abc/, message: 'bad value')).
79
- not_to allow_value('xyz').for(:attr).with_message(/bad/)
248
+ it 'rejects a bad value with an appropriate failure message' do
249
+ message = <<-MESSAGE
250
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
251
+ valid, but it was invalid instead, producing these validation errors:
252
+
253
+ * attr: ["bad value"]
254
+ MESSAGE
255
+
256
+ assertion = lambda do
257
+ expect(validating_format(with: /abc/, message: 'bad value')).
258
+ to allow_value('xyz').for(:attr).with_message(/bad/)
259
+ end
260
+
261
+ expect(&assertion).to fail_with_message(message)
80
262
  end
81
263
 
82
- it 'allows interpolation values for the message to be provided' do
83
- options = {
84
- attribute_name: :attr,
85
- attribute_type: :string
86
- }
264
+ context 'when the custom messages do not match' do
265
+ it 'rejects with an appropriate failure message' do
266
+ message = <<-MESSAGE
267
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
268
+ invalid and to produce a validation error matching ‹/different/› on
269
+ :attr. The record was indeed invalid, but it produced these validation
270
+ errors instead:
271
+
272
+ * attr: ["bad value"]
273
+ MESSAGE
87
274
 
88
- record = record_with_custom_validation(options) do
89
- if self.attr == 'xyz'
90
- self.errors.add :attr, :greater_than, count: 2
275
+ assertion = lambda do
276
+ expect(validating_format(with: /abc/, message: 'bad value')).
277
+ not_to allow_value('xyz').for(:attr).with_message(/different/)
91
278
  end
279
+
280
+ expect(&assertion).to fail_with_message(message)
92
281
  end
282
+ end
93
283
 
94
- expect(record).
95
- not_to allow_value('xyz').
96
- for(:attr).
97
- with_message(:greater_than, values: { count: 2 })
284
+ context 'when interpolation values are provided along with a custom message' do
285
+ context 'when the messages match' do
286
+ it 'accepts' do
287
+ options = {
288
+ attribute_name: :attr,
289
+ attribute_type: :string
290
+ }
291
+
292
+ record = record_with_custom_validation(options) do
293
+ if self.attr == 'xyz'
294
+ self.errors.add :attr, :greater_than, count: 2
295
+ end
296
+ end
297
+
298
+ expect(record).
299
+ not_to allow_value('xyz').
300
+ for(:attr).
301
+ with_message(:greater_than, values: { count: 2 })
302
+ end
303
+ end
304
+
305
+ context 'when the messages do not match' do
306
+ it 'rejects with an appropriate failure message' do
307
+ options = {
308
+ attribute_name: :attr,
309
+ attribute_type: :string
310
+ }
311
+
312
+ record = record_with_custom_validation(options) do
313
+ if self.attr == 'xyz'
314
+ self.errors.add :attr, "some other error"
315
+ end
316
+ end
317
+
318
+ assertion = lambda do
319
+ expect(record).
320
+ not_to allow_value('xyz').
321
+ for(:attr).
322
+ with_message(:greater_than, values: { count: 2 })
323
+ end
324
+
325
+ message = <<-MESSAGE
326
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
327
+ invalid and to produce the validation error "must be greater than 2" on
328
+ :attr. The record was indeed invalid, but it produced these validation
329
+ errors instead:
330
+
331
+ * attr: ["some other error"]
332
+ MESSAGE
333
+
334
+ expect(&assertion).to fail_with_message(message)
335
+ end
336
+ end
98
337
  end
99
338
  end
100
339
 
@@ -102,25 +341,62 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
102
341
  include UnitTests::AllowValueMatcherHelpers
103
342
 
104
343
  context 'when the validation error message was provided directly' do
105
- it 'passes given a valid value' do
106
- builder = builder_for_record_with_different_error_attribute
107
- expect(builder.record).
108
- to allow_value(builder.valid_value).
109
- for(builder.attribute_to_validate).
110
- with_message(builder.message,
111
- against: builder.attribute_that_receives_error
112
- )
344
+ context 'given a valid value' do
345
+ it 'accepts' do
346
+ builder = builder_for_record_with_different_error_attribute
347
+ expect(builder.record).
348
+ to allow_value(builder.valid_value).
349
+ for(builder.attribute_to_validate).
350
+ with_message(
351
+ builder.message,
352
+ against: builder.attribute_that_receives_error
353
+ )
354
+ end
113
355
  end
114
356
 
115
- it 'fails given an invalid value' do
116
- builder = builder_for_record_with_different_error_attribute
117
- invalid_value = "#{builder.valid_value} (invalid)"
118
- expect(builder.record).
119
- not_to allow_value(invalid_value).
120
- for(builder.attribute_to_validate).
121
- with_message(builder.message,
122
- against: builder.attribute_that_receives_error
123
- )
357
+ context 'given an invalid value' do
358
+ it 'rejects' do
359
+ builder = builder_for_record_with_different_error_attribute
360
+ invalid_value = "#{builder.valid_value} (invalid)"
361
+
362
+ expect(builder.record).
363
+ not_to allow_value(invalid_value).
364
+ for(builder.attribute_to_validate).
365
+ with_message(
366
+ builder.message,
367
+ against: builder.attribute_that_receives_error
368
+ )
369
+ end
370
+
371
+ context 'if the messages do not match' do
372
+ it 'technically accepts' do
373
+ builder = builder_for_record_with_different_error_attribute(
374
+ message: "a different error"
375
+ )
376
+ invalid_value = "#{builder.valid_value} (invalid)"
377
+
378
+ assertion = lambda do
379
+ expect(builder.record).
380
+ not_to allow_value(invalid_value).
381
+ for(builder.attribute_to_validate).
382
+ with_message(
383
+ "some error",
384
+ against: builder.attribute_that_receives_error
385
+ )
386
+ end
387
+
388
+ message = <<-MESSAGE
389
+ After setting :#{builder.attribute_to_validate} to ‹"#{invalid_value}"›, the
390
+ matcher expected the #{builder.model.name} to be invalid and to produce the validation
391
+ error "some error" on :#{builder.attribute_that_receives_error}. The record was
392
+ indeed invalid, but it produced these validation errors instead:
393
+
394
+ * #{builder.attribute_that_receives_error}: ["a different error"]
395
+ MESSAGE
396
+
397
+ expect(&assertion).to fail_with_message(message)
398
+ end
399
+ end
124
400
  end
125
401
  end
126
402
 
@@ -130,7 +406,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
130
406
  expect(builder.record).
131
407
  to allow_value(builder.valid_value).
132
408
  for(builder.attribute_to_validate).
133
- with_message(builder.validation_message_key,
409
+ with_message(
410
+ builder.validation_message_key,
134
411
  against: builder.attribute_that_receives_error
135
412
  )
136
413
  end
@@ -141,7 +418,8 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
141
418
  expect(builder.record).
142
419
  not_to allow_value(invalid_value).
143
420
  for(builder.attribute_to_validate).
144
- with_message(builder.validation_message_key,
421
+ with_message(
422
+ builder.validation_message_key,
145
423
  against: builder.attribute_that_receives_error
146
424
  )
147
425
  end
@@ -199,11 +477,29 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
199
477
  end
200
478
 
201
479
  it "does not match given good values along with bad values" do
202
- message = %{Expected errors when attr is set to "12345",\ngot no errors}
480
+ message = <<-MESSAGE.strip_heredoc
481
+ After setting :attr to ‹"12345"›, the matcher expected the Example to be
482
+ invalid, but it was valid instead.
483
+ MESSAGE
203
484
 
204
- expect {
485
+ assertion = lambda do
205
486
  expect(model).not_to allow_value('12345', *bad_values).for(:attr)
206
- }.to fail_with_message(message)
487
+ end
488
+
489
+ expect(&assertion).to fail_with_message(message)
490
+ end
491
+
492
+ it "does not match given bad values along with good values" do
493
+ message = <<-MESSAGE.strip_heredoc
494
+ After setting :attr to ‹"12345"›, the matcher expected the Example to be
495
+ invalid, but it was valid instead.
496
+ MESSAGE
497
+
498
+ assertion = lambda do
499
+ expect(model).not_to allow_value(*bad_values, '12345').for(:attr)
500
+ end
501
+
502
+ expect(&assertion).to fail_with_message(message)
207
503
  end
208
504
  end
209
505
 
@@ -226,161 +522,300 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
226
522
 
227
523
  if active_model_3_2?
228
524
  context 'an attribute with a strict format validation' do
229
- it 'strictly rejects a bad value' do
230
- expect(validating_format(with: /abc/, strict: true)).
231
- not_to allow_value('xyz').for(:attr).strict
232
- end
525
+ context 'when qualified with strict' do
526
+ it 'rejects a bad value, providing the correct failure message' do
527
+ message = <<-MESSAGE.strip_heredoc
528
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
529
+ valid, but it was invalid instead, raising a validation exception with
530
+ the message "Attr is invalid".
531
+ MESSAGE
233
532
 
234
- it 'strictly allows a bad value with a different message' do
235
- expect(validating_format(with: /abc/, strict: true)).
236
- to allow_value('xyz').for(:attr).with_message(/abc/).strict
237
- end
533
+ assertion = lambda do
534
+ expect(validating_format(with: /abc/, strict: true)).
535
+ to allow_value('xyz').for(:attr).strict
536
+ end
238
537
 
239
- it 'provides a useful negative failure message' do
240
- matcher = allow_value('xyz').for(:attr).strict.with_message(/abc/)
538
+ expect(&assertion).to fail_with_message(message)
539
+ end
241
540
 
242
- matcher.matches?(validating_format(with: /abc/, strict: true))
541
+ context 'qualified with a custom message' do
542
+ it 'rejects a bad value when the failure messages do not match' do
543
+ message = <<-MESSAGE.strip_heredoc
544
+ After setting :attr to ‹"xyz"›, the matcher expected the Example to be
545
+ invalid and to raise a validation exception with message matching
546
+ ‹/abc/›. The record was indeed invalid, but the exception message was
547
+ "Attr is invalid" instead.
548
+ MESSAGE
549
+
550
+ assertion = lambda do
551
+ expect(validating_format(with: /abc/, strict: true)).
552
+ not_to allow_value('xyz').for(:attr).with_message(/abc/).strict
553
+ end
243
554
 
244
- expect(matcher.failure_message_when_negated).to eq(
245
- %{Expected exception to include /abc/ when attr is set to "xyz",\n} +
246
- %{got: "Attr is invalid"}
247
- )
555
+ expect(&assertion).to fail_with_message(message)
556
+ end
557
+ end
248
558
  end
249
559
  end
250
560
  end
251
561
 
252
562
  context 'when the attribute interferes with attempts to be set' do
253
- context 'when the matcher has not been qualified with #ignoring_interference_by_writer' do
254
- context 'when the attribute cannot be changed from nil to non-nil' do
255
- it 'raises a CouldNotSetAttributeError' do
256
- model = define_active_model_class 'Example' do
257
- attr_reader :name
563
+ context 'when the attribute cannot be changed from nil to non-nil' do
564
+ context 'and the record remains valid' do
565
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
566
+ model = define_active_model_class 'Example', accessors: [:name] do
567
+ def name=(_value)
568
+ nil
569
+ end
570
+ end
571
+
572
+ expect(model.new).to allow_value('anything').for(:name)
573
+ end
574
+ end
575
+
576
+ context 'and the record becomes invalid' do
577
+ it 'rejects with an appropriate failure message' do
578
+ model = define_active_model_class 'Example', accessors: [:name] do
579
+ validates_presence_of :name
258
580
 
259
581
  def name=(_value)
260
582
  nil
261
583
  end
262
584
  end
263
585
 
264
- assertion = -> {
586
+ assertion = lambda do
265
587
  expect(model.new).to allow_value('anything').for(:name)
266
- }
588
+ end
267
589
 
268
- expect(&assertion).to raise_error(
269
- described_class::CouldNotSetAttributeError
270
- )
590
+ message = <<-MESSAGE.strip
591
+ After setting :name to ‹"anything"› -- which was read back as ‹nil› --
592
+ the matcher expected the Example to be valid, but it was invalid
593
+ instead, producing these validation errors:
594
+
595
+ * name: ["can't be blank"]
596
+
597
+ As indicated in the message above, :name seems to be changing certain
598
+ values as they are set, and this could have something to do with why
599
+ this test is failing. If you've overridden the writer method for this
600
+ attribute, then you may need to change it to make this test pass, or do
601
+ something else entirely.
602
+ MESSAGE
603
+
604
+ expect(&assertion).to fail_with_message(message)
271
605
  end
272
606
  end
607
+ end
273
608
 
274
- context 'when the attribute cannot be changed from non-nil to nil' do
275
- it 'raises a CouldNotSetAttribute error' do
276
- model = define_active_model_class 'Example' do
277
- attr_reader :name
278
-
609
+ context 'when the attribute cannot be changed from non-nil to nil' do
610
+ context 'and the record remains valid' do
611
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
612
+ model = define_active_model_class 'Example', accessors: [:name] do
279
613
  def name=(value)
280
- @name = value unless value.nil?
614
+ if value
615
+ super(value)
616
+ end
281
617
  end
282
618
  end
283
619
 
284
620
  record = model.new(name: 'some name')
285
621
 
286
- assertion = -> {
287
- expect(record).to allow_value(nil).for(:name)
288
- }
289
-
290
- expect(&assertion).to raise_error(
291
- described_class::CouldNotSetAttributeError
292
- )
622
+ expect(record).to allow_value(nil).for(:name)
293
623
  end
294
624
  end
295
625
 
296
- context 'when the attribute cannot be changed from a non-nil value to another non-nil value' do
297
- it 'raises a CouldNotSetAttribute error' do
298
- model = define_active_model_class 'Example' do
299
- attr_reader :name
626
+ context 'and the record becomes invalid' do
627
+ it 'rejects with an appropriate failure message' do
628
+ model = define_active_model_class 'Example', accessors: [:name] do
629
+ validates_absence_of :name
300
630
 
301
- def name=(_value)
302
- @name = 'constant name'
631
+ def name=(value)
632
+ if value
633
+ super(value)
634
+ end
303
635
  end
304
636
  end
305
637
 
306
638
  record = model.new(name: 'some name')
307
639
 
308
- assertion = -> {
309
- expect(record).to allow_value('another name').for(:name)
310
- }
640
+ assertion = lambda do
641
+ expect(record).to allow_value(nil).for(:name)
642
+ end
311
643
 
312
- expect(&assertion).to raise_error(
313
- described_class::CouldNotSetAttributeError
314
- )
644
+ message = <<-MESSAGE.strip
645
+ After setting :name to ‹nil› -- which was read back as ‹"some name"› --
646
+ the matcher expected the Example to be valid, but it was invalid
647
+ instead, producing these validation errors:
648
+
649
+ * name: ["must be blank"]
650
+
651
+ As indicated in the message above, :name seems to be changing certain
652
+ values as they are set, and this could have something to do with why
653
+ this test is failing. If you've overridden the writer method for this
654
+ attribute, then you may need to change it to make this test pass, or do
655
+ something else entirely.
656
+ MESSAGE
657
+
658
+ expect(&assertion).to fail_with_message(message)
315
659
  end
316
660
  end
317
661
  end
318
662
 
319
- context 'when the matcher has been qualified with #ignoring_interference_by_writer' do
320
- context 'when the attribute cannot be changed from nil to non-nil' do
321
- it 'does not raise an error at all' do
322
- model = define_active_model_class 'Example' do
323
- attr_reader :name
324
-
663
+ context 'when the attribute cannot be changed from a non-nil value to another non-nil value' do
664
+ context 'and the record remains valid' do
665
+ it 'accepts (and does not raise an AttributeChangedValueError)' do
666
+ model = define_active_model_class 'Example', accessors: [:name] do
325
667
  def name=(_value)
326
- nil
668
+ super('constant name')
327
669
  end
328
670
  end
329
671
 
330
- assertion = lambda do
331
- expect(model.new).
332
- to allow_value('anything').
333
- for(:name).
334
- ignoring_interference_by_writer
335
- end
672
+ record = model.new(name: 'some name')
336
673
 
337
- expect(&assertion).not_to raise_error
674
+ expect(record).to allow_value('another name').for(:name)
338
675
  end
339
676
  end
340
677
 
341
- context 'when the attribute cannot be changed from non-nil to nil' do
342
- it 'does not raise an error at all' do
343
- model = define_active_model_class 'Example' do
344
- attr_reader :name
678
+ context 'and the record becomes invalid' do
679
+ it 'rejects with an appropriate failure message' do
680
+ model = define_active_model_class 'Example', accessors: [:name] do
681
+ validates_format_of :name, with: /another name/
345
682
 
346
683
  def name=(value)
347
- @name = value unless value.nil?
684
+ super('constant name')
348
685
  end
349
686
  end
350
687
 
351
688
  record = model.new(name: 'some name')
352
689
 
353
690
  assertion = lambda do
354
- expect(record).
355
- to allow_value(nil).
356
- for(:name).
357
- ignoring_interference_by_writer
691
+ expect(record).to allow_value('another name').for(:name)
358
692
  end
359
693
 
360
- expect(&assertion).not_to raise_error
694
+ message = <<-MESSAGE.strip
695
+ After setting :name to ‹"another name"› -- which was read back as
696
+ ‹"constant name"› -- the matcher expected the Example to be valid, but
697
+ it was invalid instead, producing these validation errors:
698
+
699
+ * name: ["is invalid"]
700
+
701
+ As indicated in the message above, :name seems to be changing certain
702
+ values as they are set, and this could have something to do with why
703
+ this test is failing. If you've overridden the writer method for this
704
+ attribute, then you may need to change it to make this test pass, or do
705
+ something else entirely.
706
+ MESSAGE
707
+
708
+ expect(&assertion).to fail_with_message(message)
709
+ end
710
+ end
711
+ end
712
+ end
713
+
714
+ context 'when the attribute does not exist on the model' do
715
+ context 'when the assertion is positive' do
716
+ it 'raises an AttributeDoesNotExistError' do
717
+ model = define_class('Example')
718
+
719
+ assertion = lambda do
720
+ expect(model.new).to allow_value('foo').for(:nonexistent)
361
721
  end
722
+
723
+ message = <<-MESSAGE.rstrip
724
+ The matcher attempted to set :nonexistent on the Example to "foo", but
725
+ that attribute does not exist.
726
+ MESSAGE
727
+
728
+ expect(&assertion).to raise_error(
729
+ described_class::AttributeDoesNotExistError,
730
+ message
731
+ )
362
732
  end
733
+ end
363
734
 
364
- context 'when the attribute cannot be changed from a non-nil value to another non-nil value' do
365
- it 'does not raise an error at all' do
366
- model = define_active_model_class 'Example' do
367
- attr_reader :name
735
+ context 'when the assertion is negative' do
736
+ it 'raises an AttributeDoesNotExistError' do
737
+ model = define_class('Example')
368
738
 
369
- def name=(_value)
370
- @name = 'constant name'
371
- end
739
+ assertion = lambda do
740
+ expect(model.new).not_to allow_value('foo').for(:nonexistent)
741
+ end
742
+
743
+ message = <<-MESSAGE.rstrip
744
+ The matcher attempted to set :nonexistent on the Example to "foo", but
745
+ that attribute does not exist.
746
+ MESSAGE
747
+
748
+ expect(&assertion).to raise_error(
749
+ described_class::AttributeDoesNotExistError,
750
+ message
751
+ )
752
+ end
753
+ end
754
+ end
755
+
756
+ context 'given attributes to preset on the record before validation' do
757
+ context 'when the assertion is positive' do
758
+ context 'if any attributes do not exist on the model' do
759
+ it 'raises an AttributeDoesNotExistError' do
760
+ model = define_active_model_class('Example', accessors: [:existent])
761
+
762
+ allow_value_matcher = allow_value('foo').for(:existent).tap do |matcher|
763
+ matcher.values_to_preset = { nonexistent: 'some value' }
372
764
  end
373
765
 
374
- record = model.new(name: 'some name')
766
+ assertion = lambda do
767
+ expect(model.new).to(allow_value_matcher)
768
+ end
769
+
770
+ message = <<-MESSAGE.rstrip
771
+ The matcher attempted to set :nonexistent on the Example to "some
772
+ value", but that attribute does not exist.
773
+ MESSAGE
774
+
775
+ expect(&assertion).to raise_error(
776
+ described_class::AttributeDoesNotExistError,
777
+ message
778
+ )
779
+ end
780
+ end
781
+ end
782
+
783
+ context 'when the assertion is negative' do
784
+ context 'if any attributes do not exist on the model' do
785
+ it 'raises an AttributeDoesNotExistError' do
786
+ model = define_active_model_class('Example', accessors: [:existent])
787
+
788
+ allow_value_matcher = allow_value('foo').for(:existent).tap do |matcher|
789
+ matcher.values_to_preset = { nonexistent: 'some value' }
790
+ end
375
791
 
376
792
  assertion = lambda do
377
- expect(record).
378
- to allow_value('another name').
379
- for(:name).
380
- ignoring_interference_by_writer
793
+ expect(model.new).not_to(allow_value_matcher)
794
+ end
795
+
796
+ message = <<-MESSAGE.rstrip
797
+ The matcher attempted to set :nonexistent on the Example to "some
798
+ value", but that attribute does not exist.
799
+ MESSAGE
800
+
801
+ expect(&assertion).to raise_error(
802
+ described_class::AttributeDoesNotExistError,
803
+ message
804
+ )
805
+ end
806
+ end
807
+ end
808
+ end
809
+
810
+ if active_record_supports_enum?
811
+ context 'given an ActiveRecord model' do
812
+ context 'where the attribute under test is an enum and the given value is a value in that enum' do
813
+ it 'accepts' do
814
+ model = define_model('Shipment', status: :integer) do
815
+ enum status: { pending: 1, shipped: 2, delivered: 3 }
381
816
  end
382
817
 
383
- expect(&assertion).not_to raise_error
818
+ expect(model.new).to allow_value(1).for(:status)
384
819
  end
385
820
  end
386
821
  end