shoulda-matchers 5.2.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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