shoulda-matchers 5.0.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 319b0cb3944a37309b11169bd71ffddde2d6af35b262795e7c16466d6d2c497c
4
- data.tar.gz: 22ac49d578766e24a18826bcde86443a09afa41c1cfc9ee7932f94f214623a36
3
+ metadata.gz: 97aebe50b9fb389687c99bc5dc1229a27abc5255f15e2330c9d1409d957fb689
4
+ data.tar.gz: 7cd8151aae87feb29121f4b4c152f963e5cc1c7a136e0a2115344af225fb1dcd
5
5
  SHA512:
6
- metadata.gz: 05a067ef5aa288661855f6257a55ce887bdf19f6684cd965c145669815a3d432a236cd7fb88e21647d0251e4e971b6c45b03af04def19b5cefbef26279276d81
7
- data.tar.gz: f06e9c331d39d82fd450332cb0b81e108852c7fac2a28927ada2fbccc11b31226206023f69eb6ecccd7dceaaca4c9b3410b818ed86babd3f7f37bb4c778013df
6
+ metadata.gz: 351c2bc9565abf151db5eeef3d17280989cdc8ec1841b0a099de8be8533767324494b04bfcc4464290852f5b022f6595fb5759e79c4534f50b6e464c94e44679
7
+ data.tar.gz: 529b6d0815299c10c0f52664371e37b948ec4b8403c0096065f11649fb1849851454634f736e747820f06e27fc4d48a0616d9adfbc34b560f14a8dad364e4268
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2021 Tammer Saleh and thoughtbot, inc.
1
+ Copyright (c) 2006-2022 Tammer Saleh and thoughtbot, inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
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', '~> 4.0'
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-2021 Tammer Saleh and [thoughtbot,
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
 
@@ -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
@@ -9,5 +9,6 @@ module Shoulda
9
9
  end
10
10
 
11
11
  require_relative 'qualifiers/allow_nil'
12
+ require_relative 'qualifiers/allow_blank'
12
13
  require_relative 'qualifiers/ignore_interference_by_writer'
13
14
  require_relative 'qualifiers/ignoring_interference_by_writer'
@@ -109,6 +109,8 @@ module Shoulda
109
109
  end
110
110
  elsif array_column?
111
111
  ['an arbitary value']
112
+ elsif enum_column?
113
+ enum_values.first
112
114
  else
113
115
  case column_type
114
116
  when :integer, :float then 1
@@ -145,6 +147,15 @@ module Shoulda
145
147
  @subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
146
148
  @subject.class.columns_hash[@attribute.to_s].array
147
149
  end
150
+
151
+ def enum_column?
152
+ @subject.class.respond_to?(:defined_enums) &&
153
+ @subject.class.defined_enums.key?(@attribute.to_s)
154
+ end
155
+
156
+ def enum_values
157
+ @subject.class.defined_enums[@attribute.to_s].values
158
+ end
148
159
  end
149
160
  end
150
161
  end
@@ -355,7 +355,7 @@ EOT
355
355
  description = "validate that :#{@attribute}"
356
356
 
357
357
  description <<
358
- if @array.many?
358
+ if @array.count > 1
359
359
  " is either #{inspected_array}"
360
360
  else
361
361
  " is #{inspected_array}"
@@ -239,7 +239,7 @@ module Shoulda
239
239
  #
240
240
  # @return [ValidateLengthOfMatcher]
241
241
  #
242
- # # ##### allow_blank
242
+ # ##### allow_blank
243
243
  #
244
244
  # Use `allow_blank` to assert that the attribute allows blank.
245
245
  #
@@ -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
 
@@ -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 5 only). Can take either a boolean or a symbol:
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
- passed = expected_singleton_methods.all? do |method|
391
- model.singleton_methods.include?(method)
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
- if passed
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
- message = "#{attribute_name.inspect} does map to these "
398
- message << 'values, but the enum is '
452
+ if enum_defined?
453
+ message = 'But the class scope methods are not present'
454
+ else
455
+ message = missing_methods_message
399
456
 
400
- if expected_prefix
401
- if expected_suffix
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 `:primary`).
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_gte_7?
29
+ Gem::Requirement.new('>= 7').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,
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '5.0.0'.freeze
4
+ VERSION = '5.2.0'.freeze
5
5
  end
6
6
  end
@@ -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',
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.0.0
4
+ version: 5.2.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: 2021-07-10 00:00:00.000000000 Z
17
+ date: 2022-09-17 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport
@@ -80,6 +80,7 @@ files:
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
82
  - lib/shoulda/matchers/active_model/qualifiers.rb
83
+ - lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb
83
84
  - lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb
84
85
  - lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb
85
86
  - lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb
@@ -173,7 +174,7 @@ licenses:
173
174
  - MIT
174
175
  metadata:
175
176
  bug_tracker_uri: https://github.com/thoughtbot/shoulda-matchers/issues
176
- changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/master/CHANGELOG.md
177
+ changelog_uri: https://github.com/thoughtbot/shoulda-matchers/blob/main/CHANGELOG.md
177
178
  documentation_uri: https://matchers.shoulda.io/docs
178
179
  homepage_uri: https://matchers.shoulda.io
179
180
  source_code_uri: https://github.com/thoughtbot/shoulda-matchers
@@ -192,7 +193,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
193
  - !ruby/object:Gem::Version
193
194
  version: '0'
194
195
  requirements: []
195
- rubygems_version: 3.2.15
196
+ rubygems_version: 3.2.32
196
197
  signing_key:
197
198
  specification_version: 4
198
199
  summary: Simple one-liner tests for common Rails functionality