shoulda-matchers 5.3.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)