shoulda-matchers 5.3.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/range_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +21 -6
- data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +534 -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 +32 -86
- 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/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 +8 -6
- data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
- 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 +10 -8
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
@@ -0,0 +1,151 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord
|
4
|
+
# The `normalize` matcher is used to ensure attribute normalizations
|
5
|
+
# are transforming attribute values as expected.
|
6
|
+
#
|
7
|
+
# Take this model for example:
|
8
|
+
#
|
9
|
+
# class User < ActiveRecord::Base
|
10
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# You can use `normalize` providing an input and defining the expected
|
14
|
+
# normalization output:
|
15
|
+
#
|
16
|
+
# # RSpec
|
17
|
+
# RSpec.describe User, type: :model do
|
18
|
+
# it do
|
19
|
+
# should normalize(:email).from(" ME@XYZ.COM\n").to("me@xyz.com")
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # Minitest (Shoulda)
|
24
|
+
# class User < ActiveSupport::TestCase
|
25
|
+
# should normalize(:email).from(" ME@XYZ.COM\n").to("me@xyz.com")
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# You can use `normalize` to test multiple attributes at once:
|
29
|
+
#
|
30
|
+
# class User < ActiveRecord::Base
|
31
|
+
# normalizes :email, :handle, with: -> value { value.strip.downcase }
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # RSpec
|
35
|
+
# RSpec.describe User, type: :model do
|
36
|
+
# it do
|
37
|
+
# should normalize(:email, :handle).from(" Example\n").to("example")
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # Minitest (Shoulda)
|
42
|
+
# class User < ActiveSupport::TestCase
|
43
|
+
# should normalize(:email, handle).from(" Example\n").to("example")
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# If the normalization accepts nil values with the `apply_to_nil` option,
|
47
|
+
# you just need to use `.from(nil).to("Your expected value here")`.
|
48
|
+
#
|
49
|
+
# class User < ActiveRecord::Base
|
50
|
+
# normalizes :name, with: -> name { name&.titleize || 'Untitled' },
|
51
|
+
# apply_to_nil: true
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# # RSpec
|
55
|
+
# RSpec.describe User, type: :model do
|
56
|
+
# it { should normalize(:name).from("jane doe").to("Jane Doe") }
|
57
|
+
# it { should normalize(:name).from(nil).to("Untitled") }
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# # Minitest (Shoulda)
|
61
|
+
# class User < ActiveSupport::TestCase
|
62
|
+
# should normalize(:name).from("jane doe").to("Jane Doe")
|
63
|
+
# should normalize(:name).from(nil).to("Untitled")
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# @return [NormalizeMatcher]
|
67
|
+
#
|
68
|
+
def normalize(*attributes)
|
69
|
+
if attributes.empty?
|
70
|
+
raise ArgumentError, 'need at least one attribute'
|
71
|
+
else
|
72
|
+
NormalizeMatcher.new(*attributes)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @private
|
77
|
+
class NormalizeMatcher
|
78
|
+
attr_reader :attributes, :from_value, :to_value, :failure_message,
|
79
|
+
:failure_message_when_negated
|
80
|
+
|
81
|
+
def initialize(*attributes)
|
82
|
+
@attributes = attributes
|
83
|
+
end
|
84
|
+
|
85
|
+
def description
|
86
|
+
%(
|
87
|
+
normalize #{attributes.to_sentence(last_word_connector: ' and ')} from
|
88
|
+
‹#{from_value.inspect}› to ‹#{to_value.inspect}›
|
89
|
+
).squish
|
90
|
+
end
|
91
|
+
|
92
|
+
def from(value)
|
93
|
+
@from_value = value
|
94
|
+
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def to(value)
|
99
|
+
@to_value = value
|
100
|
+
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def matches?(subject)
|
105
|
+
attributes.all? { |attribute| attribute_matches?(subject, attribute) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def does_not_match?(subject)
|
109
|
+
attributes.all? { |attribute| attribute_does_not_match?(subject, attribute) }
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def attribute_matches?(subject, attribute)
|
115
|
+
return true if normalize_attribute?(subject, attribute)
|
116
|
+
|
117
|
+
@failure_message = build_failure_message(
|
118
|
+
attribute,
|
119
|
+
subject.class.normalize_value_for(attribute, from_value),
|
120
|
+
)
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
def attribute_does_not_match?(subject, attribute)
|
125
|
+
return true unless normalize_attribute?(subject, attribute)
|
126
|
+
|
127
|
+
@failure_message_when_negated = build_failure_message_when_negated(attribute)
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
def normalize_attribute?(subject, attribute)
|
132
|
+
subject.class.normalize_value_for(attribute, from_value) == to_value
|
133
|
+
end
|
134
|
+
|
135
|
+
def build_failure_message(attribute, attribute_value)
|
136
|
+
%(
|
137
|
+
Expected to normalize #{attribute.inspect} from ‹#{from_value.inspect}› to
|
138
|
+
‹#{to_value.inspect}› but it was normalized to ‹#{attribute_value.inspect}›
|
139
|
+
).squish
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_failure_message_when_negated(attribute)
|
143
|
+
%(
|
144
|
+
Expected to not normalize #{attribute.inspect} from ‹#{from_value.inspect}› to
|
145
|
+
‹#{to_value.inspect}› but it was normalized
|
146
|
+
).squish
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -24,6 +24,7 @@ require 'shoulda/matchers/active_record/define_enum_for_matcher'
|
|
24
24
|
require 'shoulda/matchers/active_record/uniqueness'
|
25
25
|
require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher'
|
26
26
|
require 'shoulda/matchers/active_record/have_attached_matcher'
|
27
|
+
require 'shoulda/matchers/active_record/normalize_matcher'
|
27
28
|
|
28
29
|
module Shoulda
|
29
30
|
module Matchers
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Shoulda
|
2
2
|
module Matchers
|
3
3
|
# @private
|
4
|
-
module RailsShim
|
4
|
+
module RailsShim
|
5
5
|
class << self
|
6
6
|
def action_pack_version
|
7
7
|
Gem::Version.new(::ActionPack::VERSION::STRING)
|
@@ -9,10 +9,6 @@ module Shoulda
|
|
9
9
|
Gem::Version.new('0')
|
10
10
|
end
|
11
11
|
|
12
|
-
def active_record_gte_6?
|
13
|
-
Gem::Requirement.new('>= 6').satisfied_by?(active_record_version)
|
14
|
-
end
|
15
|
-
|
16
12
|
def active_record_version
|
17
13
|
Gem::Version.new(::ActiveRecord::VERSION::STRING)
|
18
14
|
rescue NameError
|
@@ -60,9 +56,10 @@ module Shoulda
|
|
60
56
|
end
|
61
57
|
|
62
58
|
def serialized_attributes_for(model)
|
59
|
+
type_serialized_defined = Object.const_defined?('ActiveRecord::Type::Serialized')
|
63
60
|
attribute_types_for(model).
|
64
61
|
inject({}) do |hash, (attribute_name, attribute_type)|
|
65
|
-
if attribute_type.is_a?(::ActiveRecord::Type::Serialized)
|
62
|
+
if type_serialized_defined && attribute_type.is_a?(::ActiveRecord::Type::Serialized)
|
66
63
|
hash.merge(attribute_name => attribute_type.coder)
|
67
64
|
else
|
68
65
|
hash
|
@@ -146,6 +143,10 @@ module Shoulda
|
|
146
143
|
model.respond_to?(:attribute_types)
|
147
144
|
end
|
148
145
|
|
146
|
+
def validates_column_options?
|
147
|
+
Gem::Requirement.new('>= 7.1.0').satisfied_by?(active_record_version)
|
148
|
+
end
|
149
|
+
|
149
150
|
private
|
150
151
|
|
151
152
|
def simply_generate_validation_message(
|
@@ -172,6 +173,7 @@ module Shoulda
|
|
172
173
|
I18n.translate(primary_translation_key, translate_options)
|
173
174
|
end
|
174
175
|
|
176
|
+
# @private
|
175
177
|
class FakeAttributeType
|
176
178
|
def initialize(model, attribute_name)
|
177
179
|
@model = model
|
data/lib/shoulda/matchers.rb
CHANGED
@@ -14,8 +14,8 @@ require 'shoulda/matchers/active_model'
|
|
14
14
|
require 'shoulda/matchers/active_record'
|
15
15
|
require 'shoulda/matchers/routing'
|
16
16
|
|
17
|
-
module Shoulda
|
18
|
-
module Matchers
|
17
|
+
module Shoulda # :nodoc:
|
18
|
+
module Matchers # :nodoc:
|
19
19
|
class << self
|
20
20
|
# @private
|
21
21
|
attr_accessor :assertion_exception_class
|
data/shoulda-matchers.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shoulda-matchers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tammer Saleh
|
@@ -11,10 +11,10 @@ authors:
|
|
11
11
|
- Matt Jankowski
|
12
12
|
- Stafford Brunk
|
13
13
|
- Elliot Winkler
|
14
|
-
autorequire:
|
14
|
+
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2023-12-22 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activesupport
|
@@ -69,12 +69,12 @@ files:
|
|
69
69
|
- lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb
|
70
70
|
- lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb
|
71
71
|
- lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb
|
72
|
+
- lib/shoulda/matchers/active_model/comparison_matcher.rb
|
72
73
|
- lib/shoulda/matchers/active_model/disallow_value_matcher.rb
|
73
74
|
- lib/shoulda/matchers/active_model/errors.rb
|
74
75
|
- lib/shoulda/matchers/active_model/have_secure_password_matcher.rb
|
75
76
|
- lib/shoulda/matchers/active_model/helpers.rb
|
76
77
|
- lib/shoulda/matchers/active_model/numericality_matchers.rb
|
77
|
-
- lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb
|
78
78
|
- lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb
|
79
79
|
- lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb
|
80
80
|
- lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb
|
@@ -88,6 +88,7 @@ files:
|
|
88
88
|
- lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb
|
89
89
|
- lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb
|
90
90
|
- lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb
|
91
|
+
- lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb
|
91
92
|
- lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb
|
92
93
|
- lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb
|
93
94
|
- lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb
|
@@ -122,6 +123,7 @@ files:
|
|
122
123
|
- lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb
|
123
124
|
- lib/shoulda/matchers/active_record/have_rich_text_matcher.rb
|
124
125
|
- lib/shoulda/matchers/active_record/have_secure_token_matcher.rb
|
126
|
+
- lib/shoulda/matchers/active_record/normalize_matcher.rb
|
125
127
|
- lib/shoulda/matchers/active_record/serialize_matcher.rb
|
126
128
|
- lib/shoulda/matchers/active_record/uniqueness.rb
|
127
129
|
- lib/shoulda/matchers/active_record/uniqueness/model.rb
|
@@ -180,7 +182,7 @@ metadata:
|
|
180
182
|
documentation_uri: https://matchers.shoulda.io/docs
|
181
183
|
homepage_uri: https://matchers.shoulda.io
|
182
184
|
source_code_uri: https://github.com/thoughtbot/shoulda-matchers
|
183
|
-
post_install_message:
|
185
|
+
post_install_message:
|
184
186
|
rdoc_options: []
|
185
187
|
require_paths:
|
186
188
|
- lib
|
@@ -188,15 +190,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
188
190
|
requirements:
|
189
191
|
- - ">="
|
190
192
|
- !ruby/object:Gem::Version
|
191
|
-
version:
|
193
|
+
version: 3.0.5
|
192
194
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
195
|
requirements:
|
194
196
|
- - ">="
|
195
197
|
- !ruby/object:Gem::Version
|
196
198
|
version: '0'
|
197
199
|
requirements: []
|
198
|
-
rubygems_version: 3.
|
199
|
-
signing_key:
|
200
|
+
rubygems_version: 3.4.20
|
201
|
+
signing_key:
|
200
202
|
specification_version: 4
|
201
203
|
summary: Simple one-liner tests for common Rails functionality
|
202
204
|
test_files: []
|
@@ -1,136 +0,0 @@
|
|
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 ComparisonMatcher < ValidationMatcher
|
9
|
-
ERROR_MESSAGES = {
|
10
|
-
:> => {
|
11
|
-
label: :greater_than,
|
12
|
-
assertions: [false, false, true],
|
13
|
-
},
|
14
|
-
:>= => {
|
15
|
-
label: :greater_than_or_equal_to,
|
16
|
-
assertions: [false, true, true],
|
17
|
-
},
|
18
|
-
:< => {
|
19
|
-
label: :less_than,
|
20
|
-
assertions: [true, false, false],
|
21
|
-
},
|
22
|
-
:<= => {
|
23
|
-
label: :less_than_or_equal_to,
|
24
|
-
assertions: [true, true, false],
|
25
|
-
},
|
26
|
-
:== => {
|
27
|
-
label: :equal_to,
|
28
|
-
assertions: [false, true, false],
|
29
|
-
},
|
30
|
-
:!= => {
|
31
|
-
label: :other_than,
|
32
|
-
assertions: [true, false, true],
|
33
|
-
},
|
34
|
-
}.freeze
|
35
|
-
|
36
|
-
delegate :failure_message, :failure_message_when_negated, to: :submatchers
|
37
|
-
|
38
|
-
def initialize(numericality_matcher, value, operator)
|
39
|
-
super(nil)
|
40
|
-
unless numericality_matcher.respond_to? :diff_to_compare
|
41
|
-
raise ArgumentError, 'numericality_matcher is invalid'
|
42
|
-
end
|
43
|
-
|
44
|
-
@numericality_matcher = numericality_matcher
|
45
|
-
@value = value
|
46
|
-
@operator = operator
|
47
|
-
@message = ERROR_MESSAGES[operator][:label]
|
48
|
-
end
|
49
|
-
|
50
|
-
def simple_description
|
51
|
-
description = ''
|
52
|
-
|
53
|
-
if expects_strict?
|
54
|
-
description << ' strictly'
|
55
|
-
end
|
56
|
-
|
57
|
-
description +
|
58
|
-
"disallow :#{attribute} from being a number that is not " +
|
59
|
-
"#{comparison_expectation} #{@value}"
|
60
|
-
end
|
61
|
-
|
62
|
-
def for(attribute)
|
63
|
-
@attribute = attribute
|
64
|
-
self
|
65
|
-
end
|
66
|
-
|
67
|
-
def with_message(message)
|
68
|
-
@expects_custom_validation_message = true
|
69
|
-
@message = message
|
70
|
-
self
|
71
|
-
end
|
72
|
-
|
73
|
-
def expects_custom_validation_message?
|
74
|
-
@expects_custom_validation_message
|
75
|
-
end
|
76
|
-
|
77
|
-
def matches?(subject)
|
78
|
-
@subject = subject
|
79
|
-
submatchers.matches?(subject)
|
80
|
-
end
|
81
|
-
|
82
|
-
def comparison_description
|
83
|
-
"#{comparison_expectation} #{@value}"
|
84
|
-
end
|
85
|
-
|
86
|
-
def submatchers
|
87
|
-
@_submatchers ||= NumericalityMatchers::Submatchers.new(build_submatchers)
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
def build_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: @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 diffs_to_compare
|
119
|
-
diff_to_compare = @numericality_matcher.diff_to_compare
|
120
|
-
values = [-1, 0, 1].map { |sign| @value + (diff_to_compare * sign) }
|
121
|
-
|
122
|
-
if @numericality_matcher.given_numeric_column?
|
123
|
-
values
|
124
|
-
else
|
125
|
-
values.map(&:to_s)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
def comparison_expectation
|
130
|
-
ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|