shoulda-matchers 5.3.0 → 6.1.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 +26 -9
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
- data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -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/range_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +16 -6
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +0 -6
- data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +3 -3
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +4 -3
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +40 -96
- 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 +2 -1
- data/lib/shoulda/matchers/active_record/association_matcher.rb +31 -11
- 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 +0 -8
- 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/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/independent/delegate_method_matcher.rb +13 -15
- data/lib/shoulda/matchers/rails_shim.rb +8 -6
- data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
- data/lib/shoulda/matchers/util.rb +17 -19
- data/lib/shoulda/matchers/version.rb +1 -1
- data/lib/shoulda/matchers.rb +2 -2
- data/shoulda-matchers.gemspec +1 -1
- metadata +11 -8
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 500d5928f097ad1ca9d9c1ed3a0e0c925f504549c1594b57e93efc92316df558
|
4
|
+
data.tar.gz: 8a94b930a96fe1f2e3a78c916ca4fa6b68c21ab61ae9baccb109ec51e9a2961c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43d89cf2b6684f31f6fee6611fe694732446d50aee9fea5b8a5fefc79cd5ec4dc08337cb098e531cbfe4eac3ac46ed6cda46c6207b2f35956cf9b07d2925af1a
|
7
|
+
data.tar.gz: 586d777927cdafba0aa92ecc6c437b562a24a44aa343a8be309ee7563a3a2b98e4426c8804f1c40ad46a1987ff7f160e97473fcd178f3fdb57c2c471d210044b
|
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
|
@@ -55,7 +55,7 @@ Start by including `shoulda-matchers` in your Gemfile:
|
|
55
55
|
|
56
56
|
```ruby
|
57
57
|
group :test do
|
58
|
-
gem 'shoulda-matchers', '~>
|
58
|
+
gem 'shoulda-matchers', '~> 6.0'
|
59
59
|
end
|
60
60
|
```
|
61
61
|
|
@@ -117,7 +117,7 @@ Otherwise, add `shoulda-matchers` to your Gemfile:
|
|
117
117
|
|
118
118
|
```ruby
|
119
119
|
group :test do
|
120
|
-
gem 'shoulda-matchers', '~>
|
120
|
+
gem 'shoulda-matchers', '~> 6.0'
|
121
121
|
end
|
122
122
|
```
|
123
123
|
|
@@ -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,10 @@ 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
|
414
|
+
* **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
|
415
|
+
tests usage of the `encrypts` macro.
|
410
416
|
|
411
417
|
### ActionController matchers
|
412
418
|
|
@@ -468,8 +474,8 @@ machine, understanding the codebase, and creating a good pull request.
|
|
468
474
|
|
469
475
|
## Compatibility
|
470
476
|
|
471
|
-
Shoulda Matchers is tested and supported against Ruby
|
472
|
-
|
477
|
+
Shoulda Matchers is tested and supported against Ruby 3.0+, Rails
|
478
|
+
6.1+, RSpec 3.x, and Minitest 5.x.
|
473
479
|
|
474
480
|
- For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
|
475
481
|
- For Ruby < 3.0 and Rails < 6.1 compatibility, please use [v4.5.1][v4.5.1].
|
@@ -484,15 +490,26 @@ Shoulda Matchers follows Semantic Versioning 2.0 as defined at
|
|
484
490
|
|
485
491
|
## Team
|
486
492
|
|
487
|
-
Shoulda Matchers is maintained by [
|
488
|
-
|
493
|
+
Shoulda Matchers is currently maintained by [Pedro Paiva][VSPPedro] and [Matheus
|
494
|
+
Sales][matsales28]. Previous maintainers include [Elliot Winkler][mcmire],
|
495
|
+
[Gui Albuk][guialbuk], [Jason Draper][drapergeek], [Melissa Xie][mxie],
|
496
|
+
[Gabe Berke-Williams][gabebw], [Ryan McGeary][rmm5t], [Joe Ferris][jferris], and
|
497
|
+
[Tammer Saleh][tammersaleh].
|
489
498
|
|
499
|
+
[VSPPedro]: https://github.com/VSPPedro
|
500
|
+
[matsales28]: https://github.com/matsales28
|
490
501
|
[mcmire]: https://github.com/mcmire
|
491
502
|
[guialbuk]: https://github.com/guialbuk
|
503
|
+
[drapergeek]: https://github.com/drapergeek
|
504
|
+
[mxie]: https://github.com/mxie
|
505
|
+
[gabebw]: https://github.com/gabebw
|
506
|
+
[rmm5t]: https://github.com/rmm5t
|
507
|
+
[jferris]: https://github.com/jferris
|
508
|
+
[tammersaleh]: https://github.com/tammersaleh
|
492
509
|
|
493
510
|
## Copyright/License
|
494
511
|
|
495
|
-
Shoulda Matchers is copyright ©
|
512
|
+
Shoulda Matchers is copyright © Tammer Saleh and [thoughtbot,
|
496
513
|
inc][thoughtbot-website]. It is free and opensource software and may be
|
497
514
|
redistributed under the terms specified in the [LICENSE](LICENSE) file.
|
498
515
|
|
@@ -502,7 +519,7 @@ redistributed under the terms specified in the [LICENSE](LICENSE) file.
|
|
502
519
|
|
503
520
|
![thoughtbot][thoughtbot-logo]
|
504
521
|
|
505
|
-
[thoughtbot-logo]: https://
|
522
|
+
[thoughtbot-logo]: https://thoughtbot.com/brand_assets/93:44.svg
|
506
523
|
|
507
524
|
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
508
525
|
|
@@ -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:
|
@@ -276,16 +276,14 @@ module Shoulda
|
|
276
276
|
:context, :subparameter_name, :parameters_double_registry
|
277
277
|
|
278
278
|
def expectation
|
279
|
-
|
279
|
+
String.new('restrict parameters ').tap do |message|
|
280
|
+
if subparameter_name
|
281
|
+
message << "on #{subparameter_name.inspect} "
|
282
|
+
end
|
280
283
|
|
281
|
-
|
282
|
-
|
284
|
+
message << 'to '\
|
285
|
+
"#{format_parameter_names(expected_permitted_parameter_names)}"
|
283
286
|
end
|
284
|
-
|
285
|
-
message << 'to '\
|
286
|
-
"#{format_parameter_names(expected_permitted_parameter_names)}"
|
287
|
-
|
288
|
-
message
|
289
287
|
end
|
290
288
|
|
291
289
|
def reality
|
@@ -83,25 +83,23 @@ module Shoulda
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def expectation_description
|
86
|
-
|
87
|
-
|
88
|
-
string <<
|
89
|
-
if key_set?
|
90
|
-
" #{store.name}[#{key.inspect}]"
|
91
|
-
else
|
92
|
-
" any key in #{store.name}"
|
93
|
-
end
|
94
|
-
|
95
|
-
if expected_value_set?
|
86
|
+
String.new('set').tap do |string|
|
96
87
|
string <<
|
97
|
-
if
|
98
|
-
"
|
88
|
+
if key_set?
|
89
|
+
" #{store.name}[#{key.inspect}]"
|
99
90
|
else
|
100
|
-
"
|
91
|
+
" any key in #{store.name}"
|
101
92
|
end
|
102
|
-
end
|
103
93
|
|
104
|
-
|
94
|
+
if expected_value_set?
|
95
|
+
string <<
|
96
|
+
if expected_value.is_a?(Regexp)
|
97
|
+
" to a value matching #{expected_value.inspect}"
|
98
|
+
else
|
99
|
+
" to #{expected_value.inspect}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
105
103
|
end
|
106
104
|
end
|
107
105
|
end
|
@@ -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
|
@@ -541,7 +550,7 @@ module Shoulda
|
|
541
550
|
end
|
542
551
|
|
543
552
|
def default_failure_message_preface
|
544
|
-
|
553
|
+
String.new.tap do |preface|
|
545
554
|
if descriptions_for_preset_values.any?
|
546
555
|
preface << 'After setting '
|
547
556
|
preface << descriptions_for_preset_values.to_sentence
|
@@ -0,0 +1,157 @@
|
|
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 comparison_description
|
82
|
+
"#{comparison_expectation} #{@value}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def comparison_submatchers
|
86
|
+
@_comparison_submatchers ||=
|
87
|
+
NumericalityMatchers::Submatchers.new(build_comparison_submatchers)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def build_comparison_submatchers
|
93
|
+
comparison_combos.map do |diff, submatcher_method_name|
|
94
|
+
matcher = __send__(submatcher_method_name, diff, nil)
|
95
|
+
matcher.with_message(@message, values: { count: option_value })
|
96
|
+
matcher
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def comparison_combos
|
101
|
+
diffs_to_compare.zip(submatcher_method_names)
|
102
|
+
end
|
103
|
+
|
104
|
+
def submatcher_method_names
|
105
|
+
assertions.map do |value|
|
106
|
+
if value
|
107
|
+
:allow_value_matcher
|
108
|
+
else
|
109
|
+
:disallow_value_matcher
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def assertions
|
115
|
+
ERROR_MESSAGES[@operator][:assertions]
|
116
|
+
end
|
117
|
+
|
118
|
+
def option_value
|
119
|
+
if defined?(@_option_value)
|
120
|
+
@_option_value
|
121
|
+
else
|
122
|
+
@_option_value =
|
123
|
+
case @value
|
124
|
+
when Proc then @value.call(@subject)
|
125
|
+
when Symbol then @subject.send(@value)
|
126
|
+
else @value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def diffs_to_compare
|
132
|
+
diff_to_compare = @matcher.diff_to_compare
|
133
|
+
values = case option_value
|
134
|
+
when String then diffs_when_string(diff_to_compare)
|
135
|
+
else [-1, 0, 1].map { |sign| option_value + (diff_to_compare * sign) }
|
136
|
+
end
|
137
|
+
|
138
|
+
if @matcher.given_numeric_column?
|
139
|
+
values
|
140
|
+
else
|
141
|
+
values.map(&:to_s)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def diffs_when_string(diff_to_compare)
|
146
|
+
[-1, 0, 1].map do |sign|
|
147
|
+
option_value[0..-2] + (option_value[-1].ord + diff_to_compare * sign).chr
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def comparison_expectation
|
152
|
+
ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -47,6 +47,8 @@ module Shoulda
|
|
47
47
|
did_not_authenticate_correct_password: 'expected %{subject} to'\
|
48
48
|
' authenticate the correct %{attribute}',
|
49
49
|
method_not_found: 'expected %{subject} to respond to %{methods}',
|
50
|
+
should_not_have_secure_password: 'expected %{subject} to'\
|
51
|
+
' not %{description}!',
|
50
52
|
}.freeze
|
51
53
|
|
52
54
|
def initialize(attribute)
|
@@ -69,6 +71,11 @@ module Shoulda
|
|
69
71
|
failure.nil?
|
70
72
|
end
|
71
73
|
|
74
|
+
def failure_message_when_negated
|
75
|
+
MESSAGES[:should_not_have_secure_password] %
|
76
|
+
{ subject: @subject.class, description: description }
|
77
|
+
end
|
78
|
+
|
72
79
|
protected
|
73
80
|
|
74
81
|
attr_reader :subject
|
@@ -59,7 +59,7 @@ module Shoulda
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def build_comparison_submatcher(value, operator)
|
62
|
-
|
62
|
+
ComparisonMatcher.new(@numericality_matcher, value, operator).
|
63
63
|
for(@attribute).
|
64
64
|
with_message(@message).
|
65
65
|
on(@context)
|
@@ -14,21 +14,17 @@ module Shoulda
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def failure_message
|
17
|
-
|
17
|
+
failing_submatcher.failure_message
|
18
18
|
end
|
19
19
|
|
20
20
|
def failure_message_when_negated
|
21
|
-
|
21
|
+
non_failing_submatcher.failure_message_when_negated
|
22
22
|
end
|
23
23
|
|
24
24
|
def add(submatcher)
|
25
25
|
@submatchers << submatcher
|
26
26
|
end
|
27
27
|
|
28
|
-
def last_failing_submatcher
|
29
|
-
failing_submatchers.last
|
30
|
-
end
|
31
|
-
|
32
28
|
private
|
33
29
|
|
34
30
|
def failing_submatchers
|
@@ -36,6 +32,20 @@ module Shoulda
|
|
36
32
|
submatcher.matches?(@subject)
|
37
33
|
end
|
38
34
|
end
|
35
|
+
|
36
|
+
def non_failing_submatchers
|
37
|
+
@_non_failing_submatchers ||= @submatchers.reject do |submatcher|
|
38
|
+
submatcher.does_not_match?(@subject)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def failing_submatcher
|
43
|
+
failing_submatchers.last
|
44
|
+
end
|
45
|
+
|
46
|
+
def non_failing_submatcher
|
47
|
+
non_failing_submatchers.last
|
48
|
+
end
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
@@ -143,12 +143,6 @@ module Shoulda
|
|
143
143
|
@subject.class.reflect_on_association(@attribute)
|
144
144
|
end
|
145
145
|
|
146
|
-
def array_column?
|
147
|
-
@subject.class.respond_to?(:columns_hash) &&
|
148
|
-
@subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
|
149
|
-
@subject.class.columns_hash[@attribute.to_s].array
|
150
|
-
end
|
151
|
-
|
152
146
|
def enum_column?
|
153
147
|
@subject.class.respond_to?(:defined_enums) &&
|
154
148
|
@subject.class.defined_enums.key?(@attribute.to_s)
|