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 +4 -4
- data/README.md +2 -3
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +229 -7
- data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
- data/lib/shoulda/matchers/doublespeak.rb +0 -1
- data/lib/shoulda/matchers/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a3a788cc37a73b615004a4e88dacb051413e00f9a752b78f00cfa8b529e4149
|
4
|
+
data.tar.gz: 99e5319049435d5ae107d068cecacd547bbd5135e1f1966bf8e15c40e5cde84f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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](#
|
170
|
+
* [database models backed by ActiveRecord](#activerecord-matchers)
|
171
171
|
* [non-database models, form objects, etc. backed by
|
172
|
-
ActiveModel](#
|
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
|
@@ -19,10 +19,7 @@ module Shoulda
|
|
19
19
|
def matches?(subject)
|
20
20
|
self.subject = ModelReflector.new(subject, name)
|
21
21
|
|
22
|
-
if
|
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
|
-
#
|
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
|
417
|
-
true
|
418
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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-
|
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.
|
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
|