shoulda-matchers 5.2.0 → 6.0.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 (33) 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/numeric_type_matcher.rb +3 -5
  8. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  9. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +58 -0
  10. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -1
  11. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +534 -0
  12. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
  13. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +9 -6
  14. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
  15. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +72 -80
  16. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  17. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  18. data/lib/shoulda/matchers/active_model.rb +4 -1
  19. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  20. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  21. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +14 -3
  22. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  23. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  24. data/lib/shoulda/matchers/active_record.rb +1 -0
  25. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +1 -1
  26. data/lib/shoulda/matchers/rails_shim.rb +10 -8
  27. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  28. data/lib/shoulda/matchers/util.rb +1 -1
  29. data/lib/shoulda/matchers/version.rb +1 -1
  30. data/lib/shoulda/matchers.rb +2 -2
  31. data/shoulda-matchers.gemspec +1 -1
  32. metadata +12 -8
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -157
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97aebe50b9fb389687c99bc5dc1229a27abc5255f15e2330c9d1409d957fb689
4
- data.tar.gz: 7cd8151aae87feb29121f4b4c152f963e5cc1c7a136e0a2115344af225fb1dcd
3
+ metadata.gz: '00039d1c47436bb88b431784a2b956713382face294b6bbe5c7599378ce03a55'
4
+ data.tar.gz: 27640030d1ada19757e5da4d9c10465f422234f8dde9242a54bcb002585c71d5
5
5
  SHA512:
6
- metadata.gz: 351c2bc9565abf151db5eeef3d17280989cdc8ec1841b0a099de8be8533767324494b04bfcc4464290852f5b022f6595fb5759e79c4534f50b6e464c94e44679
7
- data.tar.gz: 529b6d0815299c10c0f52664371e37b948ec4b8403c0096065f11649fb1849851454634f736e747820f06e27fc4d48a0616d9adfbc34b560f14a8dad364e4268
6
+ metadata.gz: 51aff6ac4ab386f8df5dec81dfca4f197604d206263b29b711146820dda54ef5f9a9aa723aa6ce14084364eccdcbfbb8c3b1c38f2a4cdcbfc5d3ae8e8c965806
7
+ data.tar.gz: 476b055e38257cc8d6841a96779a012ef68cd01239c26c15b170c251a4f7655bab6344a7c163361e9ef283142994bc04ed69f8c6bbccf1bae3acfafc5fd62176
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2022 Tammer Saleh and thoughtbot, inc.
1
+ Copyright (c) Tammer Saleh and thoughtbot, inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [version-badge]: https://img.shields.io/gem/v/shoulda-matchers.svg
4
4
  [rubygems]: https://rubygems.org/gems/shoulda-matchers
5
- [github-actions-badge]: https://img.shields.io/github/workflow/status/thoughtbot/shoulda-matchers/Test
5
+ [github-actions-badge]: https://img.shields.io/github/actions/workflow/status/thoughtbot/shoulda-matchers/ci.yml?branch=main
6
6
  [github-actions]: https://github.com/thoughtbot/shoulda-matchers/actions
7
7
  [downloads-total]: https://img.shields.io/gem/dt/shoulda-matchers.svg
8
8
  [downloads-badge]: https://img.shields.io/gem/dtv/shoulda-matchers.svg
@@ -374,6 +374,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
374
374
  tests usage of `validates_numericality_of`.
375
375
  * **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
376
376
  tests usage of `validates_presence_of`.
377
+ * **[validate_comparison_of](lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb)**
378
+ tests usage of `validates_comparison_of`.
377
379
 
378
380
  ### ActiveRecord matchers
379
381
 
@@ -407,6 +409,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
407
409
  usage of the `serialize` macro.
408
410
  * **[validate_uniqueness_of](lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb)**
409
411
  tests usage of `validates_uniqueness_of`.
412
+ * **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
413
+ usage of the `normalize` macro
410
414
 
411
415
  ### ActionController matchers
412
416
 
@@ -468,8 +472,8 @@ machine, understanding the codebase, and creating a good pull request.
468
472
 
469
473
  ## Compatibility
470
474
 
471
- Shoulda Matchers is tested and supported against Ruby 2.6+, Rails
472
- 5.2+, RSpec 3.x, and Minitest 5.x.
475
+ Shoulda Matchers is tested and supported against Ruby 3.0+, Rails
476
+ 6.1+, RSpec 3.x, and Minitest 5.x.
473
477
 
474
478
  - For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
475
479
  - For Ruby < 3.0 and Rails < 6.1 compatibility, please use [v4.5.1][v4.5.1].
@@ -484,15 +488,26 @@ Shoulda Matchers follows Semantic Versioning 2.0 as defined at
484
488
 
485
489
  ## Team
486
490
 
487
- Shoulda Matchers is maintained by [Elliot Winkler][mcmire] and [Gui
488
- Albuk][guialbuk].
491
+ Shoulda Matchers is currently maintained by [Pedro Paiva][VSPPedro] and [Matheus
492
+ Sales][matsales28]. Previous maintainers include [Elliot Winkler][mcmire],
493
+ [Gui Albuk][guialbuk], [Jason Draper][drapergeek], [Melissa Xie][mxie],
494
+ [Gabe Berke-Williams][gabebw], [Ryan McGeary][rmm5t], [Joe Ferris][jferris], and
495
+ [Tammer Saleh][tammersaleh].
489
496
 
497
+ [VSPPedro]: https://github.com/VSPPedro
498
+ [matsales28]: https://github.com/matsales28
490
499
  [mcmire]: https://github.com/mcmire
491
500
  [guialbuk]: https://github.com/guialbuk
501
+ [drapergeek]: https://github.com/drapergeek
502
+ [mxie]: https://github.com/mxie
503
+ [gabebw]: https://github.com/gabebw
504
+ [rmm5t]: https://github.com/rmm5t
505
+ [jferris]: https://github.com/jferris
506
+ [tammersaleh]: https://github.com/tammersaleh
492
507
 
493
508
  ## Copyright/License
494
509
 
495
- Shoulda Matchers is copyright © 2006-2022 Tammer Saleh and [thoughtbot,
510
+ Shoulda Matchers is copyright © Tammer Saleh and [thoughtbot,
496
511
  inc][thoughtbot-website]. It is free and opensource software and may be
497
512
  redistributed under the terms specified in the [LICENSE](LICENSE) file.
498
513
 
@@ -502,7 +517,7 @@ redistributed under the terms specified in the [LICENSE](LICENSE) file.
502
517
 
503
518
  ![thoughtbot][thoughtbot-logo]
504
519
 
505
- [thoughtbot-logo]: https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg
520
+ [thoughtbot-logo]: https://thoughtbot.com/brand_assets/93:44.svg
506
521
 
507
522
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
508
523
 
@@ -11,7 +11,7 @@ module Shoulda
11
11
  module Matchers
12
12
  module ActionController
13
13
  # The `permit` matcher tests that an action in your controller receives a
14
- # whitelist of parameters using Rails' Strong Parameters feature
14
+ # allowlist of parameters using Rails' Strong Parameters feature
15
15
  # (specifically that `permit` was called with the correct arguments).
16
16
  #
17
17
  # Here's an example:
@@ -402,6 +402,10 @@ module Shoulda
402
402
  @result.nil?
403
403
  end
404
404
 
405
+ def has_any_errors?
406
+ validator.record.errors.any?
407
+ end
408
+
405
409
  def failure_message
406
410
  attribute_setter = result.attribute_setter
407
411
 
@@ -480,6 +484,11 @@ module Shoulda
480
484
  message << " it produced these validation errors instead:\n\n"
481
485
  message << validator.all_formatted_validation_error_messages
482
486
  end
487
+ elsif validator.has_any_errors?
488
+ message << ", placing a validation error on :#{attribute_setter.attribute_name}"
489
+ message << '. The Example was invalid,'
490
+ message << " but it had errors involving other attributes:\n\n"
491
+ message << validator.all_formatted_validation_error_messages
483
492
  else
484
493
  message << ', but it was valid instead.'
485
494
  end
@@ -0,0 +1,162 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveModel
6
+ # @private
7
+ class ComparisonMatcher < ValidationMatcher
8
+ ERROR_MESSAGES = {
9
+ :> => {
10
+ label: :greater_than,
11
+ assertions: [false, false, true],
12
+ },
13
+ :>= => {
14
+ label: :greater_than_or_equal_to,
15
+ assertions: [false, true, true],
16
+ },
17
+ :< => {
18
+ label: :less_than,
19
+ assertions: [true, false, false],
20
+ },
21
+ :<= => {
22
+ label: :less_than_or_equal_to,
23
+ assertions: [true, true, false],
24
+ },
25
+ :== => {
26
+ label: :equal_to,
27
+ assertions: [false, true, false],
28
+ },
29
+ :!= => {
30
+ label: :other_than,
31
+ assertions: [true, false, true],
32
+ },
33
+ }.freeze
34
+
35
+ delegate :failure_message, :failure_message_when_negated, to: :comparison_submatchers
36
+
37
+ def initialize(matcher, value, operator)
38
+ super(nil)
39
+ unless matcher.respond_to? :diff_to_compare
40
+ raise ArgumentError, 'matcher is invalid'
41
+ end
42
+
43
+ @matcher = matcher
44
+ @value = value
45
+ @operator = operator
46
+ @message = ERROR_MESSAGES[operator][:label]
47
+ end
48
+
49
+ def simple_description
50
+ description = ''
51
+
52
+ if expects_strict?
53
+ description << ' strictly'
54
+ end
55
+
56
+ description +
57
+ "disallow :#{attribute} from being a number that is not " +
58
+ "#{comparison_expectation} #{@value}"
59
+ end
60
+
61
+ def for(attribute)
62
+ @attribute = attribute
63
+ self
64
+ end
65
+
66
+ def with_message(message)
67
+ @expects_custom_validation_message = true
68
+ @message = message
69
+ self
70
+ end
71
+
72
+ def expects_custom_validation_message?
73
+ @expects_custom_validation_message
74
+ end
75
+
76
+ def matches?(subject)
77
+ @subject = subject
78
+ comparison_submatchers.matches?(subject)
79
+ end
80
+
81
+ def does_not_match?(subject)
82
+ @subject = subject
83
+ comparison_submatchers.does_not_match?(subject)
84
+ end
85
+
86
+ def comparison_description
87
+ "#{comparison_expectation} #{@value}"
88
+ end
89
+
90
+ def comparison_submatchers
91
+ @_comparison_submatchers ||=
92
+ NumericalityMatchers::Submatchers.new(build_comparison_submatchers)
93
+ end
94
+
95
+ private
96
+
97
+ def build_comparison_submatchers
98
+ comparison_combos.map do |diff, submatcher_method_name|
99
+ matcher = __send__(submatcher_method_name, diff, nil)
100
+ matcher.with_message(@message, values: { count: option_value })
101
+ matcher
102
+ end
103
+ end
104
+
105
+ def comparison_combos
106
+ diffs_to_compare.zip(submatcher_method_names)
107
+ end
108
+
109
+ def submatcher_method_names
110
+ assertions.map do |value|
111
+ if value
112
+ :allow_value_matcher
113
+ else
114
+ :disallow_value_matcher
115
+ end
116
+ end
117
+ end
118
+
119
+ def assertions
120
+ ERROR_MESSAGES[@operator][:assertions]
121
+ end
122
+
123
+ def option_value
124
+ if defined?(@_option_value)
125
+ @_option_value
126
+ else
127
+ @_option_value =
128
+ case @value
129
+ when Proc then @value.call(@subject)
130
+ when Symbol then @subject.send(@value)
131
+ else @value
132
+ end
133
+ end
134
+ end
135
+
136
+ def diffs_to_compare
137
+ diff_to_compare = @matcher.diff_to_compare
138
+ values = case option_value
139
+ when String then diffs_when_string(diff_to_compare)
140
+ else [-1, 0, 1].map { |sign| option_value + (diff_to_compare * sign) }
141
+ end
142
+
143
+ if @matcher.given_numeric_column?
144
+ values
145
+ else
146
+ values.map(&:to_s)
147
+ end
148
+ end
149
+
150
+ def diffs_when_string(diff_to_compare)
151
+ [-1, 0, 1].map do |sign|
152
+ option_value[0..-2] + (option_value[-1].ord + diff_to_compare * sign).chr
153
+ end
154
+ end
155
+
156
+ def comparison_expectation
157
+ ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -55,11 +55,9 @@ module Shoulda
55
55
  private
56
56
 
57
57
  def disallow_value_matcher
58
- @_disallow_value_matcher ||= begin
59
- DisallowValueMatcher.new(disallowed_value).tap do |matcher|
60
- matcher.for(attribute)
61
- wrap_disallow_value_matcher(matcher)
62
- end
58
+ @_disallow_value_matcher ||= DisallowValueMatcher.new(disallowed_value).tap do |matcher|
59
+ matcher.for(attribute)
60
+ wrap_disallow_value_matcher(matcher)
63
61
  end
64
62
  end
65
63
  end
@@ -0,0 +1,71 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveModel
6
+ module NumericalityMatchers
7
+ # @private
8
+ class RangeMatcher < ValidationMatcher
9
+ OPERATORS = [:>=, :<=].freeze
10
+
11
+ delegate :failure_message, to: :submatchers
12
+
13
+ def initialize(numericality_matcher, attribute, range)
14
+ super(attribute)
15
+ unless numericality_matcher.respond_to? :diff_to_compare
16
+ raise ArgumentError, 'numericality_matcher is invalid'
17
+ end
18
+
19
+ @numericality_matcher = numericality_matcher
20
+ @range = range
21
+ @attribute = attribute
22
+ end
23
+
24
+ def matches?(subject)
25
+ @subject = subject
26
+ submatchers.matches?(subject)
27
+ end
28
+
29
+ def simple_description
30
+ description = ''
31
+
32
+ if expects_strict?
33
+ description << ' strictly'
34
+ end
35
+
36
+ description +
37
+ "disallow :#{attribute} from being a number that is not " +
38
+ range_description
39
+ end
40
+
41
+ def range_description
42
+ "from #{Shoulda::Matchers::Util.inspect_range(@range)}"
43
+ end
44
+
45
+ def submatchers
46
+ @_submatchers ||= NumericalityMatchers::Submatchers.new(build_submatchers)
47
+ end
48
+
49
+ private
50
+
51
+ def build_submatchers
52
+ submatcher_combos.map do |value, operator|
53
+ build_comparison_submatcher(value, operator)
54
+ end
55
+ end
56
+
57
+ def submatcher_combos
58
+ @range.minmax.zip(OPERATORS)
59
+ end
60
+
61
+ def build_comparison_submatcher(value, operator)
62
+ ComparisonMatcher.new(@numericality_matcher, value, operator).
63
+ for(@attribute).
64
+ with_message(@message).
65
+ on(@context)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,58 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ module NumericalityMatchers
5
+ # @private
6
+ class Submatchers
7
+ def initialize(submatchers)
8
+ @submatchers = submatchers
9
+ end
10
+
11
+ def matches?(subject)
12
+ @subject = subject
13
+ failing_submatchers.empty?
14
+ end
15
+
16
+ def does_not_match?(subject)
17
+ @subject = subject
18
+ non_failing_submatchers.empty?
19
+ end
20
+
21
+ def failure_message
22
+ failing_submatcher.failure_message
23
+ end
24
+
25
+ def failure_message_when_negated
26
+ non_failing_submatcher.failure_message_when_negated
27
+ end
28
+
29
+ def add(submatcher)
30
+ @submatchers << submatcher
31
+ end
32
+
33
+ private
34
+
35
+ def failing_submatchers
36
+ @_failing_submatchers ||= @submatchers.reject do |submatcher|
37
+ submatcher.matches?(@subject)
38
+ end
39
+ end
40
+
41
+ def non_failing_submatchers
42
+ @_non_failing_submatchers ||= @submatchers.reject do |submatcher|
43
+ submatcher.does_not_match?(@subject)
44
+ end
45
+ end
46
+
47
+ def failing_submatcher
48
+ failing_submatchers.last
49
+ end
50
+
51
+ def non_failing_submatcher
52
+ non_failing_submatchers.last
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -108,7 +108,7 @@ module Shoulda
108
108
  obj
109
109
  end
110
110
  elsif array_column?
111
- ['an arbitary value']
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