shoulda-matchers 5.1.0 → 6.4.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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +41 -18
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
- data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
- data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
- data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
- data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
- data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -7
- data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +29 -14
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +65 -10
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +76 -86
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
- data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
- data/lib/shoulda/matchers/active_model/validator.rb +4 -0
- data/lib/shoulda/matchers/active_model.rb +4 -1
- data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
- data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
- data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
- data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +338 -30
- data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
- data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
- data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
- data/lib/shoulda/matchers/active_record.rb +2 -0
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
- data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
- data/lib/shoulda/matchers/doublespeak.rb +0 -1
- data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
- data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
- data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
- data/lib/shoulda/matchers/rails_shim.rb +22 -6
- data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
- data/lib/shoulda/matchers/util.rb +18 -20
- data/lib/shoulda/matchers/version.rb +1 -1
- data/lib/shoulda/matchers.rb +2 -2
- data/shoulda-matchers.gemspec +2 -2
- metadata +15 -9
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -157
@@ -108,7 +108,7 @@ module Shoulda
|
|
108
108
|
obj
|
109
109
|
end
|
110
110
|
elsif array_column?
|
111
|
-
['an
|
111
|
+
['an arbitrary value']
|
112
112
|
elsif enum_column?
|
113
113
|
enum_values.first
|
114
114
|
else
|
@@ -118,6 +118,7 @@ module Shoulda
|
|
118
118
|
when :datetime, :time, :timestamp then Time.current
|
119
119
|
when :date then Date.new
|
120
120
|
when :binary then '0'
|
121
|
+
when :uuid then SecureRandom.uuid
|
121
122
|
else 'an arbitrary value'
|
122
123
|
end
|
123
124
|
end
|
@@ -142,12 +143,6 @@ module Shoulda
|
|
142
143
|
@subject.class.reflect_on_association(@attribute)
|
143
144
|
end
|
144
145
|
|
145
|
-
def array_column?
|
146
|
-
@subject.class.respond_to?(:columns_hash) &&
|
147
|
-
@subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
|
148
|
-
@subject.class.columns_hash[@attribute.to_s].array
|
149
|
-
end
|
150
|
-
|
151
146
|
def enum_column?
|
152
147
|
@subject.class.respond_to?(:defined_enums) &&
|
153
148
|
@subject.class.defined_enums.key?(@attribute.to_s)
|
@@ -0,0 +1,532 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel
|
4
|
+
# The `validate_comparison_of` matcher tests usage of the
|
5
|
+
# `validates_comparison_of` validation.
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# include ActiveModel::Model
|
9
|
+
# attr_accessor :gpa
|
10
|
+
#
|
11
|
+
# validates_comparison_of :gpa, greater_than: 10
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# # RSpec
|
15
|
+
# RSpec.describe Person, type: :model do
|
16
|
+
# it { should validate_comparison_of(:gpa).is_greater_than(10) }
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # Minitest (Shoulda)
|
20
|
+
# class PersonTest < ActiveSupport::TestCase
|
21
|
+
# should validate_comparison_of(:gpa).is_greater_than(10)
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# #### Qualifiers
|
25
|
+
#
|
26
|
+
# ##### on
|
27
|
+
#
|
28
|
+
# Use `on` if your validation applies only under a certain context.
|
29
|
+
#
|
30
|
+
# class Person
|
31
|
+
# include ActiveModel::Model
|
32
|
+
# attribute :number_of_dependents, :integer
|
33
|
+
# attr_accessor :number_of_dependents
|
34
|
+
#
|
35
|
+
# validates_comparison_of :number_of_dependents, on: :create, greater_than: 0
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # RSpec
|
39
|
+
# RSpec.describe Person, type: :model do
|
40
|
+
# it do
|
41
|
+
# should validate_comparison_of(:number_of_dependents).
|
42
|
+
# is_greater_than(0).
|
43
|
+
# on(:create)
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # Minitest (Shoulda)
|
48
|
+
# class PersonTest < ActiveSupport::TestCase
|
49
|
+
# should validate_comparison_of(:number_of_dependents).is_greater_than(0).on(:create)
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# ##### is_less_than
|
53
|
+
#
|
54
|
+
# Use `is_less_than` to test usage of the the `:less_than` option. This
|
55
|
+
# asserts that the attribute can take a value which is less than the
|
56
|
+
# given value and cannot take a value which is greater than or equal to
|
57
|
+
# it. It can also accept methods or procs that returns a given value.
|
58
|
+
#
|
59
|
+
# class Person
|
60
|
+
# include ActiveModel::Model
|
61
|
+
# attribute :number_of_cars, :integer
|
62
|
+
# attr_accessor :number_of_cars
|
63
|
+
#
|
64
|
+
# validates_comparison_of :number_of_cars, less_than: :current_number_of_cars
|
65
|
+
#
|
66
|
+
# def current_number_of_cars
|
67
|
+
# 10
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# # RSpec
|
72
|
+
# RSpec.describe Person, type: :model do
|
73
|
+
# it do
|
74
|
+
# should validate_comparison_of(:number_of_cars).
|
75
|
+
# is_less_than(:current_number_of_cars)
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# # Minitest (Shoulda)
|
80
|
+
# class PersonTest < ActiveSupport::TestCase
|
81
|
+
# should validate_comparison_of(:number_of_cars).
|
82
|
+
# is_less_than(:current_number_of_cars)
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# ##### is_less_than_or_equal_to
|
86
|
+
#
|
87
|
+
# Use `is_less_than_or_equal_to` to test usage of the
|
88
|
+
# `:less_than_or_equal_to` option. This asserts that the attribute can
|
89
|
+
# take a value which is less than or equal to the given value and cannot
|
90
|
+
# take a value which is greater than it. It can also accept methods or
|
91
|
+
# procs that returns a given value.
|
92
|
+
#
|
93
|
+
# class Person
|
94
|
+
# include ActiveModel::Model
|
95
|
+
# attr_accessor :birth_date
|
96
|
+
#
|
97
|
+
# validates_comparison_of :birth_date, less_than_or_equal_to: Date.new(1987, 12, 31)
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# # RSpec
|
101
|
+
# RSpec.describe Person, type: :model do
|
102
|
+
# it do
|
103
|
+
# should validate_comparison_of(:birth_date).
|
104
|
+
# is_less_than_or_equal_to(Date.new(1987, 12, 31))
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# # Minitest (Shoulda)
|
109
|
+
# class PersonTest < ActiveSupport::TestCase
|
110
|
+
# should validate_comparison_of(:birth_date).
|
111
|
+
# is_less_than_or_equal_to(Date.new(1987, 12, 31))
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# ##### is_greater_than_or_equal_to
|
115
|
+
#
|
116
|
+
# Use `is_greater_than_or_equal_to` to test usage of the
|
117
|
+
# `:greater_than_or_equal_to` option. This asserts that the attribute can
|
118
|
+
# take a value which is greater than or equal to the given value and
|
119
|
+
# cannot take a value which is less than it.
|
120
|
+
#
|
121
|
+
# class Person
|
122
|
+
# include ActiveModel::Model
|
123
|
+
# attribute :birth_date, :date
|
124
|
+
# attr_accessor :birth_date
|
125
|
+
#
|
126
|
+
# validates_comparison_of :birth_date,
|
127
|
+
# greater_than_or_equal_to: -> { 18.years.ago.to_date }
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# # RSpec
|
131
|
+
# RSpec.describe Person, type: :model do
|
132
|
+
# it do
|
133
|
+
# should validate_comparison_of(:birth_date).
|
134
|
+
# is_greater_than_or_equal_to(-> { 18.years.ago.to_date })
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# # Minitest (Shoulda)
|
139
|
+
# class PersonTest < ActiveSupport::TestCase
|
140
|
+
# should validate_comparison_of(:birth_date).
|
141
|
+
# is_greater_than_or_equal_to(-> { 18.years.ago.to_date })
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# ##### is_greater_than
|
145
|
+
#
|
146
|
+
# Use `is_greater_than` to test usage of the `:greater_than` option.
|
147
|
+
# This asserts that the attribute can take a value which is greater than
|
148
|
+
# the given value and cannot take a value less than or equal to it.
|
149
|
+
# It can also accept methods or procs that returns a given value.
|
150
|
+
#
|
151
|
+
# class Person
|
152
|
+
# include ActiveModel::Model
|
153
|
+
# attribute :legal_age, :integer
|
154
|
+
# attr_accessor :legal_age
|
155
|
+
#
|
156
|
+
# validates_comparison_of :legal_age, greater_than: 21
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# # RSpec
|
160
|
+
# RSpec.describe Person, type: :model do
|
161
|
+
# it do
|
162
|
+
# should validate_comparison_of(:legal_age).
|
163
|
+
# is_greater_than(21)
|
164
|
+
# end
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# # Minitest (Shoulda)
|
168
|
+
# class PersonTest < ActiveSupport::TestCase
|
169
|
+
# should validate_comparison_of(:legal_age).
|
170
|
+
# is_greater_than(21)
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# ##### is_equal_to
|
174
|
+
#
|
175
|
+
# Use `is_equal_to` to test usage of the `:equal_to` option. This asserts
|
176
|
+
# that the attribute can take a value which is equal to the given value
|
177
|
+
# and cannot take a value which is not equal. It can also accept methods or
|
178
|
+
# procs that returns a given value.
|
179
|
+
#
|
180
|
+
# class Person
|
181
|
+
# include ActiveModel::Model
|
182
|
+
# attribute :favorite_color, :string
|
183
|
+
# attr_accessor :favorite_color
|
184
|
+
#
|
185
|
+
# validates_comparison_of :favorite_color, equal_to: "blue"
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# # RSpec
|
189
|
+
# RSpec.describe Person, type: :model do
|
190
|
+
# it { should validate_comparison_of(:favorite_color).is_equal_to("blue") }
|
191
|
+
# end
|
192
|
+
#
|
193
|
+
# # Minitest (Shoulda)
|
194
|
+
# class PersonTest < ActiveSupport::TestCase
|
195
|
+
# should validate_comparison_of(:favorite_color).is_equal_to("blue")
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
#
|
199
|
+
# ##### is_other_than
|
200
|
+
#
|
201
|
+
# Use `is_other_than` to test usage of the `:other_than` option.
|
202
|
+
# This asserts that the attribute can take a number which is not equal to
|
203
|
+
# the given value.
|
204
|
+
#
|
205
|
+
# class Person
|
206
|
+
# include ActiveModel::Model
|
207
|
+
# attr_accessor :legal_age
|
208
|
+
#
|
209
|
+
# validates_comparison_of :legal_age, other_than: 21
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
# # RSpec
|
213
|
+
# RSpec.describe Person, type: :model do
|
214
|
+
# it do
|
215
|
+
# should validate_comparison_of(:legal_age).
|
216
|
+
# is_other_than(21)
|
217
|
+
# end
|
218
|
+
# end
|
219
|
+
#
|
220
|
+
# # Minitest (Shoulda)
|
221
|
+
# class PersonTest < ActiveSupport::TestCase
|
222
|
+
# should validate_comparison_of(:legal_age).
|
223
|
+
# is_other_than(21)
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# ##### with_message
|
227
|
+
#
|
228
|
+
# Use `with_message` if you are using a custom validation message.
|
229
|
+
#
|
230
|
+
# class Person
|
231
|
+
# include ActiveModel::Model
|
232
|
+
# attr_accessor :number_of_dependents
|
233
|
+
#
|
234
|
+
# validates_comparison_of :number_of_dependents, greater_than: 0
|
235
|
+
# message: 'Number of dependents must be a number'
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# # RSpec
|
239
|
+
# RSpec.describe Person, type: :model do
|
240
|
+
# it do
|
241
|
+
# should validate_comparison_of(:number_of_dependents).
|
242
|
+
# is_greater_than(0).
|
243
|
+
# with_message('Number of dependents must be a number')
|
244
|
+
# end
|
245
|
+
# end
|
246
|
+
#
|
247
|
+
# # Minitest (Shoulda)
|
248
|
+
# class PersonTest < ActiveSupport::TestCase
|
249
|
+
# should validate_comparison_of(:number_of_dependents).
|
250
|
+
# is_greater_than(0).
|
251
|
+
# with_message('Number of dependents must be a number')
|
252
|
+
# end
|
253
|
+
#
|
254
|
+
# ##### allow_nil
|
255
|
+
#
|
256
|
+
# Use `allow_nil` to assert that the attribute allows nil.
|
257
|
+
#
|
258
|
+
# class Post
|
259
|
+
# include ActiveModel::Model
|
260
|
+
# attr_accessor :age
|
261
|
+
#
|
262
|
+
# validates_comparison_of :age, greater_than: 0, allow_nil: true
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# # RSpec
|
266
|
+
# RSpec.describe Post, type: :model do
|
267
|
+
# it { should validate_comparison_of(:age).is_greater_than(0).allow_nil }
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# # Minitest (Shoulda)
|
271
|
+
# class PostTest < ActiveSupport::TestCase
|
272
|
+
# should validate_comparison_of(:age).is_greater_than(0).allow_nil
|
273
|
+
# end
|
274
|
+
#
|
275
|
+
# @return [ValidateComparisonOfMatcher]
|
276
|
+
#
|
277
|
+
def validate_comparison_of(attr)
|
278
|
+
ValidateComparisonOfMatcher.new(attr)
|
279
|
+
end
|
280
|
+
|
281
|
+
# @private
|
282
|
+
class ValidateComparisonOfMatcher < ValidationMatcher
|
283
|
+
NUMERIC_NAME = 'number'.freeze
|
284
|
+
DEFAULT_DIFF_TO_COMPARE = 1
|
285
|
+
|
286
|
+
attr_reader :diff_to_compare, :number_of_submatchers
|
287
|
+
|
288
|
+
def initialize(attribute)
|
289
|
+
super
|
290
|
+
@submatchers = []
|
291
|
+
@diff_to_compare = DEFAULT_DIFF_TO_COMPARE
|
292
|
+
@expects_to_allow_nil = false
|
293
|
+
@comparison_submatcher = false
|
294
|
+
end
|
295
|
+
|
296
|
+
def allow_nil
|
297
|
+
@expects_to_allow_nil = true
|
298
|
+
prepare_submatcher(allow_value_matcher(nil))
|
299
|
+
self
|
300
|
+
end
|
301
|
+
|
302
|
+
def expects_to_allow_nil?
|
303
|
+
@expects_to_allow_nil
|
304
|
+
end
|
305
|
+
|
306
|
+
def is_greater_than(value)
|
307
|
+
prepare_submatcher(comparison_matcher_for(value, :>).for(attribute))
|
308
|
+
self
|
309
|
+
end
|
310
|
+
|
311
|
+
def is_greater_than_or_equal_to(value)
|
312
|
+
prepare_submatcher(comparison_matcher_for(value, :>=).for(attribute))
|
313
|
+
self
|
314
|
+
end
|
315
|
+
|
316
|
+
def is_equal_to(value)
|
317
|
+
prepare_submatcher(comparison_matcher_for(value, :==).for(attribute))
|
318
|
+
self
|
319
|
+
end
|
320
|
+
|
321
|
+
def is_less_than(value)
|
322
|
+
prepare_submatcher(comparison_matcher_for(value, :<).for(attribute))
|
323
|
+
self
|
324
|
+
end
|
325
|
+
|
326
|
+
def is_less_than_or_equal_to(value)
|
327
|
+
prepare_submatcher(comparison_matcher_for(value, :<=).for(attribute))
|
328
|
+
self
|
329
|
+
end
|
330
|
+
|
331
|
+
def is_other_than(value)
|
332
|
+
prepare_submatcher(comparison_matcher_for(value, :!=).for(attribute))
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
def matches?(subject)
|
337
|
+
@subject = subject
|
338
|
+
@number_of_submatchers = @submatchers.size
|
339
|
+
unless @comparison_matcher
|
340
|
+
raise(ArgumentError, "matcher isn't qualified with any comparison matcher")
|
341
|
+
end
|
342
|
+
|
343
|
+
qualify_submatchers
|
344
|
+
first_submatcher_that_fails_to_match.nil?
|
345
|
+
end
|
346
|
+
|
347
|
+
def does_not_match?(subject)
|
348
|
+
@subject = subject
|
349
|
+
@number_of_submatchers = @submatchers.size
|
350
|
+
|
351
|
+
qualify_submatchers
|
352
|
+
first_submatcher_that_fails_to_not_match.nil?
|
353
|
+
end
|
354
|
+
|
355
|
+
def simple_description
|
356
|
+
String.new.tap do |description|
|
357
|
+
description << "validate that :#{attribute} looks like "
|
358
|
+
description << Shoulda::Matchers::Util.a_or_an(allowed_type_name)
|
359
|
+
|
360
|
+
if comparison_descriptions.present?
|
361
|
+
description << " #{comparison_descriptions}"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def failure_message
|
367
|
+
overall_failure_message.dup.tap do |message|
|
368
|
+
message << "\n"
|
369
|
+
message << failure_message_for_first_submatcher_that_fails_to_match
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def failure_message_when_negated
|
374
|
+
overall_failure_message_when_negated.dup.tap do |message|
|
375
|
+
message << "\n"
|
376
|
+
message <<
|
377
|
+
failure_message_for_first_submatcher_that_fails_to_not_match
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def given_numeric_column?
|
382
|
+
attribute_is_active_record_column? &&
|
383
|
+
[:integer, :float, :decimal].include?(column_type)
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
def attribute_is_active_record_column?
|
389
|
+
columns_hash.key?(attribute.to_s)
|
390
|
+
end
|
391
|
+
|
392
|
+
def column_type
|
393
|
+
columns_hash[attribute.to_s].type
|
394
|
+
end
|
395
|
+
|
396
|
+
def columns_hash
|
397
|
+
if subject.class.respond_to?(:columns_hash)
|
398
|
+
subject.class.columns_hash
|
399
|
+
else
|
400
|
+
{}
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def prepare_submatcher(submatcher)
|
405
|
+
add_submatcher(submatcher)
|
406
|
+
submatcher
|
407
|
+
end
|
408
|
+
|
409
|
+
def comparison_matcher_for(value, operator)
|
410
|
+
@comparison_matcher = true
|
411
|
+
ComparisonMatcher.
|
412
|
+
new(self, value, operator).
|
413
|
+
for(attribute)
|
414
|
+
end
|
415
|
+
|
416
|
+
def add_submatcher(submatcher)
|
417
|
+
@submatchers << submatcher
|
418
|
+
end
|
419
|
+
|
420
|
+
def qualify_submatchers
|
421
|
+
@submatchers.each do |submatcher|
|
422
|
+
if @expects_strict
|
423
|
+
submatcher.strict
|
424
|
+
end
|
425
|
+
|
426
|
+
if @expected_message.present?
|
427
|
+
submatcher.with_message(@expected_message)
|
428
|
+
end
|
429
|
+
|
430
|
+
if @context
|
431
|
+
submatcher.on(@context)
|
432
|
+
end
|
433
|
+
|
434
|
+
submatcher.ignoring_interference_by_writer(
|
435
|
+
ignore_interference_by_writer,
|
436
|
+
)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def number_of_submatchers_for_failure_message
|
441
|
+
if has_been_qualified?
|
442
|
+
number_of_submatchers - 1
|
443
|
+
else
|
444
|
+
number_of_submatchers
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def has_been_qualified?
|
449
|
+
@submatchers.any? { |submatcher| submatcher_qualified?(submatcher) }
|
450
|
+
end
|
451
|
+
|
452
|
+
def submatcher_qualified?(submatcher)
|
453
|
+
submatcher.instance_of?(ComparisonMatcher)
|
454
|
+
end
|
455
|
+
|
456
|
+
def first_submatcher_that_fails_to_match
|
457
|
+
@_first_submatcher_that_fails_to_match ||=
|
458
|
+
@submatchers.detect do |submatcher|
|
459
|
+
!submatcher.matches?(subject)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def first_submatcher_that_fails_to_not_match
|
464
|
+
@_first_submatcher_that_fails_to_not_match ||=
|
465
|
+
@submatchers.detect do |submatcher|
|
466
|
+
submatcher.matches?(subject)
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def failure_message_for_first_submatcher_that_fails_to_match
|
471
|
+
build_submatcher_failure_message_for(
|
472
|
+
first_submatcher_that_fails_to_match,
|
473
|
+
:failure_message,
|
474
|
+
)
|
475
|
+
end
|
476
|
+
|
477
|
+
def failure_message_for_first_submatcher_that_fails_to_not_match
|
478
|
+
build_submatcher_failure_message_for(
|
479
|
+
first_submatcher_that_fails_to_not_match,
|
480
|
+
:failure_message_when_negated,
|
481
|
+
)
|
482
|
+
end
|
483
|
+
|
484
|
+
def build_submatcher_failure_message_for(
|
485
|
+
submatcher,
|
486
|
+
failure_message_method
|
487
|
+
)
|
488
|
+
failure_message = submatcher.public_send(failure_message_method)
|
489
|
+
submatcher_description = submatcher.simple_description.
|
490
|
+
sub(/\bvalidate that\b/, 'validates').
|
491
|
+
sub(/\bdisallow\b/, 'disallows').
|
492
|
+
sub(/\ballow\b/, 'allows')
|
493
|
+
submatcher_message =
|
494
|
+
if number_of_submatchers_for_failure_message > 1
|
495
|
+
"In checking that #{model.name} #{submatcher_description}, " +
|
496
|
+
failure_message[0].downcase +
|
497
|
+
failure_message[1..]
|
498
|
+
else
|
499
|
+
failure_message
|
500
|
+
end
|
501
|
+
|
502
|
+
Shoulda::Matchers.word_wrap(submatcher_message, indent: 2)
|
503
|
+
end
|
504
|
+
|
505
|
+
def comparison_descriptions
|
506
|
+
description_array = submatcher_comparison_descriptions
|
507
|
+
if description_array.empty?
|
508
|
+
''
|
509
|
+
else
|
510
|
+
submatcher_comparison_descriptions.join(' and ')
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def submatcher_comparison_descriptions
|
515
|
+
@submatchers.inject([]) do |arr, submatcher|
|
516
|
+
arr << if submatcher.respond_to? :comparison_description
|
517
|
+
submatcher.comparison_description
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def allowed_type_name
|
523
|
+
'value'
|
524
|
+
end
|
525
|
+
|
526
|
+
def non_numeric_value
|
527
|
+
'abcd'
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
@@ -3,10 +3,10 @@ module Shoulda
|
|
3
3
|
module ActiveModel
|
4
4
|
# The `validate_exclusion_of` matcher tests usage of the
|
5
5
|
# `validates_exclusion_of` validation, asserting that an attribute cannot
|
6
|
-
# take a
|
6
|
+
# take a blocklist of values, and inversely, can take values outside of
|
7
7
|
# this list.
|
8
8
|
#
|
9
|
-
# If your
|
9
|
+
# If your blocklist an array of values, use `in_array`:
|
10
10
|
#
|
11
11
|
# class Game
|
12
12
|
# include ActiveModel::Model
|
@@ -29,13 +29,13 @@ module Shoulda
|
|
29
29
|
# in_array(['Mac', 'Linux'])
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
# If your
|
32
|
+
# If your blocklist is a range of values, use `in_range`:
|
33
33
|
#
|
34
34
|
# class Game
|
35
35
|
# include ActiveModel::Model
|
36
|
-
# attr_accessor :
|
36
|
+
# attr_accessor :floors_with_enemies
|
37
37
|
#
|
38
|
-
# validates_exclusion_of :
|
38
|
+
# validates_exclusion_of :floors_with_enemies, in: 5..8
|
39
39
|
# end
|
40
40
|
#
|
41
41
|
# # RSpec
|
@@ -3,12 +3,15 @@ require 'date'
|
|
3
3
|
|
4
4
|
module Shoulda
|
5
5
|
module Matchers
|
6
|
+
# @private
|
7
|
+
class ExampleClass; end
|
8
|
+
|
6
9
|
module ActiveModel
|
7
10
|
# The `validate_inclusion_of` matcher tests usage of the
|
8
11
|
# `validates_inclusion_of` validation, asserting that an attribute can
|
9
|
-
# take a
|
12
|
+
# take a allowlist of values and cannot take values outside of this list.
|
10
13
|
#
|
11
|
-
# If your
|
14
|
+
# If your allowlist is an array of values, use `in_array`:
|
12
15
|
#
|
13
16
|
# class Issue
|
14
17
|
# include ActiveModel::Model
|
@@ -32,7 +35,7 @@ module Shoulda
|
|
32
35
|
# in_array(['open', 'resolved', 'unresolved'])
|
33
36
|
# end
|
34
37
|
#
|
35
|
-
# If your
|
38
|
+
# If your allowlist is a range of values, use `in_range`:
|
36
39
|
#
|
37
40
|
# class Issue
|
38
41
|
# include ActiveModel::Model
|
@@ -43,12 +46,12 @@ module Shoulda
|
|
43
46
|
#
|
44
47
|
# # RSpec
|
45
48
|
# RSpec.describe Issue, type: :model do
|
46
|
-
# it { should validate_inclusion_of(:
|
49
|
+
# it { should validate_inclusion_of(:priority).in_range(1..5) }
|
47
50
|
# end
|
48
51
|
#
|
49
52
|
# # Minitest (Shoulda)
|
50
53
|
# class IssueTest < ActiveSupport::TestCase
|
51
|
-
# should validate_inclusion_of(:
|
54
|
+
# should validate_inclusion_of(:priority).in_range(1..5)
|
52
55
|
# end
|
53
56
|
#
|
54
57
|
# #### Caveats
|
@@ -269,7 +272,7 @@ module Shoulda
|
|
269
272
|
# @private
|
270
273
|
class ValidateInclusionOfMatcher < ValidationMatcher
|
271
274
|
BLANK_VALUES = ['', ' ', "\n", "\r", "\t", "\f"].freeze
|
272
|
-
ARBITRARY_OUTSIDE_STRING =
|
275
|
+
ARBITRARY_OUTSIDE_STRING = Shoulda::Matchers::ExampleClass.name
|
273
276
|
ARBITRARY_OUTSIDE_INTEGER = 123456789
|
274
277
|
ARBITRARY_OUTSIDE_DECIMAL = BigDecimal('0.123456789')
|
275
278
|
ARBITRARY_OUTSIDE_DATE = Date.jd(9999999)
|
@@ -306,8 +309,8 @@ EOT
|
|
306
309
|
|
307
310
|
def in_range(range)
|
308
311
|
@range = range
|
309
|
-
@minimum =
|
310
|
-
@maximum =
|
312
|
+
@minimum = minimum_range_value
|
313
|
+
@maximum = maximum_range_value
|
311
314
|
self
|
312
315
|
end
|
313
316
|
|
@@ -397,6 +400,18 @@ EOT
|
|
397
400
|
|
398
401
|
private
|
399
402
|
|
403
|
+
def minimum_range_value
|
404
|
+
@range.begin
|
405
|
+
end
|
406
|
+
|
407
|
+
def maximum_range_value
|
408
|
+
if @range.exclude_end?
|
409
|
+
@range.end ? (@range.end - 1) : nil
|
410
|
+
else
|
411
|
+
@range.end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
400
415
|
def matches_for_range?
|
401
416
|
disallows_lower_value &&
|
402
417
|
allows_minimum_value &&
|
@@ -438,27 +453,27 @@ EOT
|
|
438
453
|
end
|
439
454
|
|
440
455
|
def allows_minimum_value
|
441
|
-
allows_value_of(@minimum, @low_message)
|
456
|
+
@minimum.nil? || allows_value_of(@minimum, @low_message)
|
442
457
|
end
|
443
458
|
|
444
459
|
def disallows_minimum_value
|
445
|
-
disallows_value_of(@minimum, @low_message)
|
460
|
+
@minimum.nil? || disallows_value_of(@minimum, @low_message)
|
446
461
|
end
|
447
462
|
|
448
463
|
def allows_maximum_value
|
449
|
-
allows_value_of(@maximum, @high_message)
|
464
|
+
@maximum.nil? || allows_value_of(@maximum, @high_message)
|
450
465
|
end
|
451
466
|
|
452
467
|
def disallows_maximum_value
|
453
|
-
disallows_value_of(@maximum, @high_message)
|
468
|
+
@maximum.nil? || disallows_value_of(@maximum, @high_message)
|
454
469
|
end
|
455
470
|
|
456
471
|
def allows_higher_value
|
457
|
-
allows_value_of(@maximum + 1, @high_message)
|
472
|
+
@maximum.nil? || allows_value_of(@maximum + 1, @high_message)
|
458
473
|
end
|
459
474
|
|
460
475
|
def disallows_higher_value
|
461
|
-
disallows_value_of(@maximum + 1, @high_message)
|
476
|
+
@maximum.nil? || disallows_value_of(@maximum + 1, @high_message)
|
462
477
|
end
|
463
478
|
|
464
479
|
def allows_all_values_in_array?
|