shoulda-matchers 5.3.0 → 6.1.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +26 -9
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  6. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +10 -1
  7. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  8. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  9. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +1 -1
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +16 -6
  11. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +0 -6
  12. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  13. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +3 -3
  14. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +4 -3
  15. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
  16. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +40 -96
  17. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  18. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  19. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  20. data/lib/shoulda/matchers/active_model.rb +2 -1
  21. data/lib/shoulda/matchers/active_record/association_matcher.rb +31 -11
  22. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  23. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  24. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  25. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  26. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  27. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  28. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  29. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  30. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  31. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  32. data/lib/shoulda/matchers/active_record.rb +2 -0
  33. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  34. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  35. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  36. data/lib/shoulda/matchers/rails_shim.rb +8 -6
  37. data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
  38. data/lib/shoulda/matchers/util.rb +17 -19
  39. data/lib/shoulda/matchers/version.rb +1 -1
  40. data/lib/shoulda/matchers.rb +2 -2
  41. data/shoulda-matchers.gemspec +1 -1
  42. metadata +11 -8
  43. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d083d22f49f99bf60d6c6bdfed7ea08ea1f838a90b4aed439d84108ebc814af9
4
- data.tar.gz: 90cc8440d8c63da1c338306b3d9f129cc9af8dc1a80277d9e7f5b233f2bbe115
3
+ metadata.gz: 500d5928f097ad1ca9d9c1ed3a0e0c925f504549c1594b57e93efc92316df558
4
+ data.tar.gz: 8a94b930a96fe1f2e3a78c916ca4fa6b68c21ab61ae9baccb109ec51e9a2961c
5
5
  SHA512:
6
- metadata.gz: '09b1dcf7e19179b50992d34cf73a86e72727cf27a1db911f2adb98fe291dc4d3d711da962d3efdbec60d2b1f280dcecd441e725a7e6a701a152ff60fdd8e340c'
7
- data.tar.gz: 8ba54626246e133cfd86b84aba3d3b05487166d6359b2ba97256af242a057b5ff6cd40d5eae35bd35f619c9a9b0f39cc2c8b6508e299b44e2605e3cdf4f64078
6
+ metadata.gz: 43d89cf2b6684f31f6fee6611fe694732446d50aee9fea5b8a5fefc79cd5ec4dc08337cb098e531cbfe4eac3ac46ed6cda46c6207b2f35956cf9b07d2925af1a
7
+ data.tar.gz: 586d777927cdafba0aa92ecc6c437b562a24a44aa343a8be309ee7563a3a2b98e4426c8804f1c40ad46a1987ff7f160e97473fcd178f3fdb57c2c471d210044b
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2006-2022 Tammer Saleh and thoughtbot, inc.
1
+ Copyright (c) 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
@@ -2,7 +2,7 @@
2
2
 
3
3
  [version-badge]: https://img.shields.io/gem/v/shoulda-matchers.svg
4
4
  [rubygems]: https://rubygems.org/gems/shoulda-matchers
5
- [github-actions-badge]: https://img.shields.io/github/workflow/status/thoughtbot/shoulda-matchers/Test
5
+ [github-actions-badge]: https://img.shields.io/github/actions/workflow/status/thoughtbot/shoulda-matchers/ci.yml?branch=main
6
6
  [github-actions]: https://github.com/thoughtbot/shoulda-matchers/actions
7
7
  [downloads-total]: https://img.shields.io/gem/dt/shoulda-matchers.svg
8
8
  [downloads-badge]: https://img.shields.io/gem/dtv/shoulda-matchers.svg
@@ -55,7 +55,7 @@ Start by including `shoulda-matchers` in your Gemfile:
55
55
 
56
56
  ```ruby
57
57
  group :test do
58
- gem 'shoulda-matchers', '~> 5.0'
58
+ gem 'shoulda-matchers', '~> 6.0'
59
59
  end
60
60
  ```
61
61
 
@@ -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', '~> 5.0'
120
+ gem 'shoulda-matchers', '~> 6.0'
121
121
  end
122
122
  ```
123
123
 
@@ -374,6 +374,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
374
374
  tests usage of `validates_numericality_of`.
375
375
  * **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
376
376
  tests usage of `validates_presence_of`.
377
+ * **[validate_comparison_of](lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb)**
378
+ tests usage of `validates_comparison_of`.
377
379
 
378
380
  ### ActiveRecord matchers
379
381
 
@@ -407,6 +409,10 @@ about any of them, make sure to [consult the documentation][rubydocs]!
407
409
  usage of the `serialize` macro.
408
410
  * **[validate_uniqueness_of](lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb)**
409
411
  tests usage of `validates_uniqueness_of`.
412
+ * **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
413
+ usage of the `normalize` macro
414
+ * **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
415
+ tests usage of the `encrypts` macro.
410
416
 
411
417
  ### ActionController matchers
412
418
 
@@ -468,8 +474,8 @@ machine, understanding the codebase, and creating a good pull request.
468
474
 
469
475
  ## Compatibility
470
476
 
471
- Shoulda Matchers is tested and supported against Ruby 2.6+, Rails
472
- 5.2+, RSpec 3.x, and Minitest 5.x.
477
+ Shoulda Matchers is tested and supported against Ruby 3.0+, Rails
478
+ 6.1+, RSpec 3.x, and Minitest 5.x.
473
479
 
474
480
  - For Ruby < 2.4 and Rails < 4.1 compatibility, please use [v3.1.3][v3.1.3].
475
481
  - For Ruby < 3.0 and Rails < 6.1 compatibility, please use [v4.5.1][v4.5.1].
@@ -484,15 +490,26 @@ Shoulda Matchers follows Semantic Versioning 2.0 as defined at
484
490
 
485
491
  ## Team
486
492
 
487
- Shoulda Matchers is maintained by [Elliot Winkler][mcmire] and [Gui
488
- Albuk][guialbuk].
493
+ Shoulda Matchers is currently maintained by [Pedro Paiva][VSPPedro] and [Matheus
494
+ Sales][matsales28]. Previous maintainers include [Elliot Winkler][mcmire],
495
+ [Gui Albuk][guialbuk], [Jason Draper][drapergeek], [Melissa Xie][mxie],
496
+ [Gabe Berke-Williams][gabebw], [Ryan McGeary][rmm5t], [Joe Ferris][jferris], and
497
+ [Tammer Saleh][tammersaleh].
489
498
 
499
+ [VSPPedro]: https://github.com/VSPPedro
500
+ [matsales28]: https://github.com/matsales28
490
501
  [mcmire]: https://github.com/mcmire
491
502
  [guialbuk]: https://github.com/guialbuk
503
+ [drapergeek]: https://github.com/drapergeek
504
+ [mxie]: https://github.com/mxie
505
+ [gabebw]: https://github.com/gabebw
506
+ [rmm5t]: https://github.com/rmm5t
507
+ [jferris]: https://github.com/jferris
508
+ [tammersaleh]: https://github.com/tammersaleh
492
509
 
493
510
  ## Copyright/License
494
511
 
495
- Shoulda Matchers is copyright © 2006-2022 Tammer Saleh and [thoughtbot,
512
+ Shoulda Matchers is copyright © Tammer Saleh and [thoughtbot,
496
513
  inc][thoughtbot-website]. It is free and opensource software and may be
497
514
  redistributed under the terms specified in the [LICENSE](LICENSE) file.
498
515
 
@@ -502,7 +519,7 @@ redistributed under the terms specified in the [LICENSE](LICENSE) file.
502
519
 
503
520
  ![thoughtbot][thoughtbot-logo]
504
521
 
505
- [thoughtbot-logo]: https://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg
522
+ [thoughtbot-logo]: https://thoughtbot.com/brand_assets/93:44.svg
506
523
 
507
524
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
508
525
 
@@ -11,7 +11,7 @@ module Shoulda
11
11
  module Matchers
12
12
  module ActionController
13
13
  # The `permit` matcher tests that an action in your controller receives a
14
- # whitelist of parameters using Rails' Strong Parameters feature
14
+ # allowlist of parameters using Rails' Strong Parameters feature
15
15
  # (specifically that `permit` was called with the correct arguments).
16
16
  #
17
17
  # Here's an example:
@@ -276,16 +276,14 @@ module Shoulda
276
276
  :context, :subparameter_name, :parameters_double_registry
277
277
 
278
278
  def expectation
279
- message = 'restrict parameters '
279
+ String.new('restrict parameters ').tap do |message|
280
+ if subparameter_name
281
+ message << "on #{subparameter_name.inspect} "
282
+ end
280
283
 
281
- if subparameter_name
282
- message << "on #{subparameter_name.inspect} "
284
+ message << 'to '\
285
+ "#{format_parameter_names(expected_permitted_parameter_names)}"
283
286
  end
284
-
285
- message << 'to '\
286
- "#{format_parameter_names(expected_permitted_parameter_names)}"
287
-
288
- message
289
287
  end
290
288
 
291
289
  def reality
@@ -83,25 +83,23 @@ module Shoulda
83
83
  end
84
84
 
85
85
  def expectation_description
86
- string = 'set'
87
-
88
- string <<
89
- if key_set?
90
- " #{store.name}[#{key.inspect}]"
91
- else
92
- " any key in #{store.name}"
93
- end
94
-
95
- if expected_value_set?
86
+ String.new('set').tap do |string|
96
87
  string <<
97
- if expected_value.is_a?(Regexp)
98
- " to a value matching #{expected_value.inspect}"
88
+ if key_set?
89
+ " #{store.name}[#{key.inspect}]"
99
90
  else
100
- " to #{expected_value.inspect}"
91
+ " any key in #{store.name}"
101
92
  end
102
- end
103
93
 
104
- string
94
+ if expected_value_set?
95
+ string <<
96
+ if expected_value.is_a?(Regexp)
97
+ " to a value matching #{expected_value.inspect}"
98
+ else
99
+ " to #{expected_value.inspect}"
100
+ end
101
+ end
102
+ end
105
103
  end
106
104
  end
107
105
  end
@@ -402,6 +402,10 @@ module Shoulda
402
402
  @result.nil?
403
403
  end
404
404
 
405
+ def has_any_errors?
406
+ validator.record.errors.any?
407
+ end
408
+
405
409
  def failure_message
406
410
  attribute_setter = result.attribute_setter
407
411
 
@@ -480,6 +484,11 @@ module Shoulda
480
484
  message << " it produced these validation errors instead:\n\n"
481
485
  message << validator.all_formatted_validation_error_messages
482
486
  end
487
+ elsif validator.has_any_errors?
488
+ message << ", placing a validation error on :#{attribute_setter.attribute_name}"
489
+ message << '. The Example was invalid,'
490
+ message << " but it had errors involving other attributes:\n\n"
491
+ message << validator.all_formatted_validation_error_messages
483
492
  else
484
493
  message << ', but it was valid instead.'
485
494
  end
@@ -541,7 +550,7 @@ module Shoulda
541
550
  end
542
551
 
543
552
  def default_failure_message_preface
544
- ''.tap do |preface|
553
+ String.new.tap do |preface|
545
554
  if descriptions_for_preset_values.any?
546
555
  preface << 'After setting '
547
556
  preface << descriptions_for_preset_values.to_sentence
@@ -0,0 +1,157 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveModel
6
+ # @private
7
+ class ComparisonMatcher < ValidationMatcher
8
+ ERROR_MESSAGES = {
9
+ :> => {
10
+ label: :greater_than,
11
+ assertions: [false, false, true],
12
+ },
13
+ :>= => {
14
+ label: :greater_than_or_equal_to,
15
+ assertions: [false, true, true],
16
+ },
17
+ :< => {
18
+ label: :less_than,
19
+ assertions: [true, false, false],
20
+ },
21
+ :<= => {
22
+ label: :less_than_or_equal_to,
23
+ assertions: [true, true, false],
24
+ },
25
+ :== => {
26
+ label: :equal_to,
27
+ assertions: [false, true, false],
28
+ },
29
+ :!= => {
30
+ label: :other_than,
31
+ assertions: [true, false, true],
32
+ },
33
+ }.freeze
34
+
35
+ delegate :failure_message, :failure_message_when_negated, to: :comparison_submatchers
36
+
37
+ def initialize(matcher, value, operator)
38
+ super(nil)
39
+ unless matcher.respond_to? :diff_to_compare
40
+ raise ArgumentError, 'matcher is invalid'
41
+ end
42
+
43
+ @matcher = matcher
44
+ @value = value
45
+ @operator = operator
46
+ @message = ERROR_MESSAGES[operator][:label]
47
+ end
48
+
49
+ def simple_description
50
+ description = ''
51
+
52
+ if expects_strict?
53
+ description = ' strictly'
54
+ end
55
+
56
+ description +
57
+ "disallow :#{attribute} from being a number that is not " +
58
+ "#{comparison_expectation} #{@value}"
59
+ end
60
+
61
+ def for(attribute)
62
+ @attribute = attribute
63
+ self
64
+ end
65
+
66
+ def with_message(message)
67
+ @expects_custom_validation_message = true
68
+ @message = message
69
+ self
70
+ end
71
+
72
+ def expects_custom_validation_message?
73
+ @expects_custom_validation_message
74
+ end
75
+
76
+ def matches?(subject)
77
+ @subject = subject
78
+ comparison_submatchers.matches?(subject)
79
+ end
80
+
81
+ def comparison_description
82
+ "#{comparison_expectation} #{@value}"
83
+ end
84
+
85
+ def comparison_submatchers
86
+ @_comparison_submatchers ||=
87
+ NumericalityMatchers::Submatchers.new(build_comparison_submatchers)
88
+ end
89
+
90
+ private
91
+
92
+ def build_comparison_submatchers
93
+ comparison_combos.map do |diff, submatcher_method_name|
94
+ matcher = __send__(submatcher_method_name, diff, nil)
95
+ matcher.with_message(@message, values: { count: option_value })
96
+ matcher
97
+ end
98
+ end
99
+
100
+ def comparison_combos
101
+ diffs_to_compare.zip(submatcher_method_names)
102
+ end
103
+
104
+ def submatcher_method_names
105
+ assertions.map do |value|
106
+ if value
107
+ :allow_value_matcher
108
+ else
109
+ :disallow_value_matcher
110
+ end
111
+ end
112
+ end
113
+
114
+ def assertions
115
+ ERROR_MESSAGES[@operator][:assertions]
116
+ end
117
+
118
+ def option_value
119
+ if defined?(@_option_value)
120
+ @_option_value
121
+ else
122
+ @_option_value =
123
+ case @value
124
+ when Proc then @value.call(@subject)
125
+ when Symbol then @subject.send(@value)
126
+ else @value
127
+ end
128
+ end
129
+ end
130
+
131
+ def diffs_to_compare
132
+ diff_to_compare = @matcher.diff_to_compare
133
+ values = case option_value
134
+ when String then diffs_when_string(diff_to_compare)
135
+ else [-1, 0, 1].map { |sign| option_value + (diff_to_compare * sign) }
136
+ end
137
+
138
+ if @matcher.given_numeric_column?
139
+ values
140
+ else
141
+ values.map(&:to_s)
142
+ end
143
+ end
144
+
145
+ def diffs_when_string(diff_to_compare)
146
+ [-1, 0, 1].map do |sign|
147
+ option_value[0..-2] + (option_value[-1].ord + diff_to_compare * sign).chr
148
+ end
149
+ end
150
+
151
+ def comparison_expectation
152
+ ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -47,6 +47,8 @@ module Shoulda
47
47
  did_not_authenticate_correct_password: 'expected %{subject} to'\
48
48
  ' authenticate the correct %{attribute}',
49
49
  method_not_found: 'expected %{subject} to respond to %{methods}',
50
+ should_not_have_secure_password: 'expected %{subject} to'\
51
+ ' not %{description}!',
50
52
  }.freeze
51
53
 
52
54
  def initialize(attribute)
@@ -69,6 +71,11 @@ module Shoulda
69
71
  failure.nil?
70
72
  end
71
73
 
74
+ def failure_message_when_negated
75
+ MESSAGES[:should_not_have_secure_password] %
76
+ { subject: @subject.class, description: description }
77
+ end
78
+
72
79
  protected
73
80
 
74
81
  attr_reader :subject
@@ -59,7 +59,7 @@ module Shoulda
59
59
  end
60
60
 
61
61
  def build_comparison_submatcher(value, operator)
62
- NumericalityMatchers::ComparisonMatcher.new(@numericality_matcher, value, operator).
62
+ ComparisonMatcher.new(@numericality_matcher, value, operator).
63
63
  for(@attribute).
64
64
  with_message(@message).
65
65
  on(@context)
@@ -14,21 +14,17 @@ module Shoulda
14
14
  end
15
15
 
16
16
  def failure_message
17
- last_failing_submatcher.failure_message
17
+ failing_submatcher.failure_message
18
18
  end
19
19
 
20
20
  def failure_message_when_negated
21
- last_failing_submatcher.failure_message_when_negated
21
+ non_failing_submatcher.failure_message_when_negated
22
22
  end
23
23
 
24
24
  def add(submatcher)
25
25
  @submatchers << submatcher
26
26
  end
27
27
 
28
- def last_failing_submatcher
29
- failing_submatchers.last
30
- end
31
-
32
28
  private
33
29
 
34
30
  def failing_submatchers
@@ -36,6 +32,20 @@ module Shoulda
36
32
  submatcher.matches?(@subject)
37
33
  end
38
34
  end
35
+
36
+ def non_failing_submatchers
37
+ @_non_failing_submatchers ||= @submatchers.reject do |submatcher|
38
+ submatcher.does_not_match?(@subject)
39
+ end
40
+ end
41
+
42
+ def failing_submatcher
43
+ failing_submatchers.last
44
+ end
45
+
46
+ def non_failing_submatcher
47
+ non_failing_submatchers.last
48
+ end
39
49
  end
40
50
  end
41
51
  end
@@ -143,12 +143,6 @@ module Shoulda
143
143
  @subject.class.reflect_on_association(@attribute)
144
144
  end
145
145
 
146
- def array_column?
147
- @subject.class.respond_to?(:columns_hash) &&
148
- @subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
149
- @subject.class.columns_hash[@attribute.to_s].array
150
- end
151
-
152
146
  def enum_column?
153
147
  @subject.class.respond_to?(:defined_enums) &&
154
148
  @subject.class.defined_enums.key?(@attribute.to_s)