shoulda-matchers 4.1.1 → 4.4.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|