rspec-expectations 3.9.3 → 3.10.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba515bcbb264eef62d6948840e73b71ef3a68ed947d2e711df04fdf1b04fa4ec
4
- data.tar.gz: ba8501df256831e843786ca4f78cc7825e8ad6dca125191beda504de0c1d16e7
3
+ metadata.gz: a1042199b6d7c762e4a79740c4996cfb198c2a5599091f9e80c4b934021dcfdd
4
+ data.tar.gz: 4aadaf8516b655b927d4daa574851e4d2113b7c0294b83f01a751b0ee785d11e
5
5
  SHA512:
6
- metadata.gz: ac2ff1267b689ba5daf08898a11ff97f1f2554db817f43135a6b1d96bcada94f03d728ddda4050158fcf8a483b8295644457aea810d18b37686d5aa253007917
7
- data.tar.gz: 6527bed095e09131e0e8a50f12784d017953f073b4099d710df7fff638f632d03bedba40836adeba6aa33f51d9d4c501f795c364c6e1304ddc8c41225df2c18d
6
+ metadata.gz: f78ad1ed8b49b3ad14c101221cfc8d380f01e31ea4c374b480ca1a514e26a3933bb05b1a1123e12cadaa5aff1d35b6e556703f0783f297dc0502e3ff2306404f
7
+ data.tar.gz: 8998ea12bd8f10ae89b18b2e138b338839202db95bb88328b16ce5435422c1cc1f2b6625f2a97aa653749ca33e93a288db7720526da954e01999214d99eb798d
checksums.yaml.gz.sig CHANGED
Binary file
data/Changelog.md CHANGED
@@ -1,5 +1,55 @@
1
+ ### Development
2
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.2...3-10-maintenance)
3
+
4
+ ### 3.10.2 / 2022-01-14
5
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.1...v3.10.2)
6
+
7
+ Bug Fixes:
8
+
9
+ * Fix support for dynamic matchers for expectation target checks (Phil Pirozhkov, #1294)
10
+ * Fix `expect(array).to include(hash).times`, previously this would fail due to
11
+ matching the entire array as a single hash, rather than a member of the hash.
12
+ (Slava Kardakov, #1322)
13
+ * Ensure `raise_error` matches works with the `error_highlight` option from Ruby 3.1.
14
+ (Peter Goldstein, #1339)
15
+
16
+ ### 3.10.1 / 2020-12-27
17
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.10.0...v3.10.1)
18
+
19
+ Bug Fixes:
20
+
21
+ * Allow JRuby 9.2.x.x to generate backtraces normally rather than via our
22
+ backfill workaround. (#1230, Jon Rowe)
23
+
24
+ ### 3.10.0 / 2020-10-30
25
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.3...v3.10.0)
26
+
27
+ Enhancements:
28
+
29
+ * Allow `include` matcher to be chained with `once`, `at_least`, etc. for simple cases.
30
+ (Marc-André Lafortune, #1168)
31
+ * Add an explicit warning when `nil` is passed to `raise_error`. (Phil Pirozhkov, #1143)
32
+ * Improve `include` matcher's composability. (Phil Pirozhkov, #1155)
33
+ * Mocks expectations can now set a custom failure message.
34
+ (Benoit Tigeot and Nicolas Zermati, #1156)
35
+ * `aggregate_failures` now shows the backtrace line for each failure. (Fabricio Bedin, #1163)
36
+ * Support multiple combinations of `yield_control` modifiers like `at_least`, `at_most`.
37
+ (Jon Rowe, #1169)
38
+ * Dynamic `have_<n>` matchers now have output consistent with other dynamic matchers.
39
+ (Marc-André Lafortune, #1195)
40
+ * New config option `strict_predicate_matchers` allows predicate matcher to be strict
41
+ (i.e. match for `true` or `false`) instead of the default (match truthy vs `false` or `nil`).
42
+ (Marc-André Lafortune, #1196)
43
+
44
+ ### 3.9.4 / 2020-10-29
45
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.3...v3.9.4)
46
+
47
+ Bug Fixes:
48
+
49
+ * Fix regression with `be_` and `have_` matchers and arguments implementing `to_hash`
50
+ were they would act like keywords and be cast to a hash. (Jon Rowe, #1222)
51
+
1
52
  ### 3.9.3 / 2020-10-23
2
- [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.2...v3.9.3)
3
53
 
4
54
  Bug Fixes:
5
55
 
@@ -14,6 +64,7 @@ Bug Fixes:
14
64
  * Prevent errors from causing false positives when using `be <operator>` comparison, e.g.
15
65
  `expect(1).not_to be < 'a'` will now correctly fail rather than pass. (Jon Rowe, #1208)
16
66
 
67
+
17
68
  ### 3.9.2 / 2020-05-08
18
69
  [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.9.1...v3.9.2)
19
70
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # RSpec Expectations [![Build Status](https://secure.travis-ci.org/rspec/rspec-expectations.svg?branch=main)](http://travis-ci.org/rspec/rspec-expectations) [![Code Climate](https://codeclimate.com/github/rspec/rspec-expectations.svg)](https://codeclimate.com/github/rspec/rspec-expectations)
1
+ # RSpec Expectations [![Build Status](https://github.com/rspec/rspec-expectations/workflows/RSpec%20CI/badge.svg?branch=3-10-maintenance)](https://github.com/rspec/rspec-expectations/actions) [![Code Climate](https://codeclimate.com/github/rspec/rspec-expectations.svg)](https://codeclimate.com/github/rspec/rspec-expectations)
2
2
 
3
3
  RSpec::Expectations lets you express expected outcomes on an object in an
4
4
  example.
@@ -28,6 +28,7 @@ module RSpec
28
28
 
29
29
  def initialize
30
30
  @on_potential_false_positives = :warn
31
+ @strict_predicate_matchers = false
31
32
  end
32
33
 
33
34
  # Configures the supported syntax.
@@ -185,6 +186,20 @@ module RSpec
185
186
  @on_potential_false_positives = behavior
186
187
  end
187
188
 
189
+ # Configures RSpec to check predicate matchers to `be(true)` / `be(false)` (strict),
190
+ # or `be_truthy` / `be_falsey` (not strict).
191
+ # 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
+
197
+ attr_reader :strict_predicate_matchers
198
+
199
+ def strict_predicate_matchers?
200
+ @strict_predicate_matchers
201
+ end
202
+
188
203
  # Indicates what RSpec will do about matcher use which will
189
204
  # potentially cause false positives in tests, generally you want to
190
205
  # avoid such scenarios so this defaults to `true`.
@@ -57,7 +57,7 @@ module RSpec
57
57
  # expect { perform }.to raise_error
58
58
  # @param [Matcher]
59
59
  # matcher
60
- # @param [String or Proc] message optional message to display when the expectation fails
60
+ # @param [String, Proc] message optional message to display when the expectation fails
61
61
  # @return [Boolean] true if the expectation succeeds (else raises)
62
62
  # @see RSpec::Matchers
63
63
  def to(matcher=nil, message=nil, &block)
@@ -70,7 +70,7 @@ module RSpec
70
70
  # expect(value).not_to eq(5)
71
71
  # @param [Matcher]
72
72
  # matcher
73
- # @param [String or Proc] message optional message to display when the expectation fails
73
+ # @param [String, Proc] message optional message to display when the expectation fails
74
74
  # @return [Boolean] false if the negative expectation succeeds (else raises)
75
75
  # @see RSpec::Matchers
76
76
  def not_to(matcher=nil, message=nil, &block)
@@ -118,9 +118,7 @@ module RSpec
118
118
  end
119
119
 
120
120
  def supports_block_expectations?(matcher)
121
- matcher.supports_block_expectations?
122
- rescue NoMethodError
123
- false
121
+ matcher.respond_to?(:supports_block_expectations?) && matcher.supports_block_expectations?
124
122
  end
125
123
  end
126
124
  end
@@ -52,10 +52,10 @@ module RSpec
52
52
 
53
53
  private
54
54
 
55
- if RSpec::Support::Ruby.jruby?
56
- # On JRuby, `caller` and `raise` produce different backtraces with regards to `.java`
57
- # stack frames. It's important that we use `raise` for JRuby to produce a backtrace
58
- # that has a continuous common section with the raised `MultipleExpectationsNotMetError`,
55
+ if RSpec::Support::Ruby.jruby? && RSpec::Support::Ruby.jruby_version < '9.2.0.0'
56
+ # On JRuby 9.1.x.x and before, `caller` and `raise` produce different backtraces with
57
+ # regards to `.java` stack frames. It's important that we use `raise` for JRuby to produce
58
+ # a backtrace that has a continuous common section with the raised `MultipleExpectationsNotMetError`,
59
59
  # so that rspec-core's truncation logic can work properly on it to list the backtrace
60
60
  # relative to the `aggregate_failures` block.
61
61
  def assign_backtrace(failure)
@@ -150,11 +150,29 @@ module RSpec
150
150
  def enumerated(exceptions, index_offset)
151
151
  exceptions.each_with_index.map do |exception, index|
152
152
  index += index_offset
153
- formatted_message = yield exception
153
+ formatted_message = "#{yield exception}\n#{format_backtrace(exception.backtrace).first}"
154
154
  "#{index_label index}#{indented formatted_message, index}"
155
155
  end
156
156
  end
157
157
 
158
+ def exclusion_patterns
159
+ patterns = %w[/lib\d*/ruby/ bin/ exe/rspec /lib/bundler/ /exe/bundle:]
160
+ patterns << "org/jruby/" if RSpec::Support::Ruby.jruby?
161
+ patterns.map! { |s| Regexp.new(s.gsub('/', File::SEPARATOR)) }
162
+ end
163
+
164
+ def format_backtrace(backtrace)
165
+ backtrace.map { |l| backtrace_line(l) }.compact.tap { |filtered| filtered.concat backtrace if filtered.empty? }
166
+ end
167
+
168
+ def backtrace_line(line)
169
+ return if [Regexp.union(RSpec::CallerFilter::IGNORE_REGEX, *exclusion_patterns)].any? { |p| line =~ p }
170
+
171
+ # It changes the current path that is relative to
172
+ # system root to be relative to the project root.
173
+ line.sub(/(\A|\s)#{File.expand_path('.')}(#{File::SEPARATOR}|\s|\Z)/, '\\1.\\2'.freeze).sub(/\A([^:]+:\d+)$/, '\\1'.freeze)
174
+ end
175
+
158
176
  def enumerated_failures
159
177
  enumerated(failures, 0, &:message)
160
178
  end
@@ -44,10 +44,16 @@ module RSpec
44
44
 
45
45
  # @private
46
46
  class PositiveExpectationHandler
47
- def self.handle_matcher(actual, initial_matcher, message=nil, &block)
48
- ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
47
+ def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block)
48
+ ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher|
49
49
  return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher
50
- matcher.matches?(actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message)
50
+
51
+ match_result = matcher.matches?(actual, &block)
52
+ if custom_message && match_result.respond_to?(:error_generator)
53
+ match_result.error_generator.opts[:message] = custom_message
54
+ end
55
+
56
+ match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message)
51
57
  end
52
58
  end
53
59
 
@@ -66,10 +72,16 @@ module RSpec
66
72
 
67
73
  # @private
68
74
  class NegativeExpectationHandler
69
- def self.handle_matcher(actual, initial_matcher, message=nil, &block)
70
- ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
75
+ def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block)
76
+ ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher|
71
77
  return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher
72
- does_not_match?(matcher, actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message_when_negated)
78
+
79
+ negated_match_result = does_not_match?(matcher, actual, &block)
80
+ if custom_message && negated_match_result.respond_to?(:error_generator)
81
+ negated_match_result.error_generator.opts[:message] = custom_message
82
+ end
83
+
84
+ negated_match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message_when_negated)
73
85
  end
74
86
  end
75
87
 
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Expectations
3
3
  # @private
4
4
  module Version
5
- STRING = '3.9.3'
5
+ STRING = '3.10.2'
6
6
  end
7
7
  end
8
8
  end
@@ -186,139 +186,6 @@ module RSpec
186
186
  @actual.__send__ @operator, @expected
187
187
  end
188
188
  end
189
-
190
- # @api private
191
- # Provides the implementation of `be_<predicate>`.
192
- # Not intended to be instantiated directly.
193
- class BePredicate < BaseMatcher
194
- include BeHelpers
195
-
196
- if RSpec::Support::RubyFeatures.kw_args_supported?
197
- binding.eval(<<-CODE, __FILE__, __LINE__)
198
- def initialize(*args, **kwargs, &block)
199
- @expected = parse_expected(args.shift)
200
- @args = args
201
- @kwargs = kwargs
202
- @block = block
203
- end
204
- CODE
205
- else
206
- def initialize(*args, &block)
207
- @expected = parse_expected(args.shift)
208
- @args = args
209
- @block = block
210
- end
211
- end
212
-
213
- def matches?(actual, &block)
214
- @actual = actual
215
- @block ||= block
216
- predicate_accessible? && predicate_matches?
217
- end
218
-
219
- def does_not_match?(actual, &block)
220
- @actual = actual
221
- @block ||= block
222
- predicate_accessible? && !predicate_matches?
223
- end
224
-
225
- # @api private
226
- # @return [String]
227
- def failure_message
228
- failure_message_expecting(true)
229
- end
230
-
231
- # @api private
232
- # @return [String]
233
- def failure_message_when_negated
234
- failure_message_expecting(false)
235
- end
236
-
237
- # @api private
238
- # @return [String]
239
- def description
240
- "#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
241
- end
242
-
243
- private
244
-
245
- def predicate_accessible?
246
- actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
247
- end
248
-
249
- # support 1.8.7, evaluate once at load time for performance
250
- if String === methods.first
251
- # :nocov:
252
- def private_predicate?
253
- @actual.private_methods.include? predicate.to_s
254
- end
255
- # :nocov:
256
- else
257
- def private_predicate?
258
- @actual.private_methods.include? predicate
259
- end
260
- end
261
-
262
- if RSpec::Support::RubyFeatures.kw_args_supported?
263
- binding.eval(<<-CODE, __FILE__, __LINE__)
264
- def predicate_matches?
265
- method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
266
- if @kwargs.empty?
267
- @predicate_matches = actual.__send__(method_name, *@args, &@block)
268
- else
269
- @predicate_matches = actual.__send__(method_name, *@args, **@kwargs, &@block)
270
- end
271
- end
272
- CODE
273
- else
274
- def predicate_matches?
275
- method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
276
- @predicate_matches = actual.__send__(method_name, *@args, &@block)
277
- end
278
- end
279
-
280
- def predicate
281
- :"#{@expected}?"
282
- end
283
-
284
- def present_tense_predicate
285
- :"#{@expected}s?"
286
- end
287
-
288
- def parse_expected(expected)
289
- @prefix, expected = prefix_and_expected(expected)
290
- expected
291
- end
292
-
293
- def prefix_and_expected(symbol)
294
- Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
295
- end
296
-
297
- def prefix_to_sentence
298
- EnglishPhrasing.split_words(@prefix)
299
- end
300
-
301
- def failure_message_expecting(value)
302
- validity_message ||
303
- "expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
304
- end
305
-
306
- def validity_message
307
- return nil if predicate_accessible?
308
-
309
- msg = "expected #{actual_formatted} to respond to `#{predicate}`".dup
310
-
311
- if private_predicate?
312
- msg << " but `#{predicate}` is a private method"
313
- elsif predicate == :true?
314
- msg << " or perhaps you meant `be true` or `be_truthy`"
315
- elsif predicate == :false?
316
- msg << " or perhaps you meant `be false` or `be_falsey`"
317
- end
318
-
319
- msg
320
- end
321
- end
322
189
  end
323
190
  end
324
191
  end
@@ -0,0 +1,169 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Asbtract class to implement `once`, `at_least` and other
6
+ # count constraints.
7
+ module CountExpectation
8
+ # @api public
9
+ # Specifies that the method is expected to match once.
10
+ def once
11
+ exactly(1)
12
+ end
13
+
14
+ # @api public
15
+ # Specifies that the method is expected to match twice.
16
+ def twice
17
+ exactly(2)
18
+ end
19
+
20
+ # @api public
21
+ # Specifies that the method is expected to match thrice.
22
+ def thrice
23
+ exactly(3)
24
+ end
25
+
26
+ # @api public
27
+ # Specifies that the method is expected to match the given number of times.
28
+ def exactly(number)
29
+ set_expected_count(:==, number)
30
+ self
31
+ end
32
+
33
+ # @api public
34
+ # Specifies the maximum number of times the method is expected to match
35
+ def at_most(number)
36
+ set_expected_count(:<=, number)
37
+ self
38
+ end
39
+
40
+ # @api public
41
+ # Specifies the minimum number of times the method is expected to match
42
+ def at_least(number)
43
+ set_expected_count(:>=, number)
44
+ self
45
+ end
46
+
47
+ # @api public
48
+ # No-op. Provides syntactic sugar.
49
+ def times
50
+ self
51
+ end
52
+
53
+ protected
54
+ # @api private
55
+ attr_reader :count_expectation_type, :expected_count
56
+
57
+ private
58
+
59
+ if RUBY_VERSION.to_f > 1.8
60
+ def cover?(count, number)
61
+ count.cover?(number)
62
+ end
63
+ else
64
+ def cover?(count, number)
65
+ number >= count.first && number <= count.last
66
+ end
67
+ end
68
+
69
+ def expected_count_matches?(actual_count)
70
+ @actual_count = actual_count
71
+ return @actual_count > 0 unless count_expectation_type
72
+ return cover?(expected_count, actual_count) if count_expectation_type == :<=>
73
+
74
+ @actual_count.__send__(count_expectation_type, expected_count)
75
+ end
76
+
77
+ def has_expected_count?
78
+ !!count_expectation_type
79
+ end
80
+
81
+ def set_expected_count(relativity, n)
82
+ raise_unsupported_count_expectation if unsupported_count_expectation?(relativity)
83
+
84
+ count = count_constraint_to_number(n)
85
+
86
+ if count_expectation_type == :<= && relativity == :>=
87
+ raise_impossible_count_expectation(count) if count > expected_count
88
+ @count_expectation_type = :<=>
89
+ @expected_count = count..expected_count
90
+ elsif count_expectation_type == :>= && relativity == :<=
91
+ raise_impossible_count_expectation(count) if count < expected_count
92
+ @count_expectation_type = :<=>
93
+ @expected_count = expected_count..count
94
+ else
95
+ @count_expectation_type = relativity
96
+ @expected_count = count
97
+ end
98
+ end
99
+
100
+ def raise_impossible_count_expectation(count)
101
+ text =
102
+ case count_expectation_type
103
+ when :<= then "at_least(#{count}).at_most(#{expected_count})"
104
+ when :>= then "at_least(#{expected_count}).at_most(#{count})"
105
+ end
106
+ raise ArgumentError, "The constraint #{text} is not possible"
107
+ end
108
+
109
+ def raise_unsupported_count_expectation
110
+ text =
111
+ case count_expectation_type
112
+ when :<= then "at_least"
113
+ when :>= then "at_most"
114
+ when :<=> then "at_least/at_most combination"
115
+ else "count"
116
+ end
117
+ raise ArgumentError, "Multiple #{text} constraints are not supported"
118
+ end
119
+
120
+ def count_constraint_to_number(n)
121
+ case n
122
+ when Numeric then n
123
+ when :once then 1
124
+ when :twice then 2
125
+ when :thrice then 3
126
+ else
127
+ raise ArgumentError, "Expected a number, :once, :twice or :thrice," \
128
+ " but got #{n}"
129
+ end
130
+ end
131
+
132
+ def unsupported_count_expectation?(relativity)
133
+ return true if count_expectation_type == :==
134
+ return true if count_expectation_type == :<=>
135
+ (count_expectation_type == :<= && relativity == :<=) ||
136
+ (count_expectation_type == :>= && relativity == :>=)
137
+ end
138
+
139
+ def count_expectation_description
140
+ "#{human_readable_expectation_type}#{human_readable_count(expected_count)}"
141
+ end
142
+
143
+ def count_failure_reason(action)
144
+ "#{count_expectation_description}" \
145
+ " but #{action}#{human_readable_count(@actual_count)}"
146
+ end
147
+
148
+ def human_readable_expectation_type
149
+ case count_expectation_type
150
+ when :<= then ' at most'
151
+ when :>= then ' at least'
152
+ when :<=> then ' between'
153
+ else ''
154
+ end
155
+ end
156
+
157
+ def human_readable_count(count)
158
+ case count
159
+ when Range then " #{count.first} and #{count.last} times"
160
+ when nil then ''
161
+ when 1 then ' once'
162
+ when 2 then ' twice'
163
+ else " #{count} times"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end