shoulda-matchers 5.1.0 → 6.4.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +41 -18
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
  6. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  7. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
  8. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  9. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
  11. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  12. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
  13. data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  14. data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
  15. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -7
  16. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  17. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
  18. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +29 -14
  19. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +65 -10
  20. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +76 -86
  21. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
  22. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  23. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  24. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  25. data/lib/shoulda/matchers/active_model.rb +4 -1
  26. data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
  27. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
  28. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
  29. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
  30. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
  31. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  32. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  33. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +338 -30
  34. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  35. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  36. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  37. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  38. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  39. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  40. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
  41. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  42. data/lib/shoulda/matchers/active_record.rb +2 -0
  43. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  44. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  45. data/lib/shoulda/matchers/doublespeak.rb +0 -1
  46. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  47. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
  48. data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
  49. data/lib/shoulda/matchers/rails_shim.rb +22 -6
  50. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  51. data/lib/shoulda/matchers/util.rb +18 -20
  52. data/lib/shoulda/matchers/version.rb +1 -1
  53. data/lib/shoulda/matchers.rb +2 -2
  54. data/shoulda-matchers.gemspec +2 -2
  55. metadata +15 -9
  56. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -157
@@ -12,15 +12,17 @@ module Shoulda
12
12
  def integrate_with(test_framework)
13
13
  test_framework.include(matchers_module, type: :controller)
14
14
 
15
- include_into(::ActionController::TestCase, matchers_module) do
16
- def subject # rubocop:disable Lint/NestedMethodDefinition
17
- @controller
15
+ tap do |instance|
16
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true) do
17
+ instance.include_into(::ActionController::TestCase, instance.matchers_module) do
18
+ def subject # rubocop:disable Lint/NestedMethodDefinition
19
+ @controller
20
+ end
21
+ end
18
22
  end
19
23
  end
20
24
  end
21
25
 
22
- private
23
-
24
26
  def matchers_module
25
27
  Shoulda::Matchers::ActionController
26
28
  end
@@ -12,11 +12,13 @@ module Shoulda
12
12
  def integrate_with(test_framework)
13
13
  test_framework.include(matchers_module, type: :routing)
14
14
 
15
- include_into(::ActionController::TestCase, matchers_module)
15
+ tap do |instance|
16
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true) do
17
+ instance.include_into(::ActionController::TestCase, instance.matchers_module)
18
+ end
19
+ end
16
20
  end
17
21
 
18
- private
19
-
20
22
  def matchers_module
21
23
  Shoulda::Matchers::Routing
22
24
  end
@@ -1,7 +1,7 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- module RailsShim # rubocop:disable Metrics/ModuleLength
4
+ module RailsShim
5
5
  class << self
6
6
  def action_pack_version
7
7
  Gem::Version.new(::ActionPack::VERSION::STRING)
@@ -9,16 +9,26 @@ 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
19
15
  Gem::Version.new('0')
20
16
  end
21
17
 
18
+ def active_model_version
19
+ Gem::Version.new(::ActiveModel::VERSION::STRING)
20
+ rescue NameError
21
+ Gem::Version.new('0')
22
+ end
23
+
24
+ def active_model_st_6_1?
25
+ Gem::Requirement.new('< 6.1').satisfied_by?(active_model_version)
26
+ end
27
+
28
+ def active_model_lt_7?
29
+ Gem::Requirement.new('< 7').satisfied_by?(active_model_version)
30
+ end
31
+
22
32
  def generate_validation_message(
23
33
  record,
24
34
  attribute,
@@ -46,9 +56,10 @@ module Shoulda
46
56
  end
47
57
 
48
58
  def serialized_attributes_for(model)
59
+ type_serialized_defined = Object.const_defined?('ActiveRecord::Type::Serialized')
49
60
  attribute_types_for(model).
50
61
  inject({}) do |hash, (attribute_name, attribute_type)|
51
- if attribute_type.is_a?(::ActiveRecord::Type::Serialized)
62
+ if type_serialized_defined && attribute_type.is_a?(::ActiveRecord::Type::Serialized)
52
63
  hash.merge(attribute_name => attribute_type.coder)
53
64
  else
54
65
  hash
@@ -132,6 +143,10 @@ module Shoulda
132
143
  model.respond_to?(:attribute_types)
133
144
  end
134
145
 
146
+ def validates_column_options?
147
+ Gem::Requirement.new('>= 7.1.0').satisfied_by?(active_record_version)
148
+ end
149
+
135
150
  private
136
151
 
137
152
  def simply_generate_validation_message(
@@ -158,6 +173,7 @@ module Shoulda
158
173
  I18n.translate(primary_translation_key, translate_options)
159
174
  end
160
175
 
176
+ # @private
161
177
  class FakeAttributeType
162
178
  def initialize(model, attribute_name)
163
179
  @model = model
@@ -41,7 +41,7 @@ module Shoulda
41
41
 
42
42
  # @private
43
43
  class Text < ::String
44
- LIST_ITEM_REGEXP = /\A((?:[a-z0-9]+(?:\)|\.)|\*) )/.freeze
44
+ LIST_ITEM_REGEXP = /\A((?:[a-z0-9]+(?:\)|\.)|\*) )/
45
45
 
46
46
  def indented?
47
47
  self =~ /\A +/
@@ -92,7 +92,7 @@ module Shoulda
92
92
  if line.list_item?
93
93
  combined_lines << line
94
94
  else
95
- combined_lines.last << (" #{line}").squeeze(' ')
95
+ combined_lines.last << " #{line}".squeeze(' ')
96
96
  end
97
97
 
98
98
  combined_lines
@@ -7,10 +7,9 @@ module Shoulda
7
7
  MAXIMUM_LENGTH_OF_VALUE_TO_DISPLAY = 500
8
8
 
9
9
  def self.deconstantize(path)
10
- if (
11
- defined?(ActiveSupport::Inflector) &&
12
- ActiveSupport::Inflector.respond_to?(:deconstantize)
13
- )
10
+ if defined?(ActiveSupport::Inflector) &&
11
+ ActiveSupport::Inflector.respond_to?(:deconstantize)
12
+
14
13
  ActiveSupport::Inflector.deconstantize(path)
15
14
  else
16
15
  path.to_s[0...(path.to_s.rindex('::') || 0)]
@@ -18,10 +17,9 @@ module Shoulda
18
17
  end
19
18
 
20
19
  def self.safe_constantize(camel_cased_word)
21
- if (
22
- defined?(ActiveSupport::Inflector) &&
23
- ActiveSupport::Inflector.respond_to?(:safe_constantize)
24
- )
20
+ if defined?(ActiveSupport::Inflector) &&
21
+ ActiveSupport::Inflector.respond_to?(:safe_constantize)
22
+
25
23
  ActiveSupport::Inflector.safe_constantize(camel_cased_word)
26
24
  else
27
25
  begin
@@ -68,21 +66,21 @@ module Shoulda
68
66
  end
69
67
 
70
68
  def self.inspect_range(range)
71
- "#{inspect_value(range.first)} to #{inspect_value(range.last)}"
69
+ "#{inspect_value(range.begin)} to #{inspect_value(range.end)}"
72
70
  end
73
71
 
74
72
  def self.inspect_hash(hash)
75
- output = '‹{'
76
-
77
- output << hash.map { |key, value|
78
- if key.is_a?(Symbol)
79
- "#{key}: #{value.inspect}"
80
- else
81
- "#{key.inspect} => #{value.inspect}"
82
- end
83
- }.join(', ')
73
+ String.new('‹{').tap do |output|
74
+ output << hash.map { |key, value|
75
+ if key.is_a?(Symbol)
76
+ "#{key}: #{value.inspect}"
77
+ else
78
+ "#{key.inspect} => #{value.inspect}"
79
+ end
80
+ }.join(', ')
84
81
 
85
- output << '}›'
82
+ output << '}›'
83
+ end
86
84
  end
87
85
 
88
86
  def self.dummy_value_for(column_type, array: false)
@@ -94,7 +92,7 @@ module Shoulda
94
92
  0
95
93
  when :date
96
94
  Date.new(2100, 1, 1)
97
- when :datetime, :timestamp
95
+ when :datetime, :timestamp, :timestamptz
98
96
  DateTime.new(2100, 1, 1)
99
97
  when :time
100
98
  Time.new(2000, 1, 1)
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '5.1.0'.freeze
4
+ VERSION = '6.4.0'.freeze
5
5
  end
6
6
  end
@@ -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
@@ -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/master/CHANGELOG.md',
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',
@@ -36,6 +36,6 @@ Gem::Specification.new do |s|
36
36
  'shoulda-matchers.gemspec']
37
37
  s.require_paths = ['lib']
38
38
 
39
- s.required_ruby_version = '>= 2.6.0'
39
+ s.required_ruby_version = '>= 3.0.5'
40
40
  s.add_dependency('activesupport', '>= 5.2.0')
41
41
  end
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.1.0
4
+ version: 6.4.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: 2021-12-22 00:00:00.000000000 Z
17
+ date: 2024-08-16 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport
@@ -69,22 +69,26 @@ 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
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
86
89
  - lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb
87
90
  - lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb
91
+ - lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb
88
92
  - lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb
89
93
  - lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb
90
94
  - lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb
@@ -112,6 +116,7 @@ files:
112
116
  - lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb
113
117
  - lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb
114
118
  - lib/shoulda/matchers/active_record/define_enum_for_matcher.rb
119
+ - lib/shoulda/matchers/active_record/encrypt_matcher.rb
115
120
  - lib/shoulda/matchers/active_record/have_attached_matcher.rb
116
121
  - lib/shoulda/matchers/active_record/have_db_column_matcher.rb
117
122
  - lib/shoulda/matchers/active_record/have_db_index_matcher.rb
@@ -119,6 +124,7 @@ files:
119
124
  - lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb
120
125
  - lib/shoulda/matchers/active_record/have_rich_text_matcher.rb
121
126
  - lib/shoulda/matchers/active_record/have_secure_token_matcher.rb
127
+ - lib/shoulda/matchers/active_record/normalize_matcher.rb
122
128
  - lib/shoulda/matchers/active_record/serialize_matcher.rb
123
129
  - lib/shoulda/matchers/active_record/uniqueness.rb
124
130
  - lib/shoulda/matchers/active_record/uniqueness/model.rb
@@ -173,11 +179,11 @@ licenses:
173
179
  - MIT
174
180
  metadata:
175
181
  bug_tracker_uri: https://github.com/thoughtbot/shoulda-matchers/issues
176
- changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/master/CHANGELOG.md
182
+ changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md
177
183
  documentation_uri: https://matchers.shoulda.io/docs
178
184
  homepage_uri: https://matchers.shoulda.io
179
185
  source_code_uri: https://github.com/thoughtbot/shoulda-matchers
180
- post_install_message:
186
+ post_install_message:
181
187
  rdoc_options: []
182
188
  require_paths:
183
189
  - lib
@@ -185,15 +191,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
185
191
  requirements:
186
192
  - - ">="
187
193
  - !ruby/object:Gem::Version
188
- version: 2.6.0
194
+ version: 3.0.5
189
195
  required_rubygems_version: !ruby/object:Gem::Requirement
190
196
  requirements:
191
197
  - - ">="
192
198
  - !ruby/object:Gem::Version
193
199
  version: '0'
194
200
  requirements: []
195
- rubygems_version: 3.2.32
196
- signing_key:
201
+ rubygems_version: 3.5.3
202
+ signing_key:
197
203
  specification_version: 4
198
204
  summary: Simple one-liner tests for common Rails functionality
199
205
  test_files: []
@@ -1,157 +0,0 @@
1
- module Shoulda
2
- module Matchers
3
- module ActiveModel
4
- module NumericalityMatchers
5
- # @private
6
- class ComparisonMatcher < ValidationMatcher
7
- ERROR_MESSAGES = {
8
- :> => {
9
- label: :greater_than,
10
- assertions: [false, false, true],
11
- },
12
- :>= => {
13
- label: :greater_than_or_equal_to,
14
- assertions: [false, true, true],
15
- },
16
- :< => {
17
- label: :less_than,
18
- assertions: [true, false, false],
19
- },
20
- :<= => {
21
- label: :less_than_or_equal_to,
22
- assertions: [true, true, false],
23
- },
24
- :== => {
25
- label: :equal_to,
26
- assertions: [false, true, false],
27
- },
28
- :!= => {
29
- label: :other_than,
30
- assertions: [true, false, true],
31
- },
32
- }.freeze
33
-
34
- def initialize(numericality_matcher, value, operator)
35
- super(nil)
36
- unless numericality_matcher.respond_to? :diff_to_compare
37
- raise ArgumentError, 'numericality_matcher is invalid'
38
- end
39
-
40
- @numericality_matcher = numericality_matcher
41
- @value = value
42
- @operator = operator
43
- @message = ERROR_MESSAGES[operator][:label]
44
- end
45
-
46
- def simple_description
47
- description = ''
48
-
49
- if expects_strict?
50
- description << ' strictly'
51
- end
52
-
53
- description +
54
- "disallow :#{attribute} from being a number that is not " +
55
- "#{comparison_expectation} #{@value}"
56
- end
57
-
58
- def for(attribute)
59
- @attribute = attribute
60
- self
61
- end
62
-
63
- def with_message(message)
64
- @expects_custom_validation_message = true
65
- @message = message
66
- self
67
- end
68
-
69
- def expects_custom_validation_message?
70
- @expects_custom_validation_message
71
- end
72
-
73
- def matches?(subject)
74
- @subject = subject
75
- all_bounds_correct?
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
84
- end
85
-
86
- def comparison_description
87
- "#{comparison_expectation} #{@value}"
88
- end
89
-
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
- 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
113
- end
114
-
115
- def submatchers_and_results
116
- @_submatchers_and_results ||= submatchers.map do |matcher|
117
- { matcher: matcher, matched: matcher.matches?(@subject) }
118
- end
119
- end
120
-
121
- def comparison_combos
122
- diffs_to_compare.zip(submatcher_method_names)
123
- end
124
-
125
- def submatcher_method_names
126
- assertions.map do |value|
127
- if value
128
- :allow_value_matcher
129
- else
130
- :disallow_value_matcher
131
- end
132
- end
133
- end
134
-
135
- def assertions
136
- ERROR_MESSAGES[@operator][:assertions]
137
- end
138
-
139
- def diffs_to_compare
140
- diff_to_compare = @numericality_matcher.diff_to_compare
141
- values = [-1, 0, 1].map { |sign| @value + (diff_to_compare * sign) }
142
-
143
- if @numericality_matcher.given_numeric_column?
144
- values
145
- else
146
- values.map(&:to_s)
147
- end
148
- end
149
-
150
- def comparison_expectation
151
- ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
152
- end
153
- end
154
- end
155
- end
156
- end
157
- end