shoulda-matchers 3.0.1 → 3.1.0

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