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
@@ -62,9 +62,10 @@ module Shoulda
62
62
  # password: 'password'
63
63
  # }
64
64
  # }
65
- # should permit(:first_name, :last_name, :email, :password).
65
+ # matcher = permit(:first_name, :last_name, :email, :password).
66
66
  # for(:create, params: params).
67
67
  # on(:user)
68
+ # assert_accepts matcher, subject
68
69
  # end
69
70
  # end
70
71
  #
@@ -132,9 +133,10 @@ module Shoulda
132
133
  # password: 'password'
133
134
  # }
134
135
  # }
135
- # should permit(:first_name, :last_name, :email, :password).
136
+ # matcher = permit(:first_name, :last_name, :email, :password).
136
137
  # for(:update, params: params).
137
138
  # on(:user)
139
+ # assert_accepts matcher, subject
138
140
  # end
139
141
  # end
140
142
  #
@@ -189,9 +191,10 @@ module Shoulda
189
191
  #
190
192
  # should "(for PUT #toggle) restrict parameters on :user to :activated" do
191
193
  # params = { id: 1, user: { activated: true } }
192
- # should permit(:activated).
194
+ # matcher = permit(:activated).
193
195
  # for(:toggle, params: params, verb: :put).
194
196
  # on(:user)
197
+ # assert_accepts matcher, subject
195
198
  # end
196
199
  # end
197
200
  #
@@ -305,18 +308,10 @@ module Shoulda
305
308
  end
306
309
  end
307
310
 
308
- def permit_called?
309
- actual_permitted_parameter_names.any?
310
- end
311
-
312
311
  def unpermitted_parameter_names
313
312
  expected_permitted_parameter_names - actual_permitted_parameter_names
314
313
  end
315
314
 
316
- def verified_permitted_parameter_names
317
- expected_permitted_parameter_names & actual_permitted_parameter_names
318
- end
319
-
320
315
  def ensure_action_and_verb_present!
321
316
  if action.blank?
322
317
  raise ActionNotDefinedError
@@ -1,8 +1,17 @@
1
1
  require 'shoulda/matchers/active_model/helpers'
2
+ require 'shoulda/matchers/active_model/qualifiers'
2
3
  require 'shoulda/matchers/active_model/validation_matcher'
4
+ require 'shoulda/matchers/active_model/validation_matcher/build_description'
3
5
  require 'shoulda/matchers/active_model/validator'
4
- require 'shoulda/matchers/active_model/strict_validator'
5
6
  require 'shoulda/matchers/active_model/allow_value_matcher'
7
+ require 'shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error'
8
+ require 'shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error'
9
+ require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setter'
10
+ require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator'
11
+ require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setters'
12
+ require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators'
13
+ require 'shoulda/matchers/active_model/allow_value_matcher/successful_check'
14
+ require 'shoulda/matchers/active_model/allow_value_matcher/successful_setting'
6
15
  require 'shoulda/matchers/active_model/disallow_value_matcher'
7
16
  require 'shoulda/matchers/active_model/validate_length_of_matcher'
8
17
  require 'shoulda/matchers/active_model/validate_inclusion_of_matcher'
@@ -58,7 +58,7 @@ module Shoulda
58
58
  # #### Caveats
59
59
  #
60
60
  # When using `allow_value` or any matchers that depend on it, you may
61
- # encounter a CouldNotSetAttributeError. This exception is raised if the
61
+ # encounter an AttributeChangedValueError. This exception is raised if the
62
62
  # matcher, in attempting to set a value on the attribute, detects that
63
63
  # the value set is different from the value that the attribute returns
64
64
  # upon reading it back.
@@ -86,7 +86,7 @@ module Shoulda
86
86
  # it do
87
87
  # foo = Foo.new
88
88
  # foo.bar = "baz"
89
- # # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
89
+ # # This will raise an AttributeChangedValueError since `foo.bar` is now "123"
90
90
  # expect(foo).not_to allow_value(nil).for(:bar)
91
91
  # end
92
92
  # end
@@ -108,7 +108,7 @@ module Shoulda
108
108
  # describe Foo do
109
109
  # it do
110
110
  # foo = Foo.new
111
- # # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
111
+ # # This will raise an AttributeChangedValueError since `foo.bar` is now "123"
112
112
  # expect(foo).not_to allow_value("abc123").for(:bar)
113
113
  # end
114
114
  # end
@@ -118,15 +118,13 @@ module Shoulda
118
118
  #
119
119
  # describe Foo do
120
120
  # # Assume that `attr` is a string
121
- # # This will raise a CouldNotSetAttributeError since `attr` typecasts `[]` to `"[]"`
121
+ # # This will raise an AttributeChangedValueError since `attr` typecasts `[]` to `"[]"`
122
122
  # it { should_not allow_value([]).for(:attr) }
123
123
  # end
124
124
  #
125
- # So when you encounter this exception, you have a couple of options:
126
- #
127
- # * If you understand the problem and wish to override this behavior to
128
- # get around this exception, you can add the
129
- # `ignoring_interference_by_writer` qualifier like so:
125
+ # Fortunately, if you understand why this is happening, and wish to get
126
+ # around this exception, it is possible to do so. You can use the
127
+ # `ignoring_interference_by_writer` qualifier like so:
130
128
  #
131
129
  # it do
132
130
  # should_not allow_value([]).
@@ -134,16 +132,11 @@ module Shoulda
134
132
  # ignoring_interference_by_writer
135
133
  # end
136
134
  #
137
- # * Note, however, that the above option will not always cause the test to
138
- # pass. In this case, this is telling you that you don't need to use
139
- # `allow_value`, or quite possibly even the validation that you're
140
- # testing altogether. In any case, we would probably make the argument
141
- # that since it's clear that something is responsible for sanitizing
142
- # incoming data before it's stored in your model, there's no need to
143
- # ensure that sanitization places the model in a valid state, if such
144
- # sanitization creates valid data. In terms of testing, the sanitization
145
- # code should probably be tested, but not the effects of that
146
- # sanitization on the validness of the model.
135
+ # Please note, however, that this qualifier won't magically cause your
136
+ # test to pass. It may just so happen that the final value that ends up
137
+ # being set causes the model to fail validation. In that case, you'll have
138
+ # to figure out what to do. You may need to write your own test, or
139
+ # perhaps even remove your test altogether.
147
140
  #
148
141
  # #### Qualifiers
149
142
  #
@@ -274,9 +267,9 @@ module Shoulda
274
267
  #
275
268
  # ##### ignoring_interference_by_writer
276
269
  #
277
- # Use `ignoring_interference_by_writer` if you've encountered a
278
- # CouldNotSetAttributeError and wish to ignore it. Please read the Caveats
279
- # section above for more information.
270
+ # Use `ignoring_interference_by_writer` to bypass an
271
+ # AttributeChangedValueError that you have encountered. Please read the
272
+ # Caveats section above for more information.
280
273
  #
281
274
  # class Address < ActiveRecord::Base
282
275
  # # Address has a zip_code field which is a string
@@ -286,16 +279,16 @@ module Shoulda
286
279
  # describe Address do
287
280
  # it do
288
281
  # should_not allow_value([]).
289
- # for(:zip_code).
290
- # ignoring_interference_by_writer
282
+ # for(:zip_code).
283
+ # ignoring_interference_by_writer
291
284
  # end
292
285
  # end
293
286
  #
294
287
  # # Minitest (Shoulda)
295
288
  # class AddressTest < ActiveSupport::TestCase
296
289
  # should_not allow_value([]).
297
- # for(:zip_code).
298
- # ignoring_interference_by_writer
290
+ # for(:zip_code).
291
+ # ignoring_interference_by_writer
299
292
  # end
300
293
  #
301
294
  # @return [AllowValueMatcher]
@@ -312,235 +305,314 @@ module Shoulda
312
305
 
313
306
  # @private
314
307
  class AllowValueMatcher
315
- # @private
316
- class CouldNotSetAttributeError < Shoulda::Matchers::Error
317
- def self.create(model, attribute, expected_value, actual_value)
318
- super(
319
- model: model,
320
- attribute: attribute,
321
- expected_value: expected_value,
322
- actual_value: actual_value
323
- )
324
- end
325
-
326
- attr_accessor :model, :attribute, :expected_value, :actual_value
327
-
328
- def message
329
- Shoulda::Matchers.word_wrap <<-MESSAGE
330
- The allow_value matcher attempted to set :#{attribute} on #{model.name} to
331
- #{expected_value.inspect}, but when the attribute was read back, it
332
- had stored #{actual_value.inspect} instead.
333
-
334
- This creates a problem because it means that the model is behaving in a way that
335
- is interfering with the test -- there's a mismatch between the test that was
336
- written and test that was actually run.
337
-
338
- There are a couple of reasons why this could be happening:
339
-
340
- * The writer method for :#{attribute} has been overridden and contains custom
341
- logic to prevent certain values from being set or change which values are
342
- stored.
343
- * ActiveRecord is typecasting the incoming value.
344
-
345
- Regardless, the fact you're seeing this message usually indicates a larger
346
- problem. Please file an issue on the GitHub repo for shoulda-matchers,
347
- including details about your model and the test you've written, and we can point
348
- you in the right direction:
349
-
350
- https://github.com/thoughtbot/shoulda-matchers/issues
351
- MESSAGE
352
- end
353
- end
354
-
355
308
  include Helpers
356
-
357
- attr_accessor :attribute_with_message
358
- attr_accessor :options
309
+ include Qualifiers::IgnoringInterferenceByWriter
310
+
311
+ attr_reader(
312
+ :after_setting_value_callback,
313
+ :attribute_to_check_message_against,
314
+ :attribute_to_set,
315
+ :context,
316
+ :instance
317
+ )
318
+
319
+ attr_writer(
320
+ :attribute_changed_value_message,
321
+ :failure_message_preface,
322
+ :values_to_preset,
323
+ )
359
324
 
360
325
  def initialize(*values)
361
- self.values_to_match = values
362
- self.options = {}
363
- self.after_setting_value_callback = -> {}
364
- self.validator = Validator.new
365
- @ignoring_interference_by_writer = false
366
- end
367
-
368
- def for(attribute)
369
- self.attribute_to_set = attribute
370
- self.attribute_to_check_message_against = attribute
326
+ super
327
+ @values_to_set = values
328
+ @options = {}
329
+ @after_setting_value_callback = -> {}
330
+ @expects_strict = false
331
+ @expects_custom_validation_message = false
332
+ @context = nil
333
+ @values_to_preset = {}
334
+ @failure_message_preface = nil
335
+ end
336
+
337
+ def for(attribute_name)
338
+ @attribute_to_set = attribute_name
339
+ @attribute_to_check_message_against = attribute_name
371
340
  self
372
341
  end
373
342
 
374
343
  def on(context)
375
- validator.context = context
344
+ if context.present?
345
+ @context = context
346
+ end
347
+
376
348
  self
377
349
  end
378
350
 
379
- def with_message(message, options={})
380
- self.options[:expected_message] = message
381
- self.options[:expected_message_values] = options.fetch(:values, {})
351
+ def with_message(message, given_options = {})
352
+ if message.present?
353
+ @expects_custom_validation_message = true
354
+ options[:expected_message] = message
355
+ options[:expected_message_values] = given_options.fetch(:values, {})
382
356
 
383
- if options.key?(:against)
384
- self.attribute_to_check_message_against = options[:against]
357
+ if given_options.key?(:against)
358
+ @attribute_to_check_message_against = given_options[:against]
359
+ end
385
360
  end
386
361
 
387
362
  self
388
363
  end
389
364
 
390
- def strict
391
- validator.strict = true
392
- self
365
+ def expected_message
366
+ if options.key?(:expected_message)
367
+ if Symbol === options[:expected_message]
368
+ default_expected_message
369
+ else
370
+ options[:expected_message]
371
+ end
372
+ end
373
+ end
374
+
375
+ def expects_custom_validation_message?
376
+ @expects_custom_validation_message
393
377
  end
394
378
 
395
- def ignoring_interference_by_writer
396
- @ignoring_interference_by_writer = true
379
+ def strict(expects_strict = true)
380
+ @expects_strict = expects_strict
397
381
  self
398
382
  end
399
383
 
384
+ def expects_strict?
385
+ @expects_strict
386
+ end
387
+
400
388
  def _after_setting_value(&callback)
401
- self.after_setting_value_callback = callback
389
+ @after_setting_value_callback = callback
402
390
  end
403
391
 
404
392
  def matches?(instance)
405
- self.instance = instance
406
- values_to_match.all? { |value| value_matches?(value) }
393
+ @instance = instance
394
+ @result = run(:first_failing)
395
+ @result.nil?
407
396
  end
408
397
 
409
398
  def does_not_match?(instance)
410
- self.instance = instance
411
- values_to_match.all? { |value| !value_matches?(value) }
399
+ @instance = instance
400
+ @result = run(:first_passing)
401
+ @result.nil?
412
402
  end
413
403
 
414
404
  def failure_message
415
- "Did not expect #{expectation},\ngot#{error_description}"
405
+ attribute_setter = result.attribute_setter
406
+
407
+ if result.attribute_setter.unsuccessfully_checked?
408
+ message = attribute_setter.failure_message
409
+ else
410
+ validator = result.validator
411
+ message = failure_message_preface.call
412
+ message << ' valid, but it was invalid instead,'
413
+
414
+ if validator.captured_validation_exception?
415
+ message << ' raising a validation exception with the message '
416
+ message << validator.validation_exception_message.inspect
417
+ message << '.'
418
+ else
419
+ message << " producing these validation errors:\n\n"
420
+ message << validator.all_formatted_validation_error_messages
421
+ end
422
+ end
423
+
424
+ if include_attribute_changed_value_message?
425
+ message << "\n\n" + attribute_changed_value_message.call
426
+ end
427
+
428
+ Shoulda::Matchers.word_wrap(message)
416
429
  end
417
430
 
418
431
  def failure_message_when_negated
419
- "Expected #{expectation},\ngot#{error_description}"
420
- end
432
+ attribute_setter = result.attribute_setter
421
433
 
422
- def description
423
- validator.allow_description(allowed_values)
424
- end
434
+ if attribute_setter.unsuccessfully_checked?
435
+ message = attribute_setter.failure_message
436
+ else
437
+ validator = result.validator
438
+ message = failure_message_preface.call + ' invalid'
439
+
440
+ if validator.type_of_message_matched?
441
+ if validator.has_messages?
442
+ message << ' and to'
443
+
444
+ if validator.captured_validation_exception?
445
+ message << ' raise a validation exception with message'
446
+ else
447
+ message << ' produce'
448
+
449
+ if expected_message.is_a?(Regexp)
450
+ message << ' a'
451
+ else
452
+ message << ' the'
453
+ end
454
+
455
+ message << ' validation error'
456
+ end
457
+
458
+ if expected_message.is_a?(Regexp)
459
+ message << ' matching '
460
+ message << Shoulda::Matchers::Util.inspect_value(
461
+ expected_message
462
+ )
463
+ else
464
+ message << " #{expected_message.inspect}"
465
+ end
466
+
467
+ unless validator.captured_validation_exception?
468
+ message << " on :#{attribute_to_check_message_against}"
469
+ end
470
+
471
+ message << '. The record was indeed invalid, but'
472
+
473
+ if validator.captured_validation_exception?
474
+ message << ' the exception message was '
475
+ message << validator.validation_exception_message.inspect
476
+ message << ' instead.'
477
+ else
478
+ message << " it produced these validation errors instead:\n\n"
479
+ message << validator.all_formatted_validation_error_messages
480
+ end
481
+ else
482
+ message << ', but it was valid instead.'
483
+ end
484
+ elsif validator.captured_validation_exception?
485
+ message << ' and to produce validation errors, but the record'
486
+ message << ' raised a validation exception instead.'
487
+ else
488
+ message << ' and to raise a validation exception, but the record'
489
+ message << ' produced validation errors instead.'
490
+ end
491
+ end
425
492
 
426
- protected
493
+ if include_attribute_changed_value_message?
494
+ message << "\n\n" + attribute_changed_value_message.call
495
+ end
427
496
 
428
- attr_reader :instance, :attribute_to_check_message_against
429
- attr_accessor :values_to_match, :attribute_to_set, :value,
430
- :matched_error, :after_setting_value_callback, :validator
497
+ Shoulda::Matchers.word_wrap(message)
498
+ end
431
499
 
432
- def instance=(instance)
433
- @instance = instance
434
- validator.record = instance
500
+ def description
501
+ ValidationMatcher::BuildDescription.call(self, simple_description)
435
502
  end
436
503
 
437
- def attribute_to_check_message_against=(attribute)
438
- @attribute_to_check_message_against = attribute
439
- validator.attribute = attribute
504
+ def simple_description
505
+ "allow :#{attribute_to_set} to be #{inspected_values_to_set}"
440
506
  end
441
507
 
442
- def ignoring_interference_by_writer?
443
- @ignoring_interference_by_writer
508
+ def model
509
+ instance.class
444
510
  end
445
511
 
446
- def value_matches?(value)
447
- self.value = value
448
- set_attribute(value)
449
- !(errors_match? || any_range_error_occurred?)
512
+ def last_attribute_setter_used
513
+ result.attribute_setter
450
514
  end
451
515
 
452
- def set_attribute(value)
453
- instance.__send__("#{attribute_to_set}=", value)
454
- ensure_that_attribute_was_set!(value)
455
- after_setting_value_callback.call
516
+ def last_value_set
517
+ last_attribute_setter_used.value_written
456
518
  end
457
519
 
458
- def ensure_that_attribute_was_set!(expected_value)
459
- actual_value = instance.__send__(attribute_to_set)
520
+ protected
460
521
 
461
- if expected_value != actual_value && !ignoring_interference_by_writer?
462
- raise CouldNotSetAttributeError.create(
463
- instance.class,
464
- attribute_to_set,
465
- expected_value,
466
- actual_value
467
- )
468
- end
469
- end
522
+ attr_reader(
523
+ :options,
524
+ :result,
525
+ :values_to_preset,
526
+ :values_to_set,
527
+ )
470
528
 
471
- def errors_match?
472
- has_messages? && errors_for_attribute_match?
473
- end
529
+ private
474
530
 
475
- def has_messages?
476
- validator.has_messages?
531
+ def run(strategy)
532
+ attribute_setters_for_values_to_preset.first_failing ||
533
+ attribute_setters_and_validators_for_values_to_set.public_send(strategy)
477
534
  end
478
535
 
479
- def errors_for_attribute_match?
480
- if expected_message
481
- self.matched_error = errors_match_regexp? || errors_match_string?
482
- else
483
- errors_for_attribute.compact.any?
484
- end
536
+ def failure_message_preface
537
+ @failure_message_preface || method(:default_failure_message_preface)
485
538
  end
486
539
 
487
- def errors_for_attribute
488
- validator.formatted_messages
489
- end
540
+ def default_failure_message_preface
541
+ ''.tap do |preface|
542
+ if descriptions_for_preset_values.any?
543
+ preface << 'After setting '
544
+ preface << descriptions_for_preset_values.to_sentence
545
+ preface << ', then '
546
+ else
547
+ preface << 'After '
548
+ end
490
549
 
491
- def errors_match_regexp?
492
- if Regexp === expected_message
493
- errors_for_attribute.detect { |e| e =~ expected_message }
550
+ preface << 'setting '
551
+ preface << description_for_resulting_attribute_setter
552
+
553
+ unless preface.end_with?('--')
554
+ preface << ','
555
+ end
556
+
557
+ preface << " the matcher expected the #{model.name} to be"
494
558
  end
495
559
  end
496
560
 
497
- def errors_match_string?
498
- if errors_for_attribute.include?(expected_message)
499
- expected_message
500
- end
561
+ def include_attribute_changed_value_message?
562
+ !ignore_interference_by_writer.never? &&
563
+ result.attribute_setter.attribute_changed_value?
501
564
  end
502
565
 
503
- def any_range_error_occurred?
504
- validator.captured_range_error?
566
+ def attribute_changed_value_message
567
+ @attribute_changed_value_message ||
568
+ method(:default_attribute_changed_value_message)
505
569
  end
506
570
 
507
- def expectation
508
- parts = [
509
- expected_messages_description,
510
- "when #{attribute_to_set} is set to #{value.inspect}"
511
- ]
571
+ def default_attribute_changed_value_message
572
+ <<-MESSAGE.strip
573
+ As indicated in the message above, :#{result.attribute_setter.attribute_name}
574
+ seems to be changing certain values as they are set, and this could have
575
+ something to do with why this test is failing. If you've overridden the writer
576
+ method for this attribute, then you may need to change it to make this test
577
+ pass, or do something else entirely.
578
+ MESSAGE
579
+ end
512
580
 
513
- parts.join(' ').squeeze(' ')
581
+ def descriptions_for_preset_values
582
+ attribute_setters_for_values_to_preset.
583
+ map(&:attribute_setter_description)
514
584
  end
515
585
 
516
- def expected_messages_description
517
- validator.expected_messages_description(expected_message)
586
+ def description_for_resulting_attribute_setter
587
+ result.attribute_setter_description
518
588
  end
519
589
 
520
- def error_description
521
- validator.messages_description
590
+ def attribute_setters_for_values_to_preset
591
+ @_attribute_setters_for_values_to_preset ||=
592
+ AttributeSetters.new(self, values_to_preset)
522
593
  end
523
594
 
524
- def allowed_values
525
- if values_to_match.length > 1
526
- "any of [#{values_to_match.map(&:inspect).join(', ')}]"
527
- else
528
- values_to_match.first.inspect
529
- end
595
+ def attribute_setters_and_validators_for_values_to_set
596
+ @_attribute_setters_and_validators_for_values_to_set ||=
597
+ AttributeSettersAndValidators.new(
598
+ self,
599
+ values_to_set.map { |value| [attribute_to_set, value] }
600
+ )
530
601
  end
531
602
 
532
- def expected_message
533
- if options.key?(:expected_message)
534
- if Symbol === options[:expected_message]
535
- default_expected_message
536
- else
537
- options[:expected_message]
538
- end
539
- end
603
+ def inspected_values_to_set
604
+ Shoulda::Matchers::Util.inspect_values(values_to_set).to_sentence(
605
+ two_words_connector: " or ",
606
+ last_word_connector: ", or"
607
+ )
540
608
  end
541
609
 
542
610
  def default_expected_message
543
- validator.expected_message_from(default_attribute_message)
611
+ if expects_strict?
612
+ "#{human_attribute_name} #{default_attribute_message}"
613
+ else
614
+ default_attribute_message
615
+ end
544
616
  end
545
617
 
546
618
  def default_attribute_message
@@ -563,6 +635,12 @@ https://github.com/thoughtbot/shoulda-matchers/issues
563
635
  def model_name
564
636
  instance.class.to_s.underscore
565
637
  end
638
+
639
+ def human_attribute_name
640
+ instance.class.human_attribute_name(
641
+ attribute_to_check_message_against
642
+ )
643
+ end
566
644
  end
567
645
  end
568
646
  end