shoulda-matchers 6.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d549ac8f3629ad37bc56d0d09daf37416ec23d78374a8ed6a630ea23e5c9e487
4
- data.tar.gz: f09bc94b6be181564cdde56729c2055192a4725adee4ba8b6b7f8e84429fe9a0
3
+ metadata.gz: 0a3a788cc37a73b615004a4e88dacb051413e00f9a752b78f00cfa8b529e4149
4
+ data.tar.gz: 99e5319049435d5ae107d068cecacd547bbd5135e1f1966bf8e15c40e5cde84f
5
5
  SHA512:
6
- metadata.gz: 1f3ba08a6d15ad56cb583c0a8289e180854f5d89020b3182b2d6140289800d71bca467f187c9a4718ba26078cc2562f6377d77b00a0e84f2e683079d62ef60d3
7
- data.tar.gz: 45d0141c79d9642439c9c03ffcd8e41d1aee993eafaea11fc72812456ead1dcf17d1698e9eb7f311c40945c54c77fc4e573a820ffa115fc344e9b0049ed69479
6
+ metadata.gz: 5f2b4708f133c2e0a790c99de4608d976d8dfe8704436adeea539e816a18bc6bca7612e7ff669bc4830f8f617550a0db7a70574f611a791dc27879d66f6ed09c
7
+ data.tar.gz: 460ac28a8c3833b30ec3171f62a5d5225712132837caeb1c620df49cdcc1fc2d92614a8a652ac93f7707b06d4fe24c5c9ee70fc9759b8b34a3929f845931a2b5
data/README.md CHANGED
@@ -167,9 +167,9 @@ end
167
167
  Most of the matchers provided by this gem are useful in a Rails context, and as
168
168
  such, can be used for different parts of a Rails app:
169
169
 
170
- * [database models backed by ActiveRecord](#activemodel-matchers)
170
+ * [database models backed by ActiveRecord](#activerecord-matchers)
171
171
  * [non-database models, form objects, etc. backed by
172
- ActiveModel](#activerecord-matchers)
172
+ ActiveModel](#activemodel-matchers)
173
173
  * [controllers](#actioncontroller-matchers)
174
174
  * [routes](#routing-matchers) (RSpec only)
175
175
  * [Rails-specific features like `delegate`](#independent-matchers)
@@ -527,7 +527,6 @@ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
527
527
 
528
528
  We love open source software!
529
529
  See [our other projects][community].
530
-
531
530
  We are [available for hire][hire].
532
531
 
533
532
  [community]: https://thoughtbot.com/community?utm_source=github
@@ -133,7 +133,7 @@ module Shoulda
133
133
  when :missing then 404
134
134
  when :error then 500..599
135
135
  when Symbol
136
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE[potential_symbol]
136
+ ::Rack::Utils.status_code(potential_symbol)
137
137
  else
138
138
  potential_symbol
139
139
  end
@@ -19,10 +19,7 @@ module Shoulda
19
19
  def matches?(subject)
20
20
  self.subject = ModelReflector.new(subject, name)
21
21
 
22
- if option_verifier.correct_for_string?(
23
- :counter_cache,
24
- counter_cache,
25
- )
22
+ if correct_value?
26
23
  true
27
24
  else
28
25
  self.missing_option = "#{name} should have #{description}"
@@ -34,9 +31,42 @@ module Shoulda
34
31
 
35
32
  attr_accessor :subject, :counter_cache, :name
36
33
 
34
+ def correct_value?
35
+ expected = normalize_value
36
+
37
+ if expected.is_a?(Hash)
38
+ option_verifier.correct_for_hash?(
39
+ :counter_cache,
40
+ expected,
41
+ )
42
+ else
43
+ option_verifier.correct_for_string?(
44
+ :counter_cache,
45
+ expected,
46
+ )
47
+ end
48
+ end
49
+
37
50
  def option_verifier
38
51
  @_option_verifier ||= OptionVerifier.new(subject)
39
52
  end
53
+
54
+ def normalize_value
55
+ if Rails::VERSION::STRING >= '7.2'
56
+ case counter_cache
57
+ when true
58
+ { active: true, column: nil }
59
+ when String, Symbol
60
+ { active: true, column: counter_cache.to_s }
61
+ when Hash
62
+ { active: true, column: nil }.merge(counter_cache)
63
+ else
64
+ raise ArgumentError, 'Invalid counter_cache option'
65
+ end
66
+ else
67
+ counter_cache
68
+ end
69
+ end
40
70
  end
41
71
  end
42
72
  end
@@ -187,8 +187,95 @@ module Shoulda
187
187
  # without_scopes
188
188
  # end
189
189
  #
190
- # @return [DefineEnumForMatcher]
190
+ # ##### with_default
191
+ #
192
+ # Use `with_default` to test that the enum is defined with a
193
+ # default value. A proc can also be passed, and will be called once each
194
+ # time a new value is needed. (If using Time or Date, it's recommended to
195
+ # freeze time or date to avoid flaky tests):
196
+ #
197
+ # class Issue < ActiveRecord::Base
198
+ # enum status: [:open, :closed], default: :closed
199
+ # end
200
+ #
201
+ # # RSpec
202
+ # RSpec.describe Issue, type: :model do
203
+ # it do
204
+ # should define_enum_for(:status).
205
+ # with_default(:closed)
206
+ # end
207
+ # end
208
+ #
209
+ # # Minitest (Shoulda)
210
+ # class ProcessTest < ActiveSupport::TestCase
211
+ # should define_enum_for(:status).
212
+ # with_default(:closed)
213
+ # end
214
+ #
215
+ # ##### validating
216
+ #
217
+ # Use `validating` to test that the enum is being validated.
218
+ # Can take a boolean value and an allowing_nil keyword argument:
219
+ #
220
+ # class Issue < ActiveRecord::Base
221
+ # enum status: [:open, :closed], validate: true
222
+ # end
223
+ #
224
+ # # RSpec
225
+ # RSpec.describe Issue, type: :model do
226
+ # it do
227
+ # should define_enum_for(:status).
228
+ # validating
229
+ # end
230
+ # end
231
+ #
232
+ # # Minitest (Shoulda)
233
+ # class ProcessTest < ActiveSupport::TestCase
234
+ # should define_enum_for(:status).
235
+ # validating
236
+ # end
237
+ #
238
+ # class Issue < ActiveRecord::Base
239
+ # enum status: [:open, :closed], validate: { allow_nil: true }
240
+ # end
241
+ #
242
+ # # RSpec
243
+ # RSpec.describe Issue, type: :model do
244
+ # it do
245
+ # should define_enum_for(:status).
246
+ # validating(allowing_nil: true)
247
+ # end
248
+ # end
249
+ #
250
+ # # Minitest (Shoulda)
251
+ # class ProcessTest < ActiveSupport::TestCase
252
+ # should define_enum_for(:status).
253
+ # validating(allowing_nil: true)
254
+ # end
255
+ #
256
+ # ##### without_instance_methods
257
+ #
258
+ # Use `without_instance_methods` to exclude the check for instance methods.
259
+ #
260
+ # class Issue < ActiveRecord::Base
261
+ # enum status: [:open, :closed], instance_methods: false
262
+ # end
263
+ #
264
+ # # RSpec
265
+ # RSpec.describe Issue, type: :model do
266
+ # it do
267
+ # should define_enum_for(:status).
268
+ # without_instance_methods
269
+ # end
270
+ # end
271
+ #
272
+ # # Minitest (Shoulda)
273
+ # class ProcessTest < ActiveSupport::TestCase
274
+ # should define_enum_for(:status).
275
+ # without_instance_methods
276
+ # end
191
277
  #
278
+ # @return [DefineEnumForMatcher]
192
279
  def define_enum_for(attribute_name)
193
280
  DefineEnumForMatcher.new(attribute_name)
194
281
  end
@@ -197,7 +284,7 @@ module Shoulda
197
284
  class DefineEnumForMatcher
198
285
  def initialize(attribute_name)
199
286
  @attribute_name = attribute_name
200
- @options = { expected_enum_values: [], scopes: true }
287
+ @options = { expected_enum_values: [], scopes: true, instance_methods: true }
201
288
  end
202
289
 
203
290
  def description
@@ -222,6 +309,12 @@ module Shoulda
222
309
  description
223
310
  end
224
311
 
312
+ def validating(value = true, allowing_nil: false)
313
+ options[:validating] = value
314
+ options[:allowing_nil] = allowing_nil
315
+ self
316
+ end
317
+
225
318
  def with_values(expected_enum_values)
226
319
  options[:expected_enum_values] = expected_enum_values
227
320
  self
@@ -247,6 +340,16 @@ module Shoulda
247
340
  self
248
341
  end
249
342
 
343
+ def without_instance_methods
344
+ options[:instance_methods] = false
345
+ self
346
+ end
347
+
348
+ def with_default(default_value)
349
+ options[:default] = default_value
350
+ self
351
+ end
352
+
250
353
  def matches?(subject)
251
354
  @record = subject
252
355
 
@@ -254,7 +357,9 @@ module Shoulda
254
357
  enum_values_match? &&
255
358
  column_type_matches? &&
256
359
  enum_value_methods_exist? &&
257
- scope_presence_matches?
360
+ scope_presence_matches? &&
361
+ default_value_matches? &&
362
+ validating_matches?
258
363
  end
259
364
 
260
365
  def failure_message
@@ -277,6 +382,30 @@ module Shoulda
277
382
 
278
383
  private
279
384
 
385
+ def validating_matches?
386
+ return true if options[:validating].nil?
387
+
388
+ validator = find_enum_validator
389
+
390
+ if expected_validating? == !!validator
391
+ if validator&.options&.dig(:allow_nil).present? == expected_allowing_nil?
392
+ true
393
+ else
394
+ @failure_message_continuation =
395
+ "However, #{attribute_name.inspect} is allowing nil values"
396
+ false
397
+ end
398
+ else
399
+ @failure_message_continuation =
400
+ if expected_validating?
401
+ "However, #{attribute_name.inspect} is not being validated"
402
+ else
403
+ "However, #{attribute_name.inspect} is being validated"
404
+ end
405
+ false
406
+ end
407
+ end
408
+
280
409
  attr_reader :attribute_name, :options, :record,
281
410
  :failure_message_continuation
282
411
 
@@ -292,6 +421,21 @@ module Shoulda
292
421
  )
293
422
  end
294
423
 
424
+ if options[:default].present?
425
+ expectation << ', with a default value of '
426
+ expectation << Shoulda::Matchers::Util.inspect_value(expected_default_value)
427
+ end
428
+
429
+ if expected_validating?
430
+ expectation << ', and being validated '
431
+ expectation <<
432
+ if expected_allowing_nil?
433
+ 'allowing nil values'
434
+ else
435
+ 'not allowing nil values'
436
+ end
437
+ end
438
+
295
439
  if expected_prefix
296
440
  expectation <<
297
441
  if expected_suffix
@@ -413,16 +557,24 @@ module Shoulda
413
557
  end
414
558
 
415
559
  def enum_value_methods_exist?
416
- if instance_methods_exist?
417
- true
418
- else
560
+ if options[:instance_methods]
561
+ return true if instance_methods_exist?
562
+
419
563
  message = missing_methods_message
564
+ message << " (we can't tell which)" if [expected_prefix, expected_suffix].any?
420
565
 
421
- message << " (we can't tell which)"
566
+ @failure_message_continuation = message
567
+
568
+ false
569
+ elsif instance_methods_exist?
570
+ message = "#{attribute_name.inspect} does map to these values"
571
+ message << ' with instance methods, but expected no instance methods'
422
572
 
423
573
  @failure_message_continuation = message
424
574
 
425
575
  false
576
+ else
577
+ true
426
578
  end
427
579
  end
428
580
 
@@ -471,11 +623,61 @@ module Shoulda
471
623
  elsif expected_suffix
472
624
  message << 'configured with either a different suffix or no '
473
625
  message << 'suffix at all'
626
+ elsif expected_instance_methods?
627
+ message << 'configured with no instance methods'
474
628
  else
475
629
  ''
476
630
  end
477
631
  end
478
632
 
633
+ def default_value_matches?
634
+ return true if options[:default].blank?
635
+
636
+ if actual_default_value.nil?
637
+ @failure_message_continuation = 'However, no default value was set'
638
+ return false
639
+ end
640
+
641
+ if actual_default_value == expected_default_value
642
+ true
643
+ else
644
+ String.new.tap do |message|
645
+ message << 'However, the default value is '
646
+ message << Shoulda::Matchers::Util.inspect_value(actual_default_value)
647
+ @failure_message_continuation = message
648
+ end
649
+ false
650
+ end
651
+ end
652
+
653
+ def expected_default_value
654
+ options[:default].respond_to?(:call) ? options[:default].call : options[:default]
655
+ end
656
+
657
+ def actual_default_value
658
+ attribute_schema = if model.respond_to?(:_default_attributes)
659
+ model._default_attributes[attribute_name.to_s]
660
+ else
661
+ model.attributes_to_define_after_schema_loads[attribute_name.to_s]
662
+ end
663
+
664
+ if Kernel.const_defined?('ActiveModel::Attribute::UserProvidedDefault') &&
665
+ attribute_schema.is_a?(::ActiveModel::Attribute::UserProvidedDefault)
666
+ attribute_schema = attribute_schema.marshal_dump
667
+ end
668
+
669
+ value = case attribute_schema
670
+ in [_, { default: default_value } ]
671
+ default_value
672
+ in [_, default_value, *]
673
+ default_value
674
+ in [_, default_value]
675
+ default_value
676
+ end
677
+
678
+ value.respond_to?(:call) ? value.call : value
679
+ end
680
+
479
681
  def singleton_methods_exist?
480
682
  expected_singleton_methods.all? do |method|
481
683
  model.singleton_methods.include?(method)
@@ -509,6 +711,10 @@ module Shoulda
509
711
  end
510
712
  end
511
713
 
714
+ def expected_instance_methods?
715
+ options[:instance_methods]
716
+ end
717
+
512
718
  def expected_prefix
513
719
  if options.include?(:prefix)
514
720
  if options[:prefix] == true
@@ -529,6 +735,22 @@ module Shoulda
529
735
  end
530
736
  end
531
737
 
738
+ def expected_validating?
739
+ options[:validating].present?
740
+ end
741
+
742
+ def expected_allowing_nil?
743
+ options[:allowing_nil].present?
744
+ end
745
+
746
+ def find_enum_validator
747
+ record.class.validators.detect do |validator|
748
+ validator.kind == :inclusion &&
749
+ validator.attributes.include?(attribute_name.to_s) &&
750
+ validator.options[:in] == expected_enum_value_names
751
+ end
752
+ end
753
+
532
754
  def exclude_scopes?
533
755
  !options[:scopes]
534
756
  end
@@ -29,7 +29,19 @@ module Shoulda
29
29
  end
30
30
 
31
31
  def symlink_to(parent)
32
- namespace.set(name, Class.new(parent))
32
+ table_name = parent.table_name
33
+
34
+ new_class = Class.new(parent) do
35
+ define_singleton_method :table_name do
36
+ table_name
37
+ end
38
+
39
+ define_singleton_method :base_class do
40
+ self
41
+ end
42
+ end
43
+
44
+ namespace.set(name, new_class)
33
45
  end
34
46
 
35
47
  def to_s
@@ -1,5 +1,4 @@
1
1
  require 'forwardable'
2
- require 'logger'
3
2
 
4
3
  module Shoulda
5
4
  module Matchers
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '6.2.0'.freeze
4
+ VERSION = '6.4.0'.freeze
5
5
  end
6
6
  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: 6.2.0
4
+ version: 6.4.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: 2024-03-15 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
@@ -198,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
198
  - !ruby/object:Gem::Version
199
199
  version: '0'
200
200
  requirements: []
201
- rubygems_version: 3.5.6
201
+ rubygems_version: 3.5.3
202
202
  signing_key:
203
203
  specification_version: 4
204
204
  summary: Simple one-liner tests for common Rails functionality