shoulda-matchers 5.0.0 → 5.3.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 +3 -3
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -34
- 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 +43 -0
- data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
- data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +13 -1
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +2 -2
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +6 -4
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +46 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
- data/lib/shoulda/matchers/active_model.rb +2 -0
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +115 -21
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +21 -3
- data/lib/shoulda/matchers/rails_shim.rb +14 -0
- data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +1 -1
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d083d22f49f99bf60d6c6bdfed7ea08ea1f838a90b4aed439d84108ebc814af9
|
4
|
+
data.tar.gz: 90cc8440d8c63da1c338306b3d9f129cc9af8dc1a80277d9e7f5b233f2bbe115
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09b1dcf7e19179b50992d34cf73a86e72727cf27a1db911f2adb98fe291dc4d3d711da962d3efdbec60d2b1f280dcecd441e725a7e6a701a152ff60fdd8e340c'
|
7
|
+
data.tar.gz: 8ba54626246e133cfd86b84aba3d3b05487166d6359b2ba97256af242a057b5ff6cd40d5eae35bd35f619c9a9b0f39cc2c8b6508e299b44e2605e3cdf4f64078
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ complex, and error-prone.
|
|
19
19
|
|
20
20
|
## Quick links
|
21
21
|
|
22
|
-
📖 **[Read the documentation for the latest version][rubydocs].**
|
22
|
+
📖 **[Read the documentation for the latest version][rubydocs].**
|
23
23
|
📢 **[See what's changed in recent versions][changelog].**
|
24
24
|
|
25
25
|
[rubydocs]: https://matchers.shoulda.io/docs
|
@@ -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', '~> 5.0'
|
121
121
|
end
|
122
122
|
```
|
123
123
|
|
@@ -492,7 +492,7 @@ Albuk][guialbuk].
|
|
492
492
|
|
493
493
|
## Copyright/License
|
494
494
|
|
495
|
-
Shoulda Matchers is copyright © 2006-
|
495
|
+
Shoulda Matchers is copyright © 2006-2022 Tammer Saleh and [thoughtbot,
|
496
496
|
inc][thoughtbot-website]. It is free and opensource software and may be
|
497
497
|
redistributed under the terms specified in the [LICENSE](LICENSE) file.
|
498
498
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
1
3
|
module Shoulda
|
2
4
|
module Matchers
|
3
5
|
module ActiveModel
|
@@ -31,6 +33,8 @@ module Shoulda
|
|
31
33
|
},
|
32
34
|
}.freeze
|
33
35
|
|
36
|
+
delegate :failure_message, :failure_message_when_negated, to: :submatchers
|
37
|
+
|
34
38
|
def initialize(numericality_matcher, value, operator)
|
35
39
|
super(nil)
|
36
40
|
unless numericality_matcher.respond_to? :diff_to_compare
|
@@ -72,49 +76,24 @@ module Shoulda
|
|
72
76
|
|
73
77
|
def matches?(subject)
|
74
78
|
@subject = subject
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
def failure_message
|
79
|
-
last_failing_submatcher.failure_message
|
80
|
-
end
|
81
|
-
|
82
|
-
def failure_message_when_negated
|
83
|
-
last_failing_submatcher.failure_message_when_negated
|
79
|
+
submatchers.matches?(subject)
|
84
80
|
end
|
85
81
|
|
86
82
|
def comparison_description
|
87
83
|
"#{comparison_expectation} #{@value}"
|
88
84
|
end
|
89
85
|
|
90
|
-
private
|
91
|
-
|
92
|
-
def all_bounds_correct?
|
93
|
-
failing_submatchers.empty?
|
94
|
-
end
|
95
|
-
|
96
|
-
def failing_submatchers
|
97
|
-
submatchers_and_results.
|
98
|
-
select { |x| !x[:matched] }.
|
99
|
-
map { |x| x[:matcher] }
|
100
|
-
end
|
101
|
-
|
102
|
-
def last_failing_submatcher
|
103
|
-
failing_submatchers.last
|
104
|
-
end
|
105
|
-
|
106
86
|
def submatchers
|
107
|
-
@_submatchers ||=
|
108
|
-
comparison_combos.map do |diff, submatcher_method_name|
|
109
|
-
matcher = __send__(submatcher_method_name, diff, nil)
|
110
|
-
matcher.with_message(@message, values: { count: @value })
|
111
|
-
matcher
|
112
|
-
end
|
87
|
+
@_submatchers ||= NumericalityMatchers::Submatchers.new(build_submatchers)
|
113
88
|
end
|
114
89
|
|
115
|
-
|
116
|
-
|
117
|
-
|
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
|
118
97
|
end
|
119
98
|
end
|
120
99
|
|
@@ -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
|
+
NumericalityMatchers::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,43 @@
|
|
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 failure_message
|
17
|
+
last_failing_submatcher.failure_message
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_when_negated
|
21
|
+
last_failing_submatcher.failure_message_when_negated
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(submatcher)
|
25
|
+
@submatchers << submatcher
|
26
|
+
end
|
27
|
+
|
28
|
+
def last_failing_submatcher
|
29
|
+
failing_submatchers.last
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def failing_submatchers
|
35
|
+
@_failing_submatchers ||= @submatchers.reject do |submatcher|
|
36
|
+
submatcher.matches?(@subject)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel
|
4
|
+
module Qualifiers
|
5
|
+
# @private
|
6
|
+
module AllowBlank
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
@expects_to_allow_blank = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def allow_blank
|
13
|
+
@expects_to_allow_blank = true
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def expects_to_allow_blank?
|
20
|
+
@expects_to_allow_blank
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -108,7 +108,9 @@ module Shoulda
|
|
108
108
|
obj
|
109
109
|
end
|
110
110
|
elsif array_column?
|
111
|
-
['an
|
111
|
+
['an arbitrary value']
|
112
|
+
elsif enum_column?
|
113
|
+
enum_values.first
|
112
114
|
else
|
113
115
|
case column_type
|
114
116
|
when :integer, :float then 1
|
@@ -116,6 +118,7 @@ module Shoulda
|
|
116
118
|
when :datetime, :time, :timestamp then Time.current
|
117
119
|
when :date then Date.new
|
118
120
|
when :binary then '0'
|
121
|
+
when :uuid then SecureRandom.uuid
|
119
122
|
else 'an arbitrary value'
|
120
123
|
end
|
121
124
|
end
|
@@ -145,6 +148,15 @@ module Shoulda
|
|
145
148
|
@subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
|
146
149
|
@subject.class.columns_hash[@attribute.to_s].array
|
147
150
|
end
|
151
|
+
|
152
|
+
def enum_column?
|
153
|
+
@subject.class.respond_to?(:defined_enums) &&
|
154
|
+
@subject.class.defined_enums.key?(@attribute.to_s)
|
155
|
+
end
|
156
|
+
|
157
|
+
def enum_values
|
158
|
+
@subject.class.defined_enums[@attribute.to_s].values
|
159
|
+
end
|
148
160
|
end
|
149
161
|
end
|
150
162
|
end
|
@@ -33,9 +33,9 @@ module Shoulda
|
|
33
33
|
#
|
34
34
|
# class Game
|
35
35
|
# include ActiveModel::Model
|
36
|
-
# attr_accessor :
|
36
|
+
# attr_accessor :floors_with_enemies
|
37
37
|
#
|
38
|
-
# validates_exclusion_of :
|
38
|
+
# validates_exclusion_of :floors_with_enemies, in: 5..8
|
39
39
|
# end
|
40
40
|
#
|
41
41
|
# # RSpec
|
@@ -3,6 +3,8 @@ require 'date'
|
|
3
3
|
|
4
4
|
module Shoulda
|
5
5
|
module Matchers
|
6
|
+
class ExampleClass; end
|
7
|
+
|
6
8
|
module ActiveModel
|
7
9
|
# The `validate_inclusion_of` matcher tests usage of the
|
8
10
|
# `validates_inclusion_of` validation, asserting that an attribute can
|
@@ -43,12 +45,12 @@ module Shoulda
|
|
43
45
|
#
|
44
46
|
# # RSpec
|
45
47
|
# RSpec.describe Issue, type: :model do
|
46
|
-
# it { should validate_inclusion_of(:
|
48
|
+
# it { should validate_inclusion_of(:priority).in_range(1..5) }
|
47
49
|
# end
|
48
50
|
#
|
49
51
|
# # Minitest (Shoulda)
|
50
52
|
# class IssueTest < ActiveSupport::TestCase
|
51
|
-
# should validate_inclusion_of(:
|
53
|
+
# should validate_inclusion_of(:priority).in_range(1..5)
|
52
54
|
# end
|
53
55
|
#
|
54
56
|
# #### Caveats
|
@@ -269,7 +271,7 @@ module Shoulda
|
|
269
271
|
# @private
|
270
272
|
class ValidateInclusionOfMatcher < ValidationMatcher
|
271
273
|
BLANK_VALUES = ['', ' ', "\n", "\r", "\t", "\f"].freeze
|
272
|
-
ARBITRARY_OUTSIDE_STRING =
|
274
|
+
ARBITRARY_OUTSIDE_STRING = Shoulda::Matchers::ExampleClass.name
|
273
275
|
ARBITRARY_OUTSIDE_INTEGER = 123456789
|
274
276
|
ARBITRARY_OUTSIDE_DECIMAL = BigDecimal('0.123456789')
|
275
277
|
ARBITRARY_OUTSIDE_DATE = Date.jd(9999999)
|
@@ -355,7 +357,7 @@ EOT
|
|
355
357
|
description = "validate that :#{@attribute}"
|
356
358
|
|
357
359
|
description <<
|
358
|
-
if @array.
|
360
|
+
if @array.count > 1
|
359
361
|
" is either #{inspected_array}"
|
360
362
|
else
|
361
363
|
" is #{inspected_array}"
|
@@ -276,6 +276,33 @@ module Shoulda
|
|
276
276
|
# should validate_numericality_of(:birth_day).odd
|
277
277
|
# end
|
278
278
|
#
|
279
|
+
# ##### is_in
|
280
|
+
#
|
281
|
+
# Use `is_in` to test usage of the `:in` option.
|
282
|
+
# This asserts that the attribute can take a number which is contained
|
283
|
+
# in the given range.
|
284
|
+
#
|
285
|
+
# class Person
|
286
|
+
# include ActiveModel::Model
|
287
|
+
# attr_accessor :legal_age
|
288
|
+
#
|
289
|
+
# validates_numericality_of :birth_month, in: 1..12
|
290
|
+
# end
|
291
|
+
#
|
292
|
+
# # RSpec
|
293
|
+
# RSpec.describe Person, type: :model do
|
294
|
+
# it do
|
295
|
+
# should validate_numericality_of(:birth_month).
|
296
|
+
# is_in(1..12)
|
297
|
+
# end
|
298
|
+
# end
|
299
|
+
#
|
300
|
+
# # Minitest (Shoulda)
|
301
|
+
# class PersonTest < ActiveSupport::TestCase
|
302
|
+
# should validate_numericality_of(:birth_month).
|
303
|
+
# is_in(1..12)
|
304
|
+
# end
|
305
|
+
#
|
279
306
|
# ##### with_message
|
280
307
|
#
|
281
308
|
# Use `with_message` if you are using a custom validation message.
|
@@ -426,6 +453,13 @@ module Shoulda
|
|
426
453
|
self
|
427
454
|
end
|
428
455
|
|
456
|
+
def is_in(range)
|
457
|
+
prepare_submatcher(
|
458
|
+
NumericalityMatchers::RangeMatcher.new(self, @attribute, range),
|
459
|
+
)
|
460
|
+
self
|
461
|
+
end
|
462
|
+
|
429
463
|
def with_message(message)
|
430
464
|
@expects_custom_validation_message = true
|
431
465
|
@expected_message = message
|
@@ -457,6 +491,10 @@ module Shoulda
|
|
457
491
|
description << "validate that :#{@attribute} looks like "
|
458
492
|
description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
|
459
493
|
|
494
|
+
if range_description.present?
|
495
|
+
description << " #{range_description}"
|
496
|
+
end
|
497
|
+
|
460
498
|
if comparison_descriptions.present?
|
461
499
|
description << " #{comparison_descriptions}"
|
462
500
|
end
|
@@ -673,6 +711,14 @@ module Shoulda
|
|
673
711
|
end
|
674
712
|
end
|
675
713
|
|
714
|
+
def range_description
|
715
|
+
range_submatcher = @submatchers.detect do |submatcher|
|
716
|
+
submatcher.respond_to? :range_description
|
717
|
+
end
|
718
|
+
|
719
|
+
range_submatcher&.range_description
|
720
|
+
end
|
721
|
+
|
676
722
|
def model
|
677
723
|
@subject.class
|
678
724
|
end
|
@@ -78,6 +78,27 @@ module Shoulda
|
|
78
78
|
# should validate_presence_of(:nickname).allow_nil
|
79
79
|
# end
|
80
80
|
#
|
81
|
+
# #### allow_blank
|
82
|
+
#
|
83
|
+
# Use `allow_blank` to assert that the attribute allows blank.
|
84
|
+
#
|
85
|
+
# class Robot
|
86
|
+
# include ActiveModel::Model
|
87
|
+
# attr_accessor :nickname
|
88
|
+
#
|
89
|
+
# validates_presence_of :nickname, allow_blank: true
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# # RSpec
|
93
|
+
# RSpec.describe Robot, type: :model do
|
94
|
+
# it { should validate_presence_of(:nickname).allow_blank }
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# # Minitest (Shoulda)
|
98
|
+
# class RobotTest < ActiveSupport::TestCase
|
99
|
+
# should validate_presence_of(:nickname).allow_blank
|
100
|
+
# end
|
101
|
+
#
|
81
102
|
# ##### on
|
82
103
|
#
|
83
104
|
# Use `on` if your validation applies only under a certain context.
|
@@ -133,6 +154,7 @@ module Shoulda
|
|
133
154
|
# @private
|
134
155
|
class ValidatePresenceOfMatcher < ValidationMatcher
|
135
156
|
include Qualifiers::AllowNil
|
157
|
+
include Qualifiers::AllowBlank
|
136
158
|
|
137
159
|
def initialize(attribute)
|
138
160
|
super
|
@@ -144,7 +166,8 @@ module Shoulda
|
|
144
166
|
|
145
167
|
possibly_ignore_interference_by_writer
|
146
168
|
|
147
|
-
if secure_password_being_validated?
|
169
|
+
if secure_password_being_validated? &&
|
170
|
+
Shoulda::Matchers::RailsShim.active_model_lt_7?
|
148
171
|
ignore_interference_by_writer.default_to(when: :blank?)
|
149
172
|
|
150
173
|
disallowed_values.all? do |value|
|
@@ -152,6 +175,7 @@ module Shoulda
|
|
152
175
|
end
|
153
176
|
else
|
154
177
|
(!expects_to_allow_nil? || allows_value_of(nil)) &&
|
178
|
+
(!expects_to_allow_blank? || allows_value_of('')) &&
|
155
179
|
disallowed_values.all? do |value|
|
156
180
|
disallows_original_or_typecast_value?(value)
|
157
181
|
end
|
@@ -171,6 +195,7 @@ module Shoulda
|
|
171
195
|
end
|
172
196
|
else
|
173
197
|
(expects_to_allow_nil? && disallows_value_of(nil)) ||
|
198
|
+
(expects_to_allow_blank? && disallows_value_of('')) ||
|
174
199
|
disallowed_values.any? do |value|
|
175
200
|
allows_original_or_typecast_value?(value)
|
176
201
|
end
|
@@ -208,7 +233,7 @@ validation for you? Instead of using `validate_presence_of`, try
|
|
208
233
|
end
|
209
234
|
|
210
235
|
def possibly_ignore_interference_by_writer
|
211
|
-
if secure_password_being_validated?
|
236
|
+
if secure_password_being_validated? && RailsShim.active_model_lt_7?
|
212
237
|
ignore_interference_by_writer.default_to(when: :blank?)
|
213
238
|
end
|
214
239
|
end
|
@@ -241,11 +266,11 @@ validation for you? Instead of using `validate_presence_of`, try
|
|
241
266
|
else
|
242
267
|
values = []
|
243
268
|
|
244
|
-
if attribute_accepts_string_values?
|
269
|
+
if attribute_accepts_string_values? && !expects_to_allow_blank?
|
245
270
|
values << ''
|
246
271
|
end
|
247
272
|
|
248
|
-
if !expects_to_allow_nil?
|
273
|
+
if !expects_to_allow_nil? && !expects_to_allow_blank?
|
249
274
|
values << nil
|
250
275
|
end
|
251
276
|
|
@@ -26,6 +26,8 @@ require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
|
|
26
26
|
require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
|
27
27
|
require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
|
28
28
|
require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
|
29
|
+
require 'shoulda/matchers/active_model/numericality_matchers/range_matcher'
|
30
|
+
require 'shoulda/matchers/active_model/numericality_matchers/submatchers'
|
29
31
|
require 'shoulda/matchers/active_model/errors'
|
30
32
|
require 'shoulda/matchers/active_model/have_secure_password_matcher'
|
31
33
|
|
@@ -116,7 +116,7 @@ module Shoulda
|
|
116
116
|
## ##### with_prefix
|
117
117
|
#
|
118
118
|
# Use `with_prefix` to test that the enum is defined with a `_prefix`
|
119
|
-
# option (Rails
|
119
|
+
# option (Rails 6+ only). Can take either a boolean or a symbol:
|
120
120
|
#
|
121
121
|
# class Issue < ActiveRecord::Base
|
122
122
|
# enum status: [:open, :closed], _prefix: :old
|
@@ -163,6 +163,30 @@ module Shoulda
|
|
163
163
|
# with_suffix
|
164
164
|
# end
|
165
165
|
#
|
166
|
+
# ##### without_scopes
|
167
|
+
#
|
168
|
+
# Use `without_scopes` to test that the enum is defined with
|
169
|
+
# '_scopes: false' option (Rails 5 only). Can take either a boolean or a
|
170
|
+
# symbol:
|
171
|
+
#
|
172
|
+
# class Issue < ActiveRecord::Base
|
173
|
+
# enum status: [:open, :closed], _scopes: false
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# # RSpec
|
177
|
+
# RSpec.describe Issue, type: :model do
|
178
|
+
# it do
|
179
|
+
# should define_enum_for(:status).
|
180
|
+
# without_scopes
|
181
|
+
# end
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# # Minitest (Shoulda)
|
185
|
+
# class ProcessTest < ActiveSupport::TestCase
|
186
|
+
# should define_enum_for(:status).
|
187
|
+
# without_scopes
|
188
|
+
# end
|
189
|
+
#
|
166
190
|
# @return [DefineEnumForMatcher]
|
167
191
|
#
|
168
192
|
def define_enum_for(attribute_name)
|
@@ -173,7 +197,7 @@ module Shoulda
|
|
173
197
|
class DefineEnumForMatcher
|
174
198
|
def initialize(attribute_name)
|
175
199
|
@attribute_name = attribute_name
|
176
|
-
@options = { expected_enum_values: [] }
|
200
|
+
@options = { expected_enum_values: [], scopes: true }
|
177
201
|
end
|
178
202
|
|
179
203
|
def description
|
@@ -226,13 +250,19 @@ module Shoulda
|
|
226
250
|
self
|
227
251
|
end
|
228
252
|
|
253
|
+
def without_scopes
|
254
|
+
options[:scopes] = false
|
255
|
+
self
|
256
|
+
end
|
257
|
+
|
229
258
|
def matches?(subject)
|
230
259
|
@record = subject
|
231
260
|
|
232
261
|
enum_defined? &&
|
233
262
|
enum_values_match? &&
|
234
263
|
column_type_matches? &&
|
235
|
-
enum_value_methods_exist?
|
264
|
+
enum_value_methods_exist? &&
|
265
|
+
scope_presence_matches?
|
236
266
|
end
|
237
267
|
|
238
268
|
def failure_message
|
@@ -294,6 +324,10 @@ module Shoulda
|
|
294
324
|
expectation << "_#{expected_suffix}".inspect
|
295
325
|
end
|
296
326
|
|
327
|
+
if exclude_scopes?
|
328
|
+
expectation << ' with no scopes'
|
329
|
+
end
|
330
|
+
|
297
331
|
expectation
|
298
332
|
else
|
299
333
|
simple_description
|
@@ -387,37 +421,81 @@ module Shoulda
|
|
387
421
|
end
|
388
422
|
|
389
423
|
def enum_value_methods_exist?
|
390
|
-
|
391
|
-
|
424
|
+
if instance_methods_exist?
|
425
|
+
true
|
426
|
+
else
|
427
|
+
message = missing_methods_message
|
428
|
+
|
429
|
+
message << " (we can't tell which)"
|
430
|
+
|
431
|
+
@failure_message_continuation = message
|
432
|
+
|
433
|
+
false
|
392
434
|
end
|
435
|
+
end
|
393
436
|
|
394
|
-
|
437
|
+
def scope_presence_matches?
|
438
|
+
if exclude_scopes?
|
439
|
+
if singleton_methods_exist?
|
440
|
+
message = "#{attribute_name.inspect} does map to these values "
|
441
|
+
message << 'but class scope methods were present'
|
442
|
+
|
443
|
+
@failure_message_continuation = message
|
444
|
+
|
445
|
+
false
|
446
|
+
else
|
447
|
+
true
|
448
|
+
end
|
449
|
+
elsif singleton_methods_exist?
|
395
450
|
true
|
396
451
|
else
|
397
|
-
|
398
|
-
|
452
|
+
if enum_defined?
|
453
|
+
message = 'But the class scope methods are not present'
|
454
|
+
else
|
455
|
+
message = missing_methods_message
|
399
456
|
|
400
|
-
|
401
|
-
|
402
|
-
message << 'configured with either a different prefix or '
|
403
|
-
message << 'suffix, or no prefix or suffix at all'
|
404
|
-
else
|
405
|
-
message << 'configured with either a different prefix or no '
|
406
|
-
message << 'prefix at all'
|
407
|
-
end
|
408
|
-
elsif expected_suffix
|
409
|
-
message << 'configured with either a different suffix or no '
|
410
|
-
message << 'suffix at all'
|
457
|
+
message << 'or the class scope methods are not present'
|
458
|
+
message << " (we can't tell which)"
|
411
459
|
end
|
412
460
|
|
413
|
-
message << " (we can't tell which)"
|
414
|
-
|
415
461
|
@failure_message_continuation = message
|
416
462
|
|
417
463
|
false
|
418
464
|
end
|
419
465
|
end
|
420
466
|
|
467
|
+
def missing_methods_message
|
468
|
+
message = "#{attribute_name.inspect} does map to these "
|
469
|
+
message << 'values, but the enum is '
|
470
|
+
|
471
|
+
if expected_prefix
|
472
|
+
if expected_suffix
|
473
|
+
message << 'configured with either a different prefix or '
|
474
|
+
message << 'suffix, or no prefix or suffix at all'
|
475
|
+
else
|
476
|
+
message << 'configured with either a different prefix or no '
|
477
|
+
message << 'prefix at all'
|
478
|
+
end
|
479
|
+
elsif expected_suffix
|
480
|
+
message << 'configured with either a different suffix or no '
|
481
|
+
message << 'suffix at all'
|
482
|
+
else
|
483
|
+
''
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def singleton_methods_exist?
|
488
|
+
expected_singleton_methods.all? do |method|
|
489
|
+
model.singleton_methods.include?(method)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def instance_methods_exist?
|
494
|
+
expected_instance_methods.all? do |method|
|
495
|
+
record.methods.include?(method)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
421
499
|
def expected_singleton_methods
|
422
500
|
expected_enum_value_names.map do |name|
|
423
501
|
[expected_prefix, name, expected_suffix].
|
@@ -427,6 +505,18 @@ module Shoulda
|
|
427
505
|
end
|
428
506
|
end
|
429
507
|
|
508
|
+
def expected_instance_methods
|
509
|
+
methods = expected_enum_value_names.map do |name|
|
510
|
+
[expected_prefix, name, expected_suffix].
|
511
|
+
select(&:present?).
|
512
|
+
join('_')
|
513
|
+
end
|
514
|
+
|
515
|
+
methods.flat_map do |m|
|
516
|
+
["#{m}?".to_sym, "#{m}!".to_sym]
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
430
520
|
def expected_prefix
|
431
521
|
if options.include?(:prefix)
|
432
522
|
if options[:prefix] == true
|
@@ -447,6 +537,10 @@ module Shoulda
|
|
447
537
|
end
|
448
538
|
end
|
449
539
|
|
540
|
+
def exclude_scopes?
|
541
|
+
!options[:scopes]
|
542
|
+
end
|
543
|
+
|
450
544
|
def to_hash(value)
|
451
545
|
if value.is_a?(Array)
|
452
546
|
value.each_with_index.inject({}) do |hash, (item, index)|
|
@@ -52,7 +52,7 @@ module Shoulda
|
|
52
52
|
#
|
53
53
|
# Use `with_options` to assert that a column has been defined with
|
54
54
|
# certain options (`:precision`, `:limit`, `:default`, `:null`, `:scale`,
|
55
|
-
# or `:
|
55
|
+
# `:primary` or `:array`).
|
56
56
|
#
|
57
57
|
# class CreatePhones < ActiveRecord::Migration
|
58
58
|
# def change
|
@@ -84,7 +84,7 @@ module Shoulda
|
|
84
84
|
|
85
85
|
# @private
|
86
86
|
class HaveDbColumnMatcher
|
87
|
-
OPTIONS = %i(precision limit default null scale primary).freeze
|
87
|
+
OPTIONS = %i(precision limit default null scale primary array).freeze
|
88
88
|
|
89
89
|
def initialize(column)
|
90
90
|
@column = column
|
@@ -115,7 +115,8 @@ module Shoulda
|
|
115
115
|
correct_default? &&
|
116
116
|
correct_null? &&
|
117
117
|
correct_scale? &&
|
118
|
-
correct_primary?
|
118
|
+
correct_primary? &&
|
119
|
+
correct_array?
|
119
120
|
end
|
120
121
|
|
121
122
|
def failure_message
|
@@ -258,6 +259,23 @@ module Shoulda
|
|
258
259
|
end
|
259
260
|
end
|
260
261
|
|
262
|
+
def correct_array?
|
263
|
+
return true unless @options.key?(:array)
|
264
|
+
|
265
|
+
if matched_column.array? == @options[:array]
|
266
|
+
true
|
267
|
+
else
|
268
|
+
@missing = "#{model_class} has a db column named #{@column} "
|
269
|
+
@missing <<
|
270
|
+
if @options[:primary]
|
271
|
+
'that is not array, but should be'
|
272
|
+
else
|
273
|
+
'that is array, but should not be'
|
274
|
+
end
|
275
|
+
false
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
261
279
|
def matched_column
|
262
280
|
@_matched_column ||= begin
|
263
281
|
column = model_class.columns.detect do |each|
|
@@ -19,6 +19,20 @@ module Shoulda
|
|
19
19
|
Gem::Version.new('0')
|
20
20
|
end
|
21
21
|
|
22
|
+
def active_model_version
|
23
|
+
Gem::Version.new(::ActiveModel::VERSION::STRING)
|
24
|
+
rescue NameError
|
25
|
+
Gem::Version.new('0')
|
26
|
+
end
|
27
|
+
|
28
|
+
def active_model_st_6_1?
|
29
|
+
Gem::Requirement.new('< 6.1').satisfied_by?(active_model_version)
|
30
|
+
end
|
31
|
+
|
32
|
+
def active_model_lt_7?
|
33
|
+
Gem::Requirement.new('< 7').satisfied_by?(active_model_version)
|
34
|
+
end
|
35
|
+
|
22
36
|
def generate_validation_message(
|
23
37
|
record,
|
24
38
|
attribute,
|
data/shoulda-matchers.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
|
|
26
26
|
|
27
27
|
s.metadata = {
|
28
28
|
'bug_tracker_uri' => 'https://github.com/thoughtbot/shoulda-matchers/issues',
|
29
|
-
'changelog_uri' => 'https://github.com/thoughtbot/shoulda-matchers/blob/
|
29
|
+
'changelog_uri' => 'https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md',
|
30
30
|
'documentation_uri' => 'https://matchers.shoulda.io/docs',
|
31
31
|
'homepage_uri' => 'https://matchers.shoulda.io',
|
32
32
|
'source_code_uri' => 'https://github.com/thoughtbot/shoulda-matchers',
|
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: 5.
|
4
|
+
version: 5.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tammer Saleh
|
@@ -14,7 +14,7 @@ authors:
|
|
14
14
|
autorequire:
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
|
-
date:
|
17
|
+
date: 2022-12-16 00:00:00.000000000 Z
|
18
18
|
dependencies:
|
19
19
|
- !ruby/object:Gem::Dependency
|
20
20
|
name: activesupport
|
@@ -79,7 +79,10 @@ files:
|
|
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
|
81
81
|
- lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb
|
82
|
+
- lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb
|
83
|
+
- lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb
|
82
84
|
- lib/shoulda/matchers/active_model/qualifiers.rb
|
85
|
+
- lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb
|
83
86
|
- lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb
|
84
87
|
- lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb
|
85
88
|
- lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb
|
@@ -173,7 +176,7 @@ licenses:
|
|
173
176
|
- MIT
|
174
177
|
metadata:
|
175
178
|
bug_tracker_uri: https://github.com/thoughtbot/shoulda-matchers/issues
|
176
|
-
changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/
|
179
|
+
changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md
|
177
180
|
documentation_uri: https://matchers.shoulda.io/docs
|
178
181
|
homepage_uri: https://matchers.shoulda.io
|
179
182
|
source_code_uri: https://github.com/thoughtbot/shoulda-matchers
|
@@ -192,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
195
|
- !ruby/object:Gem::Version
|
193
196
|
version: '0'
|
194
197
|
requirements: []
|
195
|
-
rubygems_version: 3.
|
198
|
+
rubygems_version: 3.3.7
|
196
199
|
signing_key:
|
197
200
|
specification_version: 4
|
198
201
|
summary: Simple one-liner tests for common Rails functionality
|