shoulda-matchers 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -3
  4. data/CONTRIBUTING.md +60 -28
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +15 -12
  7. data/NEWS.md +111 -0
  8. data/README.md +94 -6
  9. data/Rakefile +10 -8
  10. data/custom_plan.rb +88 -0
  11. data/gemfiles/4.0.0.gemfile +1 -0
  12. data/gemfiles/4.0.0.gemfile.lock +21 -18
  13. data/gemfiles/4.0.1.gemfile +1 -0
  14. data/gemfiles/4.0.1.gemfile.lock +21 -18
  15. data/gemfiles/4.1.gemfile +1 -0
  16. data/gemfiles/4.1.gemfile.lock +21 -18
  17. data/gemfiles/4.2.gemfile +1 -0
  18. data/gemfiles/4.2.gemfile.lock +24 -21
  19. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
  20. data/lib/shoulda/matchers/active_model.rb +10 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  26. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  27. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  29. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  30. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
  31. data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
  32. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
  37. data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
  38. data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  39. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  40. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
  41. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
  42. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
  43. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
  44. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
  45. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
  46. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
  47. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
  48. data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
  49. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
  50. data/lib/shoulda/matchers/active_model/validator.rb +71 -52
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
  53. data/lib/shoulda/matchers/util.rb +43 -0
  54. data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
  55. data/lib/shoulda/matchers/version.rb +1 -1
  56. data/script/update_gem_in_all_appraisals +1 -1
  57. data/script/update_gems_in_all_appraisals +1 -1
  58. data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
  59. data/spec/acceptance/rails_integration_spec.rb +6 -2
  60. data/spec/spec_helper.rb +1 -3
  61. data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
  62. data/spec/support/tests/current_bundle.rb +21 -7
  63. data/spec/support/unit/active_record/create_table.rb +54 -0
  64. data/spec/support/unit/attribute.rb +47 -0
  65. data/spec/support/unit/capture.rb +6 -0
  66. data/spec/support/unit/change_value.rb +111 -0
  67. data/spec/support/unit/create_model_arguments/basic.rb +135 -0
  68. data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
  69. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
  70. data/spec/support/unit/helpers/active_record_versions.rb +1 -1
  71. data/spec/support/unit/helpers/class_builder.rb +61 -47
  72. data/spec/support/unit/helpers/database_helpers.rb +5 -3
  73. data/spec/support/unit/helpers/model_builder.rb +77 -97
  74. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
  75. data/spec/support/unit/load_environment.rb +12 -0
  76. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
  77. data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
  78. data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
  79. data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
  80. data/spec/support/unit/model_creators.rb +19 -0
  81. data/spec/support/unit/model_creators/active_model.rb +39 -0
  82. data/spec/support/unit/model_creators/active_record.rb +43 -0
  83. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
  84. data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
  85. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
  86. data/spec/support/unit/model_creators/basic.rb +97 -0
  87. data/spec/support/unit/rails_application.rb +1 -1
  88. data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
  89. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
  90. data/spec/support/unit/validation_matcher_scenario.rb +62 -0
  91. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
  93. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
  94. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
  95. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
  96. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
  97. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
  98. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
  99. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
  100. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
  101. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
  102. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
  103. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
  104. data/spec/unit_spec_helper.rb +10 -22
  105. data/zeus.json +11 -0
  106. metadata +64 -23
  107. data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
  108. data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
  109. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
  110. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
  111. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
  112. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -0,0 +1,45 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class AttributeChangedValueError < Shoulda::Matchers::Error
7
+ attr_accessor :matcher_name, :model, :attribute_name, :value_written,
8
+ :value_read
9
+
10
+ def message
11
+ Shoulda::Matchers.word_wrap <<-MESSAGE
12
+ The #{matcher_name} matcher attempted to set :#{attribute_name} on
13
+ #{model.name} to #{value_written.inspect}, but when the attribute was
14
+ read back, it had stored #{value_read.inspect} instead.
15
+
16
+ This creates a problem because it means that the model is behaving in a
17
+ way that is interfering with the test -- there's a mismatch between the
18
+ test that you wrote and test that we actually ran.
19
+
20
+ There are a couple of reasons why this could be happening:
21
+
22
+ * ActiveRecord is typecasting the incoming value.
23
+ * The writer method for :#{attribute_name} has been overridden so that
24
+ incoming values are changed in some way.
25
+
26
+ If this exception makes sense to you and you wish to bypass it, try
27
+ adding the `ignoring_interference_by_writer` qualifier onto the end of
28
+ your matcher. If the test still does not pass after that, then you may
29
+ need to do something different.
30
+
31
+ If you need help, feel free to ask a question on the shoulda-matchers
32
+ issues list:
33
+
34
+ http://github.com/thoughtbot/shoulda-matchers/issues
35
+ MESSAGE
36
+ end
37
+
38
+ def successful?
39
+ false
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class AttributeDoesNotExistError < Shoulda::Matchers::Error
7
+ attr_accessor :model, :attribute_name, :value
8
+
9
+ def message
10
+ Shoulda::Matchers.word_wrap <<-MESSAGE
11
+ The matcher attempted to set :#{attribute_name} on the #{model.name} to
12
+ #{value.inspect}, but that attribute does not exist.
13
+ MESSAGE
14
+ end
15
+
16
+ def successful?
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,236 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class AttributeSetter
7
+ def self.set(args)
8
+ new(args).set
9
+ end
10
+
11
+ attr_reader(
12
+ :attribute_name,
13
+ :result_of_checking,
14
+ :result_of_setting,
15
+ :value_written,
16
+ )
17
+
18
+ def initialize(args)
19
+ @args = args
20
+ @matcher_name = args.fetch(:matcher_name)
21
+ @object = args.fetch(:object)
22
+ @attribute_name = args.fetch(:attribute_name)
23
+ @value_written = args.fetch(:value)
24
+ @ignore_interference_by_writer = args.fetch(
25
+ :ignore_interference_by_writer,
26
+ Qualifiers::IgnoreInterferenceByWriter.new
27
+ )
28
+ @after_set_callback = args.fetch(:after_set_callback, -> { })
29
+
30
+ @result_of_checking = nil
31
+ @result_of_setting = nil
32
+ end
33
+
34
+ def description
35
+ description = ":#{attribute_name} to "
36
+ description << Shoulda::Matchers::Util.inspect_value(value_written)
37
+
38
+ if attribute_changed_value?
39
+ description << " -- which was read back as "
40
+ description << Shoulda::Matchers::Util.inspect_value(value_read)
41
+ description << " --"
42
+ end
43
+
44
+ description
45
+ end
46
+
47
+ def run
48
+ check && set
49
+ end
50
+
51
+ def run!
52
+ check && set!
53
+ end
54
+
55
+ def check
56
+ if attribute_exists?
57
+ @result_of_checking = successful_check
58
+ true
59
+ else
60
+ @result_of_checking = attribute_does_not_exist_error
61
+ false
62
+ end
63
+ end
64
+
65
+ def set!
66
+ if attribute_exists?
67
+ set
68
+
69
+ unless result_of_setting.successful?
70
+ raise result_of_setting
71
+ end
72
+
73
+ @result_of_checking = successful_check
74
+ @result_of_setting = successful_setting
75
+
76
+ true
77
+ else
78
+ attribute_does_not_exist!
79
+ end
80
+ end
81
+
82
+ def set
83
+ object.public_send("#{attribute_name}=", value_written)
84
+ after_set_callback.call
85
+
86
+ @result_of_checking = successful_check
87
+
88
+ if raise_attribute_changed_value_error?
89
+ @result_of_setting = attribute_changed_value_error
90
+ false
91
+ else
92
+ @result_of_setting = successful_setting
93
+ true
94
+ end
95
+ end
96
+
97
+ def failure_message
98
+ if successful?
99
+ raise "We're not supposed to be here!"
100
+ elsif result_of_setting
101
+ result_of_setting.message
102
+ else
103
+ result_of_checking.message
104
+ end
105
+ end
106
+
107
+ def successful?
108
+ successfully_checked? && successfully_set?
109
+ end
110
+
111
+ def unsuccessful?
112
+ !successful?
113
+ end
114
+
115
+ def checked?
116
+ !result_of_checking.nil?
117
+ end
118
+
119
+ def successfully_checked?
120
+ checked? && result_of_checking.successful?
121
+ end
122
+
123
+ def unsuccessfully_checked?
124
+ !successfully_checked?
125
+ end
126
+
127
+ def set?
128
+ !result_of_setting.nil?
129
+ end
130
+
131
+ def successfully_set?
132
+ set? && result_of_setting.successful?
133
+ end
134
+
135
+ def value_read
136
+ @_value_read ||= object.public_send(attribute_name)
137
+ end
138
+
139
+ def attribute_changed_value?
140
+ value_written != value_read
141
+ end
142
+
143
+ protected
144
+
145
+ attr_reader :args, :matcher_name, :object, :after_set_callback
146
+
147
+ private
148
+
149
+ def model
150
+ object.class
151
+ end
152
+
153
+ def attribute_exists?
154
+ if active_resource_object?
155
+ object.known_attributes.include?(attribute_name.to_s)
156
+ else
157
+ object.respond_to?("#{attribute_name}=")
158
+ end
159
+ end
160
+
161
+ def ignore_interference_by_writer
162
+ @ignore_interference_by_writer
163
+ end
164
+
165
+ def raise_attribute_changed_value_error?
166
+ attribute_changed_value? &&
167
+ !(attribute_is_an_enum? && value_read_is_expected_for_an_enum?) &&
168
+ !ignore_interference_by_writer.considering?(value_read)
169
+ end
170
+
171
+ def attribute_is_an_enum?
172
+ enum_values.any?
173
+ end
174
+
175
+ def value_read_is_expected_for_an_enum?
176
+ enum_values.key?(value_read) &&
177
+ enum_values[value_read] == value_written
178
+ end
179
+
180
+ def enum_values
181
+ defined_enums.fetch(attribute_name.to_s, {})
182
+ end
183
+
184
+ def defined_enums
185
+ if model.respond_to?(:defined_enums)
186
+ model.defined_enums
187
+ else
188
+ {}
189
+ end
190
+ end
191
+
192
+ def successful_check
193
+ SuccessfulCheck.new
194
+ end
195
+
196
+ def successful_setting
197
+ SuccessfulSetting.new
198
+ end
199
+
200
+ def attribute_changed_value!
201
+ raise attribute_changed_value_error
202
+ end
203
+
204
+ def attribute_changed_value_error
205
+ AttributeChangedValueError.create(
206
+ model: object.class,
207
+ attribute_name: attribute_name,
208
+ value_written: value_written,
209
+ value_read: value_read
210
+ )
211
+ end
212
+
213
+ def attribute_does_not_exist!
214
+ raise attribute_does_not_exist_error
215
+ end
216
+
217
+ def attribute_does_not_exist_error
218
+ AttributeDoesNotExistError.create(
219
+ model: object.class,
220
+ attribute_name: attribute_name,
221
+ value: value_written
222
+ )
223
+ end
224
+
225
+ def active_resource_object?
226
+ object.respond_to?(:known_attributes)
227
+ end
228
+
229
+ def model
230
+ object.class
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,62 @@
1
+ require 'forwardable'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveModel
6
+ class AllowValueMatcher
7
+ # @private
8
+ class AttributeSetterAndValidator
9
+ extend Forwardable
10
+
11
+ def_delegators(
12
+ :allow_value_matcher,
13
+ :after_setting_value_callback,
14
+ :attribute_to_check_message_against,
15
+ :context,
16
+ :expected_message,
17
+ :expects_strict?,
18
+ :ignore_interference_by_writer,
19
+ :instance,
20
+ )
21
+
22
+ def initialize(allow_value_matcher, attribute_name, value)
23
+ @allow_value_matcher = allow_value_matcher
24
+ @attribute_name = attribute_name
25
+ @value = value
26
+ @_attribute_setter = nil
27
+ @_validator = nil
28
+ end
29
+
30
+ def attribute_setter
31
+ @_attribute_setter ||= AttributeSetter.new(
32
+ matcher_name: :allow_value,
33
+ object: instance,
34
+ attribute_name: attribute_name,
35
+ value: value,
36
+ ignore_interference_by_writer: ignore_interference_by_writer,
37
+ after_set_callback: after_setting_value_callback
38
+ )
39
+ end
40
+
41
+ def attribute_setter_description
42
+ attribute_setter.description
43
+ end
44
+
45
+ def validator
46
+ @_validator ||= Validator.new(
47
+ instance,
48
+ attribute_to_check_message_against,
49
+ context: context,
50
+ expects_strict: expects_strict?,
51
+ expected_message: expected_message
52
+ )
53
+ end
54
+
55
+ protected
56
+
57
+ attr_reader :allow_value_matcher, :attribute_name, :value
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,40 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class AttributeSetters
7
+ include Enumerable
8
+
9
+ def initialize(allow_value_matcher, values)
10
+ @tuples = values.map do |attribute_name, value|
11
+ AttributeSetterAndValidator.new(
12
+ allow_value_matcher,
13
+ attribute_name,
14
+ value
15
+ )
16
+ end
17
+ end
18
+
19
+ def each(&block)
20
+ tuples.each(&block)
21
+ end
22
+
23
+ def first_failing
24
+ tuples.detect(&method(:does_not_match?))
25
+ end
26
+
27
+ protected
28
+
29
+ attr_reader :tuples
30
+
31
+ private
32
+
33
+ def does_not_match?(tuple)
34
+ !tuple.attribute_setter.set!
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,48 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class AttributeSettersAndValidators
7
+ include Enumerable
8
+
9
+ def initialize(allow_value_matcher, values)
10
+ @tuples = values.map do |attribute_name, value|
11
+ AttributeSetterAndValidator.new(
12
+ allow_value_matcher,
13
+ attribute_name,
14
+ value
15
+ )
16
+ end
17
+ end
18
+
19
+ def each(&block)
20
+ tuples.each(&block)
21
+ end
22
+
23
+ def first_passing
24
+ tuples.detect(&method(:matches?))
25
+ end
26
+
27
+ def first_failing
28
+ tuples.detect(&method(:does_not_match?))
29
+ end
30
+
31
+ protected
32
+
33
+ attr_reader :tuples
34
+
35
+ private
36
+
37
+ def matches?(tuple)
38
+ tuple.attribute_setter.set! && tuple.validator.call
39
+ end
40
+
41
+ def does_not_match?(tuple)
42
+ !matches?(tuple)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end