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 +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
|