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
@@ -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