rspec-expectations 3.12.3 → 3.13.5

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.
data/README.md CHANGED
@@ -13,7 +13,9 @@ If you want to use rspec-expectations with rspec, just install the rspec gem
13
13
  and RubyGems will also install rspec-expectations for you (along with
14
14
  rspec-core and rspec-mocks):
15
15
 
16
- gem install rspec
16
+ ```shell
17
+ gem install rspec
18
+ ```
17
19
 
18
20
  Want to run against the `main` branch? You'll need to include the dependent
19
21
  RSpec repos as well. Add the following to your `Gemfile`:
@@ -27,7 +29,9 @@ end
27
29
  If you want to use rspec-expectations with another tool, like Test::Unit,
28
30
  Minitest, or Cucumber, you can install it directly:
29
31
 
30
- gem install rspec-expectations
32
+ ```shell
33
+ gem install rspec-expectations
34
+ ```
31
35
 
32
36
  ## Contributing
33
37
 
@@ -67,8 +71,10 @@ The `describe` and `it` methods come from rspec-core. The `Order`, `LineItem`,
67
71
  expresses an expected outcome. If `order.total == Money.new(5.55, :USD)`, then
68
72
  the example passes. If not, it fails with a message like:
69
73
 
70
- expected: #<Money @value=5.55 @currency=:USD>
71
- got: #<Money @value=1.11 @currency=:USD>
74
+ ```
75
+ expected: #<Money @value=5.55 @currency=:USD>
76
+ got: #<Money @value=1.11 @currency=:USD>
77
+ ```
72
78
 
73
79
  ## Built-in matchers
74
80
 
@@ -46,11 +46,13 @@ module RSpec
46
46
  RSpec.world.source_from_file(file_path)
47
47
  end
48
48
  else
49
+ # :nocov:
49
50
  RSpec::Support.require_rspec_support 'source'
50
51
  def source
51
52
  raise TargetNotFoundError unless File.exist?(file_path)
52
53
  @source ||= RSpec::Support::Source.from_file(file_path)
53
54
  end
55
+ # :nocov:
54
56
  end
55
57
 
56
58
  def file_path
@@ -89,6 +89,7 @@ module RSpec
89
89
  ::RSpec.configuration.color_enabled?
90
90
  end
91
91
  else
92
+ # :nocov:
92
93
  # Indicates whether or not diffs should be colored.
93
94
  # Delegates to rspec-core's color option if rspec-core
94
95
  # is loaded; otherwise you can set it here.
@@ -100,8 +101,11 @@ module RSpec
100
101
  def color?
101
102
  defined?(@color) && @color
102
103
  end
104
+ # :nocov:
103
105
  end
104
106
 
107
+ # :nocov: Because this is only really _useful_ on 1.8, and hard to test elsewhere.
108
+ #
105
109
  # Adds `should` and `should_not` to the given classes
106
110
  # or modules. This can be used to ensure `should` works
107
111
  # properly on things like proxy objects (particular
@@ -114,6 +118,7 @@ module RSpec
114
118
  Expectations::Syntax.enable_should(mod)
115
119
  end
116
120
  end
121
+ # :nocov:
117
122
 
118
123
  # Sets or gets the backtrace formatter. The backtrace formatter should
119
124
  # implement `#format_backtrace(Array<String>)`. This is used
@@ -174,11 +179,18 @@ module RSpec
174
179
  # no-op, handler is something else
175
180
  end
176
181
  end
182
+
183
+ # Configures what RSpec will do about matcher use which would potentially cause
184
+ # false positives in tests. Defaults to `:warn` since this is generally the desired behavior,
185
+ # but can also be set to `:raise` or `:nothing`.
177
186
  #
178
- # Configures what RSpec will do about matcher use which will
179
- # potentially cause false positives in tests.
180
- #
181
- # @param [Symbol] behavior can be set to :warn, :raise or :nothing
187
+ # @overload on_potential_false_positives
188
+ # @return [Symbol] the behavior setting
189
+ # @overload on_potential_false_positives=(value)
190
+ # @param [Symbol] behavior can be set to `:warn`, `:raise` or `:nothing`
191
+ # @return [Symbol] the behavior setting
192
+ attr_reader :on_potential_false_positives
193
+
182
194
  def on_potential_false_positives=(behavior)
183
195
  unless FALSE_POSITIVE_BEHAVIOURS.key?(behavior)
184
196
  raise ArgumentError, "Supported values are: #{FALSE_POSITIVE_BEHAVIOURS.keys}"
@@ -189,22 +201,24 @@ module RSpec
189
201
  # Configures RSpec to check predicate matchers to `be(true)` / `be(false)` (strict),
190
202
  # or `be_truthy` / `be_falsey` (not strict).
191
203
  # Historically, the default was `false`, but `true` is recommended.
192
- def strict_predicate_matchers=(flag)
193
- raise ArgumentError, "Pass `true` or `false`" unless flag == true || flag == false
194
- @strict_predicate_matchers = flag
195
- end
196
-
204
+ #
205
+ # @overload strict_predicate_matchers
206
+ # @return [Boolean]
207
+ # @overload strict_predicate_matchers?
208
+ # @return [Boolean]
209
+ # @overload strict_predicate_matchers=(value)
210
+ # @param [Boolean] value
197
211
  attr_reader :strict_predicate_matchers
198
212
 
213
+ def strict_predicate_matchers=(value)
214
+ raise ArgumentError, "Pass `true` or `false`" unless value == true || value == false
215
+ @strict_predicate_matchers = value
216
+ end
217
+
199
218
  def strict_predicate_matchers?
200
219
  @strict_predicate_matchers
201
220
  end
202
221
 
203
- # Indicates what RSpec will do about matcher use which will
204
- # potentially cause false positives in tests, generally you want to
205
- # avoid such scenarios so this defaults to `true`.
206
- attr_reader :on_potential_false_positives
207
-
208
222
  # Indicates whether RSpec will warn about matcher use which will
209
223
  # potentially cause false positives in tests, generally you want to
210
224
  # avoid such scenarios so this defaults to `true`.
@@ -30,7 +30,7 @@ module RSpec
30
30
  "appropriate failure_message[_when_negated] method to return a string?"
31
31
  end
32
32
 
33
- message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
33
+ message = ::RSpec::Matchers::MultiMatcherDiff.from(expected, actual).message_with_diff(message, differ)
34
34
 
35
35
  RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
36
36
  end
@@ -6,6 +6,10 @@ module RSpec
6
6
 
7
7
  # @private
8
8
  class AggregatedFailure
9
+ # :nocov:
10
+ # `inspect` was apparently used by some versions early in ruby 3 while constructing
11
+ # NoMethodError, but seems to be no longer.
12
+ #
9
13
  # @private
10
14
  MESSAGE =
11
15
  'AggregatedFailure: This method caused a failure which has been ' \
@@ -15,6 +19,7 @@ module RSpec
15
19
  def inspect
16
20
  MESSAGE
17
21
  end
22
+ # :nocov:
18
23
  end
19
24
 
20
25
  AGGREGATED_FAILURE = AggregatedFailure.new
@@ -75,11 +80,13 @@ module RSpec
75
80
  # a backtrace that has a continuous common section with the raised `MultipleExpectationsNotMetError`,
76
81
  # so that rspec-core's truncation logic can work properly on it to list the backtrace
77
82
  # relative to the `aggregate_failures` block.
83
+ # :nocov:
78
84
  def assign_backtrace(failure)
79
85
  raise failure
80
86
  rescue failure.class => e
81
87
  failure.set_backtrace(e.backtrace)
82
88
  end
89
+ # :nocov:
83
90
  else
84
91
  # Using `caller` performs better (and is simpler) than `raise` on most Rubies.
85
92
  def assign_backtrace(failure)
@@ -4,11 +4,10 @@ module RSpec
4
4
  module ExpectationHelper
5
5
  def self.check_message(msg)
6
6
  unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call)
7
- ::Kernel.warn [
8
- "WARNING: ignoring the provided expectation message argument (",
9
- msg.inspect,
10
- ") since it is not a string or a proc."
11
- ].join
7
+ RSpec.warning(
8
+ "ignoring the provided expectation message argument" \
9
+ "(#{ msg.inspect }) since it is not a string or a proc"
10
+ )
12
11
  end
13
12
  end
14
13
 
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Expectations
3
3
  # @private
4
4
  module Version
5
- STRING = '3.12.3'
5
+ STRING = '3.13.5'
6
6
  end
7
7
  end
8
8
  end
@@ -124,24 +124,6 @@ module RSpec
124
124
  end
125
125
  private_class_method :underscore
126
126
 
127
- private
128
-
129
- def assert_ivars(*expected_ivars)
130
- return unless (expected_ivars - present_ivars).any?
131
- ivar_list = EnglishPhrasing.list(expected_ivars)
132
- raise "#{self.class.name} needs to supply#{ivar_list}"
133
- end
134
-
135
- if RUBY_VERSION.to_f < 1.9
136
- # :nocov:
137
- def present_ivars
138
- instance_variables.map(&:to_sym)
139
- end
140
- # :nocov:
141
- else
142
- alias present_ivars instance_variables
143
- end
144
-
145
127
  # @private
146
128
  module HashFormatting
147
129
  # `{ :a => 5, :b => 2 }.inspect` produces:
@@ -161,6 +143,51 @@ module RSpec
161
143
 
162
144
  include HashFormatting
163
145
 
146
+ # @private
147
+ module StringEncodingFormatting
148
+ # @api private
149
+ # @return [Boolean] True if the actual and expected string encoding are different.
150
+ # i.e. the failure may be related to encoding differences and the encoding
151
+ # should be shown to the user. false otherwise.
152
+ if String.method_defined?(:encoding)
153
+ def string_encoding_differs?
154
+ actual.is_a?(String) && expected.is_a?(String) && actual.encoding != expected.encoding
155
+ end
156
+ else
157
+ # @api private
158
+ # @return [Boolean] False always as the curent Ruby version does not support String encoding
159
+ # :nocov:
160
+ def string_encoding_differs?
161
+ false
162
+ end
163
+ # :nocov:
164
+ end
165
+ module_function :string_encoding_differs?
166
+
167
+ if String.method_defined?(:encoding)
168
+ # @api private
169
+ # Formats a String's encoding as a human readable string
170
+ # @param value [String]
171
+ # @return [String]
172
+ def format_encoding(value)
173
+ "#<Encoding:#{value.encoding.name}>"
174
+ end
175
+ else
176
+ # @api private
177
+ # Formats a String's encoding as a human readable string
178
+ # @param _value [String]
179
+ # @return [nil] nil as the curent Ruby version does not support String encoding
180
+ # :nocov:
181
+ def format_encoding(_value)
182
+ nil
183
+ end
184
+ # :nocov:
185
+ end
186
+ module_function :format_encoding
187
+ end
188
+
189
+ include StringEncodingFormatting
190
+
164
191
  # @api private
165
192
  # Provides default implementations of failure messages, based on the `description`.
166
193
  module DefaultFailureMessages
@@ -440,9 +440,11 @@ module RSpec
440
440
  Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name)
441
441
  end
442
442
  else
443
+ # :nocov:
443
444
  def extract_value_block_snippet
444
445
  nil
445
446
  end
447
+ # :nocov:
446
448
  end
447
449
  end
448
450
  end
@@ -51,10 +51,10 @@ module RSpec
51
51
  end
52
52
 
53
53
  # @api private
54
- # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
54
+ # @return [RSpec::Matchers::MultiMatcherDiff]
55
55
  def expected
56
56
  return nil unless evaluator
57
- ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
57
+ ::RSpec::Matchers::MultiMatcherDiff.for_many_matchers(diffable_matcher_list)
58
58
  end
59
59
 
60
60
  protected
@@ -77,8 +77,11 @@ module RSpec
77
77
  def match(_expected, actual)
78
78
  evaluator_klass = if supports_block_expectations? && Proc === actual
79
79
  NestedEvaluator
80
- else
80
+ elsif supports_value_expectations?
81
81
  SequentialEvaluator
82
+ else
83
+ # Can't raise an ArgumentError in this context, as it's rescued
84
+ raise "Block and value matchers can't be combined in a compound expectation (#{matcher_1.description}, #{matcher_2.description})"
82
85
  end
83
86
 
84
87
  @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2)
@@ -108,12 +108,14 @@ module RSpec
108
108
  end
109
109
 
110
110
  if RUBY_VERSION == "1.8.7"
111
+ # :nocov:
111
112
  def to_a_disallowed?(object)
112
113
  case object
113
114
  when NilClass, String then true
114
115
  else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
115
116
  end
116
117
  end
118
+ # :nocov:
117
119
  else
118
120
  def to_a_disallowed?(object)
119
121
  NilClass === object
@@ -61,9 +61,11 @@ module RSpec
61
61
  count.cover?(number)
62
62
  end
63
63
  else
64
+ # :nocov:
64
65
  def cover?(count, number)
65
66
  number >= count.first && number <= count.last
66
67
  end
68
+ # :nocov:
67
69
  end
68
70
 
69
71
  def expected_count_matches?(actual_count)
@@ -8,7 +8,11 @@ module RSpec
8
8
  # @api private
9
9
  # @return [String]
10
10
  def failure_message
11
- "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
11
+ if string_encoding_differs?
12
+ "\nexpected: #{format_encoding(expected)} #{expected_formatted}\n got: #{format_encoding(actual)} #{actual_formatted}\n\n(compared using ==)\n"
13
+ else
14
+ "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
15
+ end
12
16
  end
13
17
 
14
18
  # @api private
@@ -8,7 +8,11 @@ module RSpec
8
8
  # @api private
9
9
  # @return [String]
10
10
  def failure_message
11
- "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
11
+ if string_encoding_differs?
12
+ "\nexpected: #{format_encoding(expected)} #{expected_formatted}\n got: #{format_encoding(actual)} #{actual_formatted}\n\n(compared using eql?)\n"
13
+ else
14
+ "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
15
+ end
12
16
  end
13
17
 
14
18
  # @api private
@@ -46,8 +46,27 @@ module RSpec
46
46
 
47
47
  private
48
48
 
49
+ # Catch a semi-frequent typo - if you have strict_predicate_matchers disabled and
50
+ # expect(spy).to have_receieveddd(:foo) it would be evergreen - the dynamic matcher
51
+ # queries `has_receiveddd?`, the spy _fakes_ the method, returning its (truthy) self.
52
+ if defined?(RSpec::Mocks::Double)
53
+ def really_responds_to?(method)
54
+ if RSpec::Mocks::Double === @actual
55
+ @actual.respond_to?(method) && methods_include?(method)
56
+ else
57
+ @actual.respond_to?(method)
58
+ end
59
+ end
60
+ else
61
+ # :nocov:
62
+ def really_responds_to?(method)
63
+ @actual.respond_to?(method)
64
+ end
65
+ # :nocov:
66
+ end
67
+
49
68
  def predicate_accessible?
50
- @actual.respond_to? predicate
69
+ really_responds_to?(predicate)
51
70
  end
52
71
 
53
72
  # support 1.8.7, evaluate once at load time for performance
@@ -56,11 +75,19 @@ module RSpec
56
75
  def private_predicate?
57
76
  @actual.private_methods.include? predicate.to_s
58
77
  end
78
+
79
+ def methods_include?(method)
80
+ @actual.methods.include?(method.to_s)
81
+ end
59
82
  # :nocov:
60
83
  else
61
84
  def private_predicate?
62
85
  @actual.private_methods.include? predicate
63
86
  end
87
+
88
+ def methods_include?(method)
89
+ @actual.methods.include?(method)
90
+ end
64
91
  end
65
92
 
66
93
  def predicate_result
@@ -155,7 +182,7 @@ module RSpec
155
182
  end
156
183
 
157
184
  def predicate_accessible?
158
- super || actual.respond_to?(present_tense_predicate)
185
+ super || really_responds_to?(present_tense_predicate)
159
186
  end
160
187
 
161
188
  def present_tense_predicate
@@ -13,6 +13,7 @@ module RSpec
13
13
 
14
14
  # @api private
15
15
  def initialize(*expecteds)
16
+ raise(ArgumentError, 'include() is not supported, please supply an argument') if expecteds.empty?
16
17
  @expecteds = expecteds
17
18
  end
18
19
 
@@ -165,6 +166,7 @@ module RSpec
165
166
  end
166
167
 
167
168
  def actual_collection_includes?(expected_item)
169
+ return actual.scan(expected_item).size > 0 if Regexp === expected_item && String === actual
168
170
  return true if actual.include?(expected_item)
169
171
 
170
172
  # String lacks an `any?` method...
@@ -174,9 +176,11 @@ module RSpec
174
176
  end
175
177
 
176
178
  if RUBY_VERSION < '1.9'
179
+ # :nocov:
177
180
  def count_enumerable(expected_item)
178
181
  actual.select { |value| values_match?(expected_item, value) }.size
179
182
  end
183
+ # :nocov:
180
184
  else
181
185
  def count_enumerable(expected_item)
182
186
  actual.count { |value| values_match?(expected_item, value) }
@@ -197,6 +201,7 @@ module RSpec
197
201
 
198
202
  def diff_would_wrongly_highlight_matched_item?
199
203
  return false unless actual.is_a?(String) && expected.is_a?(Array)
204
+ return false if Regexp === expecteds.first
200
205
 
201
206
  lines = actual.split("\n")
202
207
  expected.any? do |str|
@@ -33,11 +33,23 @@ module RSpec
33
33
  self
34
34
  end
35
35
 
36
+ # @api private
37
+ # @return [String]
38
+ def failure_message
39
+ if Array === expected && !(actual.respond_to?(:to_a) || actual.respond_to?(:to_ary))
40
+ return "expected a collection that can be converted to an array with " \
41
+ "`#to_ary` or `#to_a`, but got #{actual_formatted}"
42
+ end
43
+
44
+ super
45
+ end
46
+
36
47
  private
37
48
 
38
49
  def match(expected, actual)
39
50
  return match_captures(expected, actual) if @expected_captures
40
51
  return true if values_match?(expected, actual)
52
+ return false if Array === expected
41
53
  return false unless can_safely_call_match?(expected, actual)
42
54
  actual.match(expected)
43
55
  end
@@ -78,9 +90,11 @@ module RSpec
78
90
  # @api private
79
91
  # Returns match data names for named captures
80
92
  # @return Array
93
+ # :nocov:
81
94
  def names
82
95
  []
83
96
  end
97
+ # :nocov:
84
98
  else
85
99
  # @api private
86
100
  # Returns match data names for named captures
@@ -13,6 +13,10 @@ module RSpec
13
13
  # argument. We can't use `nil` for that because we need to warn when `nil` is
14
14
  # passed in a different way. It's an Object, not a Module, since Module's `===`
15
15
  # does not evaluate to true when compared to itself.
16
+ #
17
+ # Note; this _is_ the default value supplied for expected_error_or_message, but
18
+ # because there are two method-calls involved, that default is actually supplied
19
+ # in the definition of the _matcher_ method, `RSpec::Matchers#raise_error`
16
20
  UndefinedValue = Object.new.freeze
17
21
 
18
22
  def initialize(expected_error_or_message, expected_message, &block)
@@ -25,7 +29,7 @@ module RSpec
25
29
  when nil, UndefinedValue
26
30
  @expected_error = Exception
27
31
  @expected_message = expected_message
28
- when String
32
+ when String, Regexp
29
33
  @expected_error = Exception
30
34
  @expected_message = expected_error_or_message
31
35
  else
@@ -50,9 +50,11 @@ module RSpec
50
50
  Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
51
51
  end
52
52
  else
53
+ # :nocov:
53
54
  def block_representation
54
55
  'block'
55
56
  end
57
+ # :nocov:
56
58
  end
57
59
  end
58
60
  end
@@ -467,11 +467,12 @@ module RSpec
467
467
  @chained_method_clauses = []
468
468
  @block_arg = block_arg
469
469
 
470
- klass = class << self
471
- # See `Macros#define_user_override` above, for an explanation.
472
- include(@user_method_defs = Module.new)
473
- self
474
- end
470
+ klass =
471
+ class << self
472
+ # See `Macros#define_user_override` above, for an explanation.
473
+ include(@user_method_defs = Module.new)
474
+ self
475
+ end
475
476
  RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *expected, &declarations)
476
477
  end
477
478
 
@@ -45,12 +45,14 @@ module RSpec
45
45
  # So here we replace `Kernel#Array` with our own warning-free implementation for 1.8.7.
46
46
  # @private
47
47
  # rubocop:disable Naming/MethodName
48
+ # :nocov:
48
49
  def self.Array(obj)
49
50
  case obj
50
51
  when Array then obj
51
52
  else [obj]
52
53
  end
53
54
  end
55
+ # :nocov:
54
56
  # rubocop:enable Naming/MethodName
55
57
  end
56
58
  end
@@ -1,8 +1,34 @@
1
1
  module RSpec
2
2
  module Matchers
3
+ # Provides a base class with as little methods as possible, so that
4
+ # most methods can be delegated via `method_missing`.
5
+ #
6
+ # On Ruby 2.0+ BasicObject could be used for this purpose, but it
7
+ # introduce some extra complexity with constant resolution, so the
8
+ # BlankSlate pattern was prefered.
9
+ # @private
10
+ class BaseDelegator
11
+ kept_methods = [
12
+ # Methods that raise warnings if removed.
13
+ :__id__, :__send__, :object_id,
14
+
15
+ # Methods that are explicitly undefined in some subclasses.
16
+ :==, :===,
17
+
18
+ # Methods we keep on purpose.
19
+ :class, :respond_to?, :__method__, :method, :dup,
20
+ :clone, :initialize_dup, :initialize_copy, :initialize_clone,
21
+ ]
22
+ instance_methods.each do |method|
23
+ unless kept_methods.include?(method.to_sym)
24
+ undef_method(method)
25
+ end
26
+ end
27
+ end
28
+
3
29
  # Provides the necessary plumbing to wrap a matcher with a decorator.
4
30
  # @private
5
- class MatcherDelegator
31
+ class MatcherDelegator < BaseDelegator
6
32
  include Composable
7
33
  attr_reader :base_matcher
8
34