shoulda-matchers 5.3.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +22 -7
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +1 -1
  5. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -0
  6. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +162 -0
  7. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +1 -1
  8. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +21 -6
  9. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +534 -0
  10. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +3 -3
  11. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +4 -3
  12. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
  13. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +32 -86
  14. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  15. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  16. data/lib/shoulda/matchers/active_model.rb +2 -1
  17. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  18. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  19. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +14 -3
  20. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  21. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  22. data/lib/shoulda/matchers/active_record.rb +1 -0
  23. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +1 -1
  24. data/lib/shoulda/matchers/rails_shim.rb +8 -6
  25. data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
  26. data/lib/shoulda/matchers/util.rb +1 -1
  27. data/lib/shoulda/matchers/version.rb +1 -1
  28. data/lib/shoulda/matchers.rb +2 -2
  29. data/shoulda-matchers.gemspec +1 -1
  30. metadata +10 -8
  31. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
@@ -0,0 +1,534 @@
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).greater_than(10) }
17
+ # end
18
+ #
19
+ # # Minitest (Shoulda)
20
+ # class PersonTest < ActiveSupport::TestCase
21
+ # should validate_comparison_of(:gpa).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
+ # 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).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
+ description = ''
357
+
358
+ description << "validate that :#{attribute} looks like "
359
+ description << Shoulda::Matchers::Util.a_or_an(allowed_type_name)
360
+
361
+ if comparison_descriptions.present?
362
+ description << " #{comparison_descriptions}"
363
+ end
364
+
365
+ description
366
+ end
367
+
368
+ def failure_message
369
+ overall_failure_message.dup.tap do |message|
370
+ message << "\n"
371
+ message << failure_message_for_first_submatcher_that_fails_to_match
372
+ end
373
+ end
374
+
375
+ def failure_message_when_negated
376
+ overall_failure_message_when_negated.dup.tap do |message|
377
+ message << "\n"
378
+ message <<
379
+ failure_message_for_first_submatcher_that_fails_to_not_match
380
+ end
381
+ end
382
+
383
+ def given_numeric_column?
384
+ attribute_is_active_record_column? &&
385
+ [:integer, :float, :decimal].include?(column_type)
386
+ end
387
+
388
+ private
389
+
390
+ def attribute_is_active_record_column?
391
+ columns_hash.key?(attribute.to_s)
392
+ end
393
+
394
+ def column_type
395
+ columns_hash[attribute.to_s].type
396
+ end
397
+
398
+ def columns_hash
399
+ if subject.class.respond_to?(:columns_hash)
400
+ subject.class.columns_hash
401
+ else
402
+ {}
403
+ end
404
+ end
405
+
406
+ def prepare_submatcher(submatcher)
407
+ add_submatcher(submatcher)
408
+ submatcher
409
+ end
410
+
411
+ def comparison_matcher_for(value, operator)
412
+ @comparison_matcher = true
413
+ ComparisonMatcher.
414
+ new(self, value, operator).
415
+ for(attribute)
416
+ end
417
+
418
+ def add_submatcher(submatcher)
419
+ @submatchers << submatcher
420
+ end
421
+
422
+ def qualify_submatchers
423
+ @submatchers.each do |submatcher|
424
+ if @expects_strict
425
+ submatcher.strict
426
+ end
427
+
428
+ if @expected_message.present?
429
+ submatcher.with_message(@expected_message)
430
+ end
431
+
432
+ if @context
433
+ submatcher.on(@context)
434
+ end
435
+
436
+ submatcher.ignoring_interference_by_writer(
437
+ ignore_interference_by_writer,
438
+ )
439
+ end
440
+ end
441
+
442
+ def number_of_submatchers_for_failure_message
443
+ if has_been_qualified?
444
+ number_of_submatchers - 1
445
+ else
446
+ number_of_submatchers
447
+ end
448
+ end
449
+
450
+ def has_been_qualified?
451
+ @submatchers.any? { |submatcher| submatcher_qualified?(submatcher) }
452
+ end
453
+
454
+ def submatcher_qualified?(submatcher)
455
+ submatcher.instance_of?(ComparisonMatcher)
456
+ end
457
+
458
+ def first_submatcher_that_fails_to_match
459
+ @_first_submatcher_that_fails_to_match ||=
460
+ @submatchers.detect do |submatcher|
461
+ !submatcher.matches?(subject)
462
+ end
463
+ end
464
+
465
+ def first_submatcher_that_fails_to_not_match
466
+ @_first_submatcher_that_fails_to_not_match ||=
467
+ @submatchers.detect do |submatcher|
468
+ !submatcher.does_not_match?(subject)
469
+ end
470
+ end
471
+
472
+ def failure_message_for_first_submatcher_that_fails_to_match
473
+ build_submatcher_failure_message_for(
474
+ first_submatcher_that_fails_to_match,
475
+ :failure_message,
476
+ )
477
+ end
478
+
479
+ def failure_message_for_first_submatcher_that_fails_to_not_match
480
+ build_submatcher_failure_message_for(
481
+ first_submatcher_that_fails_to_not_match,
482
+ :failure_message_when_negated,
483
+ )
484
+ end
485
+
486
+ def build_submatcher_failure_message_for(
487
+ submatcher,
488
+ failure_message_method
489
+ )
490
+ failure_message = submatcher.public_send(failure_message_method)
491
+ submatcher_description = submatcher.simple_description.
492
+ sub(/\bvalidate that\b/, 'validates').
493
+ sub(/\bdisallow\b/, 'disallows').
494
+ sub(/\ballow\b/, 'allows')
495
+ submatcher_message =
496
+ if number_of_submatchers_for_failure_message > 1
497
+ "In checking that #{model.name} #{submatcher_description}, " +
498
+ failure_message[0].downcase +
499
+ failure_message[1..]
500
+ else
501
+ failure_message
502
+ end
503
+
504
+ Shoulda::Matchers.word_wrap(submatcher_message, indent: 2)
505
+ end
506
+
507
+ def comparison_descriptions
508
+ description_array = submatcher_comparison_descriptions
509
+ if description_array.empty?
510
+ ''
511
+ else
512
+ submatcher_comparison_descriptions.join(' and ')
513
+ end
514
+ end
515
+
516
+ def submatcher_comparison_descriptions
517
+ @submatchers.inject([]) do |arr, submatcher|
518
+ arr << if submatcher.respond_to? :comparison_description
519
+ submatcher.comparison_description
520
+ end
521
+ end
522
+ end
523
+
524
+ def allowed_type_name
525
+ 'value'
526
+ end
527
+
528
+ def non_numeric_value
529
+ 'abcd'
530
+ end
531
+ end
532
+ end
533
+ end
534
+ 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 blacklist of values, and inversely, can take values outside of
6
+ # take a blocklist of values, and inversely, can take values outside of
7
7
  # this list.
8
8
  #
9
- # If your blacklist is an array of values, use `in_array`:
9
+ # If your blocklist an array of values, use `in_array`:
10
10
  #
11
11
  # class Game
12
12
  # include ActiveModel::Model
@@ -29,7 +29,7 @@ module Shoulda
29
29
  # in_array(['Mac', 'Linux'])
30
30
  # end
31
31
  #
32
- # If your blacklist is a range of values, use `in_range`:
32
+ # If your blocklist is a range of values, use `in_range`:
33
33
  #
34
34
  # class Game
35
35
  # include ActiveModel::Model
@@ -3,14 +3,15 @@ require 'date'
3
3
 
4
4
  module Shoulda
5
5
  module Matchers
6
+ # @private
6
7
  class ExampleClass; end
7
8
 
8
9
  module ActiveModel
9
10
  # The `validate_inclusion_of` matcher tests usage of the
10
11
  # `validates_inclusion_of` validation, asserting that an attribute can
11
- # take a whitelist of values and cannot take values outside of this list.
12
+ # take a allowlist of values and cannot take values outside of this list.
12
13
  #
13
- # If your whitelist is an array of values, use `in_array`:
14
+ # If your allowlist is an array of values, use `in_array`:
14
15
  #
15
16
  # class Issue
16
17
  # include ActiveModel::Model
@@ -34,7 +35,7 @@ module Shoulda
34
35
  # in_array(['open', 'resolved', 'unresolved'])
35
36
  # end
36
37
  #
37
- # If your whitelist is a range of values, use `in_range`:
38
+ # If your allowlist is a range of values, use `in_range`:
38
39
  #
39
40
  # class Issue
40
41
  # include ActiveModel::Model