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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +22 -7
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -0
- data/lib/shoulda/matchers/active_model/comparison_matcher.rb +162 -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 +58 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -1
- data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +534 -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 +9 -6
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +72 -80
- 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/define_enum_for_matcher.rb +0 -8
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +14 -3
- data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
- data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
- data/lib/shoulda/matchers/active_record.rb +1 -0
- data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +1 -1
- data/lib/shoulda/matchers/rails_shim.rb +10 -8
- data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
- data/lib/shoulda/matchers/util.rb +1 -1
- data/lib/shoulda/matchers/version.rb +1 -1
- data/lib/shoulda/matchers.rb +2 -2
- data/shoulda-matchers.gemspec +1 -1
- metadata +12 -8
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '00039d1c47436bb88b431784a2b956713382face294b6bbe5c7599378ce03a55'
|
4
|
+
data.tar.gz: 27640030d1ada19757e5da4d9c10465f422234f8dde9242a54bcb002585c71d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51aff6ac4ab386f8df5dec81dfca4f197604d206263b29b711146820dda54ef5f9a9aa723aa6ce14084364eccdcbfbb8c3b1c38f2a4cdcbfc5d3ae8e8c965806
|
7
|
+
data.tar.gz: 476b055e38257cc8d6841a96779a012ef68cd01239c26c15b170c251a4f7655bab6344a7c163361e9ef283142994bc04ed69f8c6bbccf1bae3acfafc5fd62176
|
data/LICENSE
CHANGED
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/
|
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
|
472
|
-
|
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 [
|
488
|
-
|
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 ©
|
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://
|
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
|
-
#
|
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 ||=
|
59
|
-
|
60
|
-
|
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
|
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
|