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
@@ -32,14 +32,42 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
32
32
  expect(validating_absence_of(:attr, {}, type: type)).
33
33
  to validate_absence_of(:attr)
34
34
  end
35
+
36
+ it_supports(
37
+ 'ignoring_interference_by_writer',
38
+ tests: {
39
+ accept_if_qualified_but_changing_value_does_not_interfere: {
40
+ changing_values_with: :next_value
41
+ },
42
+ }
43
+ )
44
+
45
+ define_method(:validation_matcher_scenario_args) do |*args|
46
+ super(*args).deep_merge(column_type: type)
47
+ end
35
48
  end
36
49
  end
50
+
51
+ def validation_matcher_scenario_args
52
+ super.deep_merge(model_creator: :active_record)
53
+ end
37
54
  end
38
55
 
39
56
  context 'a model without an absence validation' do
40
- it 'rejects' do
41
- model = define_model(:example, attr: :string).new
42
- expect(model).not_to validate_absence_of(:attr)
57
+ it 'rejects with the correct failure message' do
58
+ record = define_model(:example, attr: :string).new
59
+
60
+ message = <<-MESSAGE
61
+ Example did not properly validate that :attr is empty/falsy.
62
+ After setting :attr to ‹"an arbitrary value"›, the matcher expected
63
+ the Example to be invalid, but it was valid instead.
64
+ MESSAGE
65
+
66
+ assertion = lambda do
67
+ expect(record).to validate_absence_of(:attr)
68
+ end
69
+
70
+ expect(&assertion).to fail_with_message(message)
43
71
  end
44
72
  end
45
73
 
@@ -51,17 +79,34 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
51
79
  it 'does not override the default message with a blank' do
52
80
  expect(active_model_validating_absence_of(:attr)).to validate_absence_of(:attr).with_message(nil)
53
81
  end
82
+
83
+ it_supports(
84
+ 'ignoring_interference_by_writer',
85
+ tests: {
86
+ accept_if_qualified_but_changing_value_does_not_interfere: {
87
+ changing_values_with: :upcase
88
+ },
89
+ }
90
+ )
91
+
92
+ def validation_matcher_scenario_args
93
+ super.deep_merge(model_creator: :active_model)
94
+ end
54
95
  end
55
96
 
56
97
  context 'an ActiveModel class without an absence validation' do
57
- it 'rejects' do
58
- expect(active_model_with(:attr)).not_to validate_absence_of(:attr)
59
- end
98
+ it 'rejects with the correct failure message' do
99
+ message = <<-MESSAGE
100
+ Example did not properly validate that :attr is empty/falsy.
101
+ After setting :attr to ‹"an arbitrary value"›, the matcher expected
102
+ the Example to be invalid, but it was valid instead.
103
+ MESSAGE
60
104
 
61
- it 'provides the correct failure message' do
62
- message = %{Expected errors to include "must be blank" when attr is set to "an arbitrary value",\ngot no errors}
105
+ assertion = lambda do
106
+ expect(active_model_with(:attr)).to validate_absence_of(:attr)
107
+ end
63
108
 
64
- expect { expect(active_model_with(:attr)).to validate_absence_of(:attr) }.to fail_with_message(message)
109
+ expect(&assertion).to fail_with_message(message)
65
110
  end
66
111
  end
67
112
 
@@ -69,6 +114,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
69
114
  it 'requires the attribute to not be set' do
70
115
  expect(having_many(:children, absence: true)).to validate_absence_of(:children)
71
116
  end
117
+
118
+ it_supports(
119
+ 'ignoring_interference_by_writer',
120
+ tests: {
121
+ accept_if_qualified_but_changing_value_does_not_interfere: {
122
+ changing_values_with: :next_value
123
+ },
124
+ }
125
+ )
126
+
127
+ def validation_matcher_scenario_args
128
+ super.deep_merge(model_creator: :"active_record/has_many")
129
+ end
72
130
  end
73
131
 
74
132
  context 'a has_many association without an absence validation' do
@@ -83,12 +141,36 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
83
141
  model = having_and_belonging_to_many(:children, absence: true)
84
142
  expect(model).to validate_absence_of(:children)
85
143
  end
144
+
145
+ it_supports(
146
+ 'ignoring_interference_by_writer',
147
+ tests: {
148
+ accept_if_qualified_but_changing_value_does_not_interfere: {
149
+ changing_values_with: :next_value
150
+ },
151
+ }
152
+ )
153
+
154
+ def validation_matcher_scenario_args
155
+ super.deep_merge(model_creator: :"active_record/habtm")
156
+ end
86
157
  end
87
158
 
88
159
  context 'a non-absent has_and_belongs_to_many association' do
89
- it 'rejects' do
160
+ it 'rejects with the correct failure message' do
90
161
  model = having_and_belonging_to_many(:children, absence: false)
91
- expect(model).not_to validate_absence_of(:children)
162
+
163
+ message = <<-MESSAGE
164
+ Parent did not properly validate that :children is empty/falsy.
165
+ After setting :children to ‹[#<Child id: nil>]›, the matcher expected
166
+ the Parent to be invalid, but it was valid instead.
167
+ MESSAGE
168
+
169
+ assertion = lambda do
170
+ expect(model).to validate_absence_of(:children)
171
+ end
172
+
173
+ expect(&assertion).to fail_with_message(message)
92
174
  end
93
175
  end
94
176
 
@@ -119,14 +201,28 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
119
201
  end
120
202
  end
121
203
 
122
- def validating_absence_of(attr, validation_options = {}, given_column_options = {})
204
+ def define_model_validating_absence_of(attr, validation_options = {}, given_column_options = {})
123
205
  default_column_options = { type: :string, options: {} }
124
206
  column_options = default_column_options.merge(given_column_options)
125
207
 
126
- define_model :example, attr => column_options do
127
- validates_absence_of attr, validation_options
128
- end.new
208
+ define_model :example, attr => column_options do |model|
209
+ model.validates_absence_of(attr, validation_options)
210
+
211
+ if block_given?
212
+ yield model
213
+ end
214
+ end
215
+ end
216
+
217
+ def validating_absence_of(attr, validation_options = {}, given_column_options = {})
218
+ model = define_model_validating_absence_of(
219
+ attr,
220
+ validation_options,
221
+ given_column_options
222
+ )
223
+ model.new
129
224
  end
225
+ alias_method :build_record_validating_absence_of, :validating_absence_of
130
226
 
131
227
  def active_model_with(attr, &block)
132
228
  define_active_model_class('Example', accessors: [attr], &block).new
@@ -162,5 +258,9 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
162
258
  end
163
259
  end.new
164
260
  end
261
+
262
+ def validation_matcher_scenario_args
263
+ super.deep_merge(matcher_name: :validate_absence_of)
264
+ end
165
265
  end
166
266
  end
@@ -9,6 +9,32 @@ describe Shoulda::Matchers::ActiveModel::ValidateAcceptanceOfMatcher, type: :mod
9
9
  it 'does not overwrite the default message with nil' do
10
10
  expect(record_validating_acceptance).to matcher.with_message(nil)
11
11
  end
12
+
13
+ it_supports(
14
+ 'ignoring_interference_by_writer',
15
+ tests: {
16
+ accept_if_qualified_but_changing_value_does_not_interfere: {
17
+ changing_values_with: :never_falsy,
18
+ },
19
+ reject_if_qualified_but_changing_value_interferes: {
20
+ model_name: 'Example',
21
+ attribute_name: :attr,
22
+ changing_values_with: :always_nil,
23
+ expected_message: <<-MESSAGE.strip
24
+ Example did not properly validate that :attr has been set to "1".
25
+ After setting :attr to ‹false› -- which was read back as ‹nil› -- the
26
+ matcher expected the Example to be invalid, but it was valid instead.
27
+
28
+ As indicated in the message above, :attr seems to be changing certain
29
+ values as they are set, and this could have something to do with why
30
+ this test is failing. If you've overridden the writer method for this
31
+ attribute, then you may need to change it to make this test pass, or
32
+ do something else entirely.
33
+ MESSAGE
34
+ },
35
+ },
36
+ model_creator: :active_model
37
+ )
12
38
  end
13
39
 
14
40
  context 'a model without an acceptance validation' do
@@ -33,8 +59,9 @@ describe Shoulda::Matchers::ActiveModel::ValidateAcceptanceOfMatcher, type: :mod
33
59
  validate_acceptance_of(:attr)
34
60
  end
35
61
 
36
- def model_validating_nothing(&block)
37
- define_active_model_class(:example, accessors: [:attr], &block)
62
+ def model_validating_nothing(options = {}, &block)
63
+ attribute_name = options.fetch(:attribute_name, :attr)
64
+ define_active_model_class(:example, accessors: [attribute_name], &block)
38
65
  end
39
66
 
40
67
  def record_validating_nothing
@@ -42,12 +69,23 @@ describe Shoulda::Matchers::ActiveModel::ValidateAcceptanceOfMatcher, type: :mod
42
69
  end
43
70
 
44
71
  def model_validating_acceptance(options = {})
45
- model_validating_nothing do
46
- validates_acceptance_of :attr, options
72
+ attribute_name = options.fetch(:attribute_name, :attr)
73
+
74
+ model_validating_nothing(attribute_name: attribute_name) do
75
+ validates_acceptance_of attribute_name, options
47
76
  end
48
77
  end
49
78
 
79
+ alias_method :define_model_validating_acceptance, :model_validating_acceptance
80
+
50
81
  def record_validating_acceptance(options = {})
51
82
  model_validating_acceptance(options).new
52
83
  end
84
+
85
+ alias_method :build_record_validating_acceptance,
86
+ :record_validating_acceptance
87
+
88
+ def validation_matcher_scenario_args
89
+ { matcher_name: :validate_acceptance_of }
90
+ end
53
91
  end
@@ -6,7 +6,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateConfirmationOfMatcher, type: :m
6
6
  context '#description' do
7
7
  it 'states that the confirmation must match its base attribute' do
8
8
  builder = builder_for_record_validating_confirmation
9
- message = "require #{builder.confirmation_attribute} to match #{builder.attribute_to_confirm}"
9
+ message = "validate that :#{builder.confirmation_attribute} matches :#{builder.attribute_to_confirm}"
10
10
  matcher = described_class.new(builder.attribute_to_confirm)
11
11
  expect(matcher.description).to eq(message)
12
12
  end
@@ -27,13 +27,95 @@ describe Shoulda::Matchers::ActiveModel::ValidateConfirmationOfMatcher, type: :m
27
27
  with_message(nil)
28
28
  end
29
29
  end
30
+
31
+ it_supports(
32
+ 'ignoring_interference_by_writer',
33
+ tests: {
34
+ reject_if_qualified_but_changing_value_interferes: {
35
+ model_name: 'Example',
36
+ attribute_name: :password,
37
+ changing_values_with: :next_value,
38
+ expected_message: <<-MESSAGE.strip
39
+ Example did not properly validate that :password_confirmation matches
40
+ :password.
41
+ After setting :password_confirmation to ‹"same value"›, then setting
42
+ :password to ‹"same value"› -- which was read back as ‹"same valuf"›
43
+ -- the matcher expected the Example to be valid, but it was invalid
44
+ instead, producing these validation errors:
45
+
46
+ * password_confirmation: ["doesn't match Password"]
47
+
48
+ As indicated in the message above, :password seems to be changing
49
+ certain values as they are set, and this could have something to do
50
+ with why this test is failing. If you've overridden the writer method
51
+ for this attribute, then you may need to change it to make this test
52
+ pass, or do something else entirely.
53
+ MESSAGE
54
+ },
55
+ },
56
+ model_creator: :active_model
57
+ )
58
+ end
59
+
60
+ context 'when the model does not have a confirmation attribute' do
61
+ it 'raises an AttributeDoesNotExistError' do
62
+ model = define_model(:example)
63
+
64
+ assertion = lambda do
65
+ expect(model.new).to validate_confirmation_of(:attribute_to_confirm)
66
+ end
67
+
68
+ message = <<-MESSAGE.rstrip
69
+ The matcher attempted to set :attribute_to_confirm_confirmation on the
70
+ Example to "some value", but that attribute does not exist.
71
+ MESSAGE
72
+
73
+ expect(&assertion).to raise_error(
74
+ Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeDoesNotExistError,
75
+ message
76
+ )
77
+ end
78
+ end
79
+
80
+ context 'when the model does not have the attribute under test' do
81
+ it 'raises an AttributeDoesNotExistError' do
82
+ model = define_model(:example, attribute_to_confirm_confirmation: :string)
83
+
84
+ assertion = lambda do
85
+ expect(model.new).to validate_confirmation_of(:attribute_to_confirm)
86
+ end
87
+
88
+ message = <<-MESSAGE.rstrip
89
+ The matcher attempted to set :attribute_to_confirm on the Example to
90
+ "different value", but that attribute does not exist.
91
+ MESSAGE
92
+
93
+ expect(&assertion).to raise_error(
94
+ Shoulda::Matchers::ActiveModel::AllowValueMatcher::AttributeDoesNotExistError,
95
+ message
96
+ )
97
+ end
30
98
  end
31
99
 
32
- context 'when the model does not have a confirmation validation' do
33
- it 'fails' do
34
- model = define_model(:example, attribute_to_confirm: :string)
35
- record = model.new
36
- expect(record).not_to validate_confirmation_of(:attribute_to_confirm)
100
+ context 'when the model has all attributes, but does not have the validation' do
101
+ it 'fails with an appropriate failure message' do
102
+ model = define_model(:example, attribute_to_confirm: :string) do
103
+ attr_accessor :attribute_to_confirm_confirmation
104
+ end
105
+
106
+ assertion = lambda do
107
+ expect(model.new).to validate_confirmation_of(:attribute_to_confirm)
108
+ end
109
+
110
+ message = <<-MESSAGE
111
+ Example did not properly validate that
112
+ :attribute_to_confirm_confirmation matches :attribute_to_confirm.
113
+ After setting :attribute_to_confirm_confirmation to ‹"some value"›,
114
+ then setting :attribute_to_confirm to ‹"different value"›, the matcher
115
+ expected the Example to be invalid, but it was valid instead.
116
+ MESSAGE
117
+
118
+ expect(&assertion).to fail_with_message(message)
37
119
  end
38
120
  end
39
121
 
@@ -60,4 +142,8 @@ describe Shoulda::Matchers::ActiveModel::ValidateConfirmationOfMatcher, type: :m
60
142
  to validate_confirmation_of(builder.attribute_to_confirm)
61
143
  end
62
144
  end
145
+
146
+ def validation_matcher_scenario_args
147
+ super.deep_merge(matcher_name: :validate_confirmation_of)
148
+ end
63
149
  end
@@ -16,6 +16,41 @@ describe Shoulda::Matchers::ActiveModel::ValidateExclusionOfMatcher, type: :mode
16
16
  expect(validating_exclusion(in: 2..5)).
17
17
  to validate_exclusion_of(:attr).in_range(2..5).with_message(nil)
18
18
  end
19
+
20
+ it_supports(
21
+ 'ignoring_interference_by_writer',
22
+ tests: {
23
+ reject_if_qualified_but_changing_value_interferes: {
24
+ model_name: 'Example',
25
+ attribute_name: :attr,
26
+ changing_values_with: :next_value,
27
+ expected_message: <<-MESSAGE.strip
28
+ Example did not properly validate that :attr lies outside the range ‹2›
29
+ to ‹5›.
30
+ After setting :attr to ‹1› -- which was read back as ‹2› -- the
31
+ matcher expected the Example to be valid, but it was invalid instead,
32
+ producing these validation errors:
33
+
34
+ * attr: ["is reserved"]
35
+
36
+ As indicated in the message above, :attr seems to be changing certain
37
+ values as they are set, and this could have something to do with why
38
+ this test is failing. If you've overridden the writer method for this
39
+ attribute, then you may need to change it to make this test pass, or
40
+ do something else entirely.
41
+ MESSAGE
42
+ },
43
+ },
44
+ model_creator: :active_model
45
+ ) do
46
+ def validation_matcher_scenario_args
47
+ super.deep_merge(validation_options: { in: 2..5 })
48
+ end
49
+
50
+ def configure_validation_matcher(matcher)
51
+ matcher.in_range(2..5)
52
+ end
53
+ end
19
54
  end
20
55
 
21
56
  context 'an attribute which must be excluded from a range with excluded end' do
@@ -57,6 +92,14 @@ describe Shoulda::Matchers::ActiveModel::ValidateExclusionOfMatcher, type: :mode
57
92
  expect(model).to validate_exclusion_of(:attr).in_range(2...5).
58
93
  with_message(/should be out of this range/)
59
94
  end
95
+
96
+ it 'has correct description' do
97
+ matcher = validate_exclusion_of(:attr).in_range(1..10)
98
+
99
+ expect(matcher.description).to eq(
100
+ 'validate that :attr lies outside the range ‹1› to ‹10›'
101
+ )
102
+ end
60
103
  end
61
104
 
62
105
  context 'an attribute which must be excluded from an array' do
@@ -75,21 +118,90 @@ describe Shoulda::Matchers::ActiveModel::ValidateExclusionOfMatcher, type: :mode
75
118
  not_to validate_exclusion_of(:attr).in_array(%w(cat dog))
76
119
  end
77
120
 
78
- it 'has correct description' do
79
- expect(validate_exclusion_of(:attr).in_array([true, 'dog']).description).
80
- to eq 'ensure exclusion of attr in [true, "dog"]'
121
+ context 'when there is one value' do
122
+ it 'has correct description' do
123
+ expect(validate_exclusion_of(:attr).in_array([true]).description).
124
+ to eq 'validate that :attr is not ‹true›'
125
+ end
81
126
  end
82
127
 
83
- def validating_exclusion(options)
84
- define_model(:example, attr: :string) do
85
- validates_exclusion_of :attr, options
86
- end.new
128
+ context 'when there are two values' do
129
+ it 'has correct description' do
130
+ matcher = validate_exclusion_of(:attr).in_array([true, 'dog'])
131
+
132
+ expect(matcher.description).to eq(
133
+ 'validate that :attr is neither ‹true› nor ‹"dog"›'
134
+ )
135
+ end
136
+ end
137
+
138
+ context 'when there are three or more values' do
139
+ it 'has correct description' do
140
+ matcher = validate_exclusion_of(:attr).in_array([true, 'dog', 'cat'])
141
+
142
+ expect(matcher.description).to eq(
143
+ 'validate that :attr is neither ‹true›, ‹"dog"›, nor ‹"cat"›'
144
+ )
145
+ end
146
+ end
147
+
148
+ it_supports(
149
+ 'ignoring_interference_by_writer',
150
+ tests: {
151
+ reject_if_qualified_but_changing_value_interferes: {
152
+ model_name: 'Example',
153
+ attribute_name: :attr,
154
+ changing_values_with: :next_value,
155
+ expected_message: <<-MESSAGE.strip
156
+ Example did not properly validate that :attr is neither ‹"one"› nor
157
+ ‹"two"›.
158
+ After setting :attr to ‹"one"› -- which was read back as ‹"onf"› --
159
+ the matcher expected the Example to be invalid, but it was valid
160
+ instead.
161
+
162
+ As indicated in the message above, :attr seems to be changing certain
163
+ values as they are set, and this could have something to do with why
164
+ this test is failing. If you've overridden the writer method for this
165
+ attribute, then you may need to change it to make this test pass, or
166
+ do something else entirely.
167
+ MESSAGE
168
+ },
169
+ },
170
+ model_creator: :active_model
171
+ ) do
172
+ def validation_matcher_scenario_args
173
+ super.deep_merge(validation_options: { in: ['one', 'two'] })
174
+ end
175
+
176
+ def configure_validation_matcher(matcher)
177
+ matcher.in_array(['one', 'two'])
178
+ end
179
+ end
180
+
181
+ def define_model_validating_exclusion(options)
182
+ options = options.dup
183
+ column_type = options.delete(:column_type) { :string }
184
+ super options.merge(column_type: column_type)
185
+ end
186
+ end
187
+
188
+ def define_model_validating_exclusion(options)
189
+ options = options.dup
190
+ attribute_name = options.delete(:attribute_name) { :attr }
191
+ column_type = options.delete(:column_type) { :integer }
192
+
193
+ define_model(:example, attribute_name => column_type) do |model|
194
+ model.validates_exclusion_of(attribute_name, options)
87
195
  end
88
196
  end
89
197
 
90
198
  def validating_exclusion(options)
91
- define_model(:example, attr: :integer) do
92
- validates_exclusion_of :attr, options
93
- end.new
199
+ define_model_validating_exclusion(options).new
200
+ end
201
+
202
+ alias_method :build_record_validating_exclusion, :validating_exclusion
203
+
204
+ def validation_matcher_scenario_args
205
+ super.deep_merge(matcher_name: :validate_exclusion_of)
94
206
  end
95
207
  end