shoulda-matchers 6.2.0 → 6.4.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: 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