shoulda-matchers 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -3
  4. data/CONTRIBUTING.md +60 -28
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +15 -12
  7. data/NEWS.md +111 -0
  8. data/README.md +94 -6
  9. data/Rakefile +10 -8
  10. data/custom_plan.rb +88 -0
  11. data/gemfiles/4.0.0.gemfile +1 -0
  12. data/gemfiles/4.0.0.gemfile.lock +21 -18
  13. data/gemfiles/4.0.1.gemfile +1 -0
  14. data/gemfiles/4.0.1.gemfile.lock +21 -18
  15. data/gemfiles/4.1.gemfile +1 -0
  16. data/gemfiles/4.1.gemfile.lock +21 -18
  17. data/gemfiles/4.2.gemfile +1 -0
  18. data/gemfiles/4.2.gemfile.lock +24 -21
  19. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
  20. data/lib/shoulda/matchers/active_model.rb +10 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  26. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  27. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  29. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  30. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
  31. data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
  32. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
  37. data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
  38. data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  39. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  40. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
  41. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
  42. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
  43. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
  44. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
  45. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
  46. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
  47. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
  48. data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
  49. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
  50. data/lib/shoulda/matchers/active_model/validator.rb +71 -52
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
  53. data/lib/shoulda/matchers/util.rb +43 -0
  54. data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
  55. data/lib/shoulda/matchers/version.rb +1 -1
  56. data/script/update_gem_in_all_appraisals +1 -1
  57. data/script/update_gems_in_all_appraisals +1 -1
  58. data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
  59. data/spec/acceptance/rails_integration_spec.rb +6 -2
  60. data/spec/spec_helper.rb +1 -3
  61. data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
  62. data/spec/support/tests/current_bundle.rb +21 -7
  63. data/spec/support/unit/active_record/create_table.rb +54 -0
  64. data/spec/support/unit/attribute.rb +47 -0
  65. data/spec/support/unit/capture.rb +6 -0
  66. data/spec/support/unit/change_value.rb +111 -0
  67. data/spec/support/unit/create_model_arguments/basic.rb +135 -0
  68. data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
  69. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
  70. data/spec/support/unit/helpers/active_record_versions.rb +1 -1
  71. data/spec/support/unit/helpers/class_builder.rb +61 -47
  72. data/spec/support/unit/helpers/database_helpers.rb +5 -3
  73. data/spec/support/unit/helpers/model_builder.rb +77 -97
  74. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
  75. data/spec/support/unit/load_environment.rb +12 -0
  76. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
  77. data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
  78. data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
  79. data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
  80. data/spec/support/unit/model_creators.rb +19 -0
  81. data/spec/support/unit/model_creators/active_model.rb +39 -0
  82. data/spec/support/unit/model_creators/active_record.rb +43 -0
  83. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
  84. data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
  85. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
  86. data/spec/support/unit/model_creators/basic.rb +97 -0
  87. data/spec/support/unit/rails_application.rb +1 -1
  88. data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
  89. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
  90. data/spec/support/unit/validation_matcher_scenario.rb +62 -0
  91. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
  93. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
  94. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
  95. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
  96. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
  97. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
  98. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
  99. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
  100. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
  101. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
  102. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
  103. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
  104. data/spec/unit_spec_helper.rb +10 -22
  105. data/zeus.json +11 -0
  106. metadata +64 -23
  107. data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
  108. data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
  109. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
  110. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
  111. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
  112. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -0,0 +1,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