shoulda-matchers 4.1.1 → 4.4.1
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/README.md +163 -79
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +2 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +5 -1
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +2 -21
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +27 -3
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +32 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -9
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +25 -0
- data/lib/shoulda/matchers/active_record.rb +3 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +23 -7
- data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +2 -2
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +12 -6
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +20 -3
- data/lib/shoulda/matchers/active_record/have_attached_matcher.rb +147 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +106 -0
- data/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +79 -0
- data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +28 -9
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +2 -6
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +2 -2
- data/lib/shoulda/matchers/independent.rb +0 -1
- data/lib/shoulda/matchers/rails_shim.rb +42 -44
- data/lib/shoulda/matchers/util.rb +9 -2
- data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
- data/lib/shoulda/matchers/version.rb +1 -1
- metadata +9 -8
- data/MIT-LICENSE +0 -22
- data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +0 -37
@@ -0,0 +1,106 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord
|
4
|
+
# The `have_implicit_order_column` matcher tests that the model has `implicit_order_column`
|
5
|
+
# assigned to one of the table columns. (Rails 6+ only)
|
6
|
+
#
|
7
|
+
# class Product < ApplicationRecord
|
8
|
+
# self.implicit_order_column = :created_at
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# # RSpec
|
12
|
+
# RSpec.describe Product, type: :model do
|
13
|
+
# it { should have_implicit_order_column(:created_at) }
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Minitest (Shoulda)
|
17
|
+
# class ProductTest < ActiveSupport::TestCase
|
18
|
+
# should have_implicit_order_column(:created_at)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @return [HaveImplicitOrderColumnMatcher]
|
22
|
+
#
|
23
|
+
if RailsShim.active_record_gte_6?
|
24
|
+
def have_implicit_order_column(column_name)
|
25
|
+
HaveImplicitOrderColumnMatcher.new(column_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
class HaveImplicitOrderColumnMatcher
|
31
|
+
attr_reader :failure_message
|
32
|
+
|
33
|
+
def initialize(column_name)
|
34
|
+
@column_name = column_name
|
35
|
+
end
|
36
|
+
|
37
|
+
def matches?(subject)
|
38
|
+
@subject = subject
|
39
|
+
check_column_exists!
|
40
|
+
check_implicit_order_column_matches!
|
41
|
+
true
|
42
|
+
rescue SecondaryCheckFailedError => error
|
43
|
+
@failure_message = Shoulda::Matchers.word_wrap(
|
44
|
+
"Expected #{model.name} to #{expectation}, " +
|
45
|
+
"but that could not be proved: #{error.message}."
|
46
|
+
)
|
47
|
+
false
|
48
|
+
rescue PrimaryCheckFailedError => error
|
49
|
+
@failure_message = Shoulda::Matchers.word_wrap(
|
50
|
+
"Expected #{model.name} to #{expectation}, but #{error.message}."
|
51
|
+
)
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
55
|
+
def failure_message_when_negated
|
56
|
+
Shoulda::Matchers.word_wrap(
|
57
|
+
"Expected #{model.name} not to #{expectation}, but it did."
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def description
|
62
|
+
expectation
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :column_name, :subject
|
68
|
+
|
69
|
+
def check_column_exists!
|
70
|
+
matcher = HaveDbColumnMatcher.new(column_name)
|
71
|
+
|
72
|
+
if !matcher.matches?(@subject)
|
73
|
+
raise SecondaryCheckFailedError.new(
|
74
|
+
"The :#{model.table_name} table does not have a " +
|
75
|
+
":#{column_name} column"
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_implicit_order_column_matches!
|
81
|
+
if model.implicit_order_column.to_s != column_name.to_s
|
82
|
+
message =
|
83
|
+
if model.implicit_order_column.nil?
|
84
|
+
"implicit_order_column is not set"
|
85
|
+
else
|
86
|
+
"it is :#{model.implicit_order_column}"
|
87
|
+
end
|
88
|
+
|
89
|
+
raise PrimaryCheckFailedError.new(message)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def model
|
94
|
+
subject.class
|
95
|
+
end
|
96
|
+
|
97
|
+
def expectation
|
98
|
+
"have an implicit_order_column of :#{column_name}"
|
99
|
+
end
|
100
|
+
|
101
|
+
class SecondaryCheckFailedError < StandardError; end
|
102
|
+
class PrimaryCheckFailedError < StandardError; end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord
|
4
|
+
# The `have_rich_text` matcher tests usage of the
|
5
|
+
# `has_rich_text` macro.
|
6
|
+
#
|
7
|
+
# #### Example
|
8
|
+
#
|
9
|
+
# class Post < ActiveRecord
|
10
|
+
# has_rich_text :content
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # RSpec
|
14
|
+
# RSpec.describe Post, type: :model do
|
15
|
+
# it { is_expected.to have_rich_text(:content) }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # Minitest (Shoulda)
|
19
|
+
# class PostTest < ActiveSupport::TestCase
|
20
|
+
# should have_rich_text(:content)
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @return [HaveRichText]
|
24
|
+
#
|
25
|
+
def have_rich_text(rich_text_attribute)
|
26
|
+
HaveRichText.new(rich_text_attribute)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
class HaveRichText
|
31
|
+
def initialize(rich_text_attribute)
|
32
|
+
@rich_text_attribute = rich_text_attribute
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
"have configured :#{rich_text_attribute} as a ActionText::RichText association"
|
37
|
+
end
|
38
|
+
|
39
|
+
def failure_message
|
40
|
+
"Expected #{subject.class} to #{error_description}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def failure_message_when_negated
|
44
|
+
"Did not expect #{subject.class} to have ActionText::RichText :#{rich_text_attribute}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def matches?(subject)
|
48
|
+
@subject = subject
|
49
|
+
@error = run_checks
|
50
|
+
@error.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :error, :rich_text_attribute, :subject
|
56
|
+
|
57
|
+
def run_checks
|
58
|
+
if !has_attribute?
|
59
|
+
":#{rich_text_attribute} does not exist"
|
60
|
+
elsif !has_expected_action_text?
|
61
|
+
:default
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_attribute?
|
66
|
+
@subject.respond_to?(rich_text_attribute.to_s)
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_expected_action_text?
|
70
|
+
@subject.send(rich_text_attribute).class.name == 'ActionText::RichText'
|
71
|
+
end
|
72
|
+
|
73
|
+
def error_description
|
74
|
+
error == :default ? description : "#{description} but #{error}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -4,12 +4,7 @@ module Shoulda
|
|
4
4
|
# The `have_secure_token` matcher tests usage of the
|
5
5
|
# `has_secure_token` macro.
|
6
6
|
#
|
7
|
-
# #### Example
|
8
|
-
#
|
9
7
|
# class User < ActiveRecord
|
10
|
-
# attr_accessor :token
|
11
|
-
# attr_accessor :auth_token
|
12
|
-
#
|
13
8
|
# has_secure_token
|
14
9
|
# has_secure_token :auth_token
|
15
10
|
# end
|
@@ -26,14 +21,32 @@ module Shoulda
|
|
26
21
|
# should have_secure_token(:auth_token)
|
27
22
|
# end
|
28
23
|
#
|
24
|
+
# #### Qualifiers
|
25
|
+
#
|
26
|
+
# ##### ignoring_check_for_db_index
|
27
|
+
#
|
28
|
+
# By default, this matcher tests that an index is defined on your token
|
29
|
+
# column. Use `ignoring_check_for_db_index` if this is not the case.
|
30
|
+
#
|
31
|
+
# class User < ActiveRecord
|
32
|
+
# has_secure_token :auth_token
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# # RSpec
|
36
|
+
# RSpec.describe User, type: :model do
|
37
|
+
# it { should have_secure_token(:auth_token).ignoring_check_for_db_index }
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# # Minitest (Shoulda)
|
41
|
+
# class UserTest < ActiveSupport::TestCase
|
42
|
+
# should have_secure_token(:auth_token).ignoring_check_for_db_index
|
43
|
+
# end
|
44
|
+
#
|
29
45
|
# @return [HaveSecureToken]
|
30
46
|
#
|
31
|
-
|
32
|
-
# rubocop:disable Style/PredicateName
|
33
47
|
def have_secure_token(token_attribute = :token)
|
34
48
|
HaveSecureTokenMatcher.new(token_attribute)
|
35
49
|
end
|
36
|
-
# rubocop:enable Style/PredicateName
|
37
50
|
|
38
51
|
# @private
|
39
52
|
class HaveSecureTokenMatcher
|
@@ -41,6 +54,7 @@ module Shoulda
|
|
41
54
|
|
42
55
|
def initialize(token_attribute)
|
43
56
|
@token_attribute = token_attribute
|
57
|
+
@options = { ignore_check_for_db_index: false }
|
44
58
|
end
|
45
59
|
|
46
60
|
def description
|
@@ -65,6 +79,11 @@ module Shoulda
|
|
65
79
|
@errors.empty?
|
66
80
|
end
|
67
81
|
|
82
|
+
def ignoring_check_for_db_index
|
83
|
+
@options[:ignore_check_for_db_index] = true
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
68
87
|
private
|
69
88
|
|
70
89
|
def run_checks
|
@@ -75,7 +94,7 @@ module Shoulda
|
|
75
94
|
if !has_expected_db_column?
|
76
95
|
@errors << "missing correct column #{token_attribute}:string"
|
77
96
|
end
|
78
|
-
if !has_expected_db_index?
|
97
|
+
if !@options[:ignore_check_for_db_index] && !has_expected_db_index?
|
79
98
|
@errors << "missing unique index for #{table_and_column}"
|
80
99
|
end
|
81
100
|
@errors
|
@@ -183,15 +183,11 @@ module Shoulda
|
|
183
183
|
end
|
184
184
|
|
185
185
|
def attribute_is_serialized?
|
186
|
-
|
186
|
+
!!serialization_coder
|
187
187
|
end
|
188
188
|
|
189
189
|
def serialization_coder
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
def serialized_attributes
|
194
|
-
Shoulda::Matchers::RailsShim.serialized_attributes_for(model)
|
190
|
+
RailsShim.attribute_serialization_coder_for(model, @name)
|
195
191
|
end
|
196
192
|
|
197
193
|
def model
|
@@ -396,7 +396,7 @@ module Shoulda
|
|
396
396
|
end
|
397
397
|
|
398
398
|
def validations
|
399
|
-
model.
|
399
|
+
model.validators_on(@attribute).select do |validator|
|
400
400
|
validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
|
401
401
|
end
|
402
402
|
end
|
@@ -820,7 +820,7 @@ module Shoulda
|
|
820
820
|
elsif previous_value.respond_to?(:next)
|
821
821
|
previous_value.next
|
822
822
|
elsif previous_value.respond_to?(:to_datetime)
|
823
|
-
previous_value.to_datetime.next
|
823
|
+
previous_value.to_datetime.in(60).next
|
824
824
|
elsif boolean_value?(previous_value)
|
825
825
|
!previous_value
|
826
826
|
else
|
@@ -3,10 +3,6 @@ module Shoulda
|
|
3
3
|
# @private
|
4
4
|
module RailsShim
|
5
5
|
class << self
|
6
|
-
def action_pack_gte_4_1?
|
7
|
-
Gem::Requirement.new('>= 4.1').satisfied_by?(action_pack_version)
|
8
|
-
end
|
9
|
-
|
10
6
|
def action_pack_gte_5?
|
11
7
|
Gem::Requirement.new('>= 5').satisfied_by?(action_pack_version)
|
12
8
|
end
|
@@ -25,6 +21,10 @@ module Shoulda
|
|
25
21
|
Gem::Requirement.new('>= 5').satisfied_by?(active_record_version)
|
26
22
|
end
|
27
23
|
|
24
|
+
def active_record_gte_6?
|
25
|
+
Gem::Requirement.new('>= 6').satisfied_by?(active_record_version)
|
26
|
+
end
|
27
|
+
|
28
28
|
def active_record_version
|
29
29
|
Gem::Version.new(::ActiveRecord::VERSION::STRING)
|
30
30
|
rescue NameError
|
@@ -69,30 +69,20 @@ module Shoulda
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def serialized_attributes_for(model)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
serialized_columns.inject({}) do |hash, column|
|
81
|
-
hash[column.name.to_s] = model.type_for_attribute(column.name).coder
|
82
|
-
hash
|
72
|
+
attribute_types_for(model).
|
73
|
+
inject({}) do |hash, (attribute_name, attribute_type)|
|
74
|
+
if attribute_type.is_a?(::ActiveRecord::Type::Serialized)
|
75
|
+
hash.merge(attribute_name => attribute_type.coder)
|
76
|
+
else
|
77
|
+
hash
|
78
|
+
end
|
83
79
|
end
|
84
|
-
|
85
|
-
|
86
|
-
end
|
80
|
+
rescue NotImplementedError
|
81
|
+
{}
|
87
82
|
end
|
88
83
|
|
89
|
-
def
|
90
|
-
|
91
|
-
# Rails 4.2
|
92
|
-
model.column_defaults[column.name]
|
93
|
-
else
|
94
|
-
column.default
|
95
|
-
end
|
84
|
+
def attribute_serialization_coder_for(model, attribute_name)
|
85
|
+
serialized_attributes_for(model)[attribute_name.to_s]
|
96
86
|
end
|
97
87
|
|
98
88
|
def tables_and_views(connection)
|
@@ -104,11 +94,7 @@ module Shoulda
|
|
104
94
|
end
|
105
95
|
|
106
96
|
def verb_for_update
|
107
|
-
|
108
|
-
:patch
|
109
|
-
else
|
110
|
-
:put
|
111
|
-
end
|
97
|
+
:patch
|
112
98
|
end
|
113
99
|
|
114
100
|
def validation_message_key_for_association_required_option
|
@@ -156,14 +142,35 @@ module Shoulda
|
|
156
142
|
nil
|
157
143
|
end
|
158
144
|
|
145
|
+
def attribute_types_for(model)
|
146
|
+
if model.respond_to?(:attribute_types)
|
147
|
+
model.attribute_types
|
148
|
+
elsif model.respond_to?(:type_for_attribute)
|
149
|
+
model.columns.inject({}) do |hash, column|
|
150
|
+
key = column.name.to_s
|
151
|
+
value = model.type_for_attribute(column.name)
|
152
|
+
hash.merge(key => value)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
raise NotImplementedError
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
159
|
def attribute_type_for(model, attribute_name)
|
160
|
-
|
161
|
-
|
160
|
+
attribute_types_for(model)[attribute_name.to_s]
|
161
|
+
rescue NotImplementedError
|
162
|
+
if model.respond_to?(:type_for_attribute)
|
163
|
+
model.type_for_attribute(attribute_name)
|
162
164
|
else
|
163
|
-
|
165
|
+
FakeAttributeType.new(model, attribute_name)
|
164
166
|
end
|
165
167
|
end
|
166
168
|
|
169
|
+
def supports_full_attributes_api?(model)
|
170
|
+
defined?(::ActiveModel::Attributes) &&
|
171
|
+
model.respond_to?(:attribute_types)
|
172
|
+
end
|
173
|
+
|
167
174
|
private
|
168
175
|
|
169
176
|
def simply_generate_validation_message(
|
@@ -188,23 +195,14 @@ module Shoulda
|
|
188
195
|
I18n.translate(primary_translation_key, translate_options)
|
189
196
|
end
|
190
197
|
|
191
|
-
|
192
|
-
defined?(::ActiveModel::Attributes) &&
|
193
|
-
model.respond_to?(:attribute_types)
|
194
|
-
end
|
195
|
-
|
196
|
-
class LegacyAttributeType
|
198
|
+
class FakeAttributeType
|
197
199
|
def initialize(model, attribute_name)
|
198
200
|
@model = model
|
199
201
|
@attribute_name = attribute_name
|
200
202
|
end
|
201
203
|
|
202
204
|
def coder
|
203
|
-
|
204
|
-
ActiveSupport::Deprecation.silence do
|
205
|
-
model.serialized_attributes[attribute_name.to_s]
|
206
|
-
end
|
207
|
-
end
|
205
|
+
nil
|
208
206
|
end
|
209
207
|
|
210
208
|
private
|