rspec-expectations 3.5.0 → 3.9.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/Changelog.md +138 -2
- data/README.md +37 -20
- data/lib/rspec/expectations.rb +2 -1
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +14 -0
- data/lib/rspec/expectations/expectation_target.rb +2 -2
- data/lib/rspec/expectations/fail_with.rb +9 -1
- data/lib/rspec/expectations/handler.rb +2 -2
- data/lib/rspec/expectations/minitest_integration.rb +1 -1
- data/lib/rspec/expectations/syntax.rb +2 -2
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers.rb +97 -97
- data/lib/rspec/matchers/built_in/all.rb +1 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
- data/lib/rspec/matchers/built_in/be.rb +2 -2
- data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
- data/lib/rspec/matchers/built_in/change.rb +127 -53
- data/lib/rspec/matchers/built_in/compound.rb +6 -2
- data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
- data/lib/rspec/matchers/built_in/exist.rb +5 -1
- data/lib/rspec/matchers/built_in/has.rb +1 -1
- data/lib/rspec/matchers/built_in/include.rb +6 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
- data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
- data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
- data/lib/rspec/matchers/built_in/yield.rb +43 -30
- data/lib/rspec/matchers/composable.rb +6 -20
- data/lib/rspec/matchers/dsl.rb +72 -4
- data/lib/rspec/matchers/english_phrasing.rb +3 -3
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
- data/lib/rspec/matchers/generated_descriptions.rb +1 -2
- metadata +22 -18
- metadata.gz.sig +0 -0
@@ -14,7 +14,11 @@ module RSpec
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def match(expected, actual)
|
17
|
-
actual.instance_of?
|
17
|
+
actual.instance_of?(expected)
|
18
|
+
rescue NoMethodError
|
19
|
+
raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
|
20
|
+
"the actual object responds to #instance_of? method " \
|
21
|
+
"but a `NoMethodError` was encountered instead."
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -8,7 +8,11 @@ module RSpec
|
|
8
8
|
private
|
9
9
|
|
10
10
|
def match(expected, actual)
|
11
|
-
actual.kind_of?
|
11
|
+
actual.kind_of?(expected)
|
12
|
+
rescue NoMethodError
|
13
|
+
raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
|
14
|
+
"the actual object responds to #kind_of? method " \
|
15
|
+
"but a `NoMethodError` was encountered instead."
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -8,7 +8,7 @@ module RSpec
|
|
8
8
|
# @api public
|
9
9
|
# Specifies the delta of the expected change.
|
10
10
|
def by(expected_delta)
|
11
|
-
ChangeRelatively.new(
|
11
|
+
ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta|
|
12
12
|
values_match?(expected_delta, actual_delta)
|
13
13
|
end
|
14
14
|
end
|
@@ -16,7 +16,7 @@ module RSpec
|
|
16
16
|
# @api public
|
17
17
|
# Specifies a minimum delta of the expected change.
|
18
18
|
def by_at_least(minimum)
|
19
|
-
ChangeRelatively.new(
|
19
|
+
ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta|
|
20
20
|
actual_delta >= minimum
|
21
21
|
end
|
22
22
|
end
|
@@ -24,7 +24,7 @@ module RSpec
|
|
24
24
|
# @api public
|
25
25
|
# Specifies a maximum delta of the expected change.
|
26
26
|
def by_at_most(maximum)
|
27
|
-
ChangeRelatively.new(
|
27
|
+
ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta|
|
28
28
|
actual_delta <= maximum
|
29
29
|
end
|
30
30
|
end
|
@@ -32,47 +32,44 @@ module RSpec
|
|
32
32
|
# @api public
|
33
33
|
# Specifies the new value you expect.
|
34
34
|
def to(value)
|
35
|
-
ChangeToValue.new(
|
35
|
+
ChangeToValue.new(change_details, value)
|
36
36
|
end
|
37
37
|
|
38
38
|
# @api public
|
39
39
|
# Specifies the original value.
|
40
40
|
def from(value)
|
41
|
-
ChangeFromValue.new(
|
41
|
+
ChangeFromValue.new(change_details, value)
|
42
42
|
end
|
43
43
|
|
44
44
|
# @private
|
45
45
|
def matches?(event_proc)
|
46
|
-
@event_proc = event_proc
|
47
|
-
return false unless Proc === event_proc
|
48
46
|
raise_block_syntax_error if block_given?
|
49
|
-
|
50
|
-
@change_details.changed?
|
47
|
+
perform_change(event_proc) && change_details.changed?
|
51
48
|
end
|
52
49
|
|
53
50
|
def does_not_match?(event_proc)
|
54
51
|
raise_block_syntax_error if block_given?
|
55
|
-
|
52
|
+
perform_change(event_proc) && !change_details.changed?
|
56
53
|
end
|
57
54
|
|
58
55
|
# @api private
|
59
56
|
# @return [String]
|
60
57
|
def failure_message
|
61
|
-
"expected #{
|
58
|
+
"expected #{change_details.value_representation} to have changed, " \
|
62
59
|
"but #{positive_failure_reason}"
|
63
60
|
end
|
64
61
|
|
65
62
|
# @api private
|
66
63
|
# @return [String]
|
67
64
|
def failure_message_when_negated
|
68
|
-
"expected #{
|
65
|
+
"expected #{change_details.value_representation} not to have changed, " \
|
69
66
|
"but #{negative_failure_reason}"
|
70
67
|
end
|
71
68
|
|
72
69
|
# @api private
|
73
70
|
# @return [String]
|
74
71
|
def description
|
75
|
-
"change #{
|
72
|
+
"change #{change_details.value_representation}"
|
76
73
|
end
|
77
74
|
|
78
75
|
# @private
|
@@ -83,7 +80,25 @@ module RSpec
|
|
83
80
|
private
|
84
81
|
|
85
82
|
def initialize(receiver=nil, message=nil, &block)
|
86
|
-
@
|
83
|
+
@receiver = receiver
|
84
|
+
@message = message
|
85
|
+
@block = block
|
86
|
+
end
|
87
|
+
|
88
|
+
def change_details
|
89
|
+
@change_details ||= ChangeDetails.new(matcher_name, @receiver, @message, &@block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def perform_change(event_proc)
|
93
|
+
@event_proc = event_proc
|
94
|
+
change_details.perform_change(event_proc) do |actual_before|
|
95
|
+
# pre-compute values derived from the `before` value before the
|
96
|
+
# mutation is applied, in case the specified mutation is mutation
|
97
|
+
# of a single object (rather than a changing what object a method
|
98
|
+
# returns). We need to cache these values before the `before` value
|
99
|
+
# they are based on potentially gets mutated.
|
100
|
+
@actual_before_description = description_of(actual_before)
|
101
|
+
end
|
87
102
|
end
|
88
103
|
|
89
104
|
def raise_block_syntax_error
|
@@ -93,13 +108,13 @@ module RSpec
|
|
93
108
|
|
94
109
|
def positive_failure_reason
|
95
110
|
return "was not given a block" unless Proc === @event_proc
|
96
|
-
"is still #{
|
111
|
+
"is still #{@actual_before_description}"
|
97
112
|
end
|
98
113
|
|
99
114
|
def negative_failure_reason
|
100
115
|
return "was not given a block" unless Proc === @event_proc
|
101
|
-
"did change from #{
|
102
|
-
"to #{description_of
|
116
|
+
"did change from #{@actual_before_description} " \
|
117
|
+
"to #{description_of change_details.actual_after}"
|
103
118
|
end
|
104
119
|
end
|
105
120
|
|
@@ -115,7 +130,7 @@ module RSpec
|
|
115
130
|
|
116
131
|
# @private
|
117
132
|
def failure_message
|
118
|
-
"expected #{@change_details.
|
133
|
+
"expected #{@change_details.value_representation} to have changed " \
|
119
134
|
"#{@relativity.to_s.tr('_', ' ')} " \
|
120
135
|
"#{description_of @expected_delta}, but #{failure_reason}"
|
121
136
|
end
|
@@ -123,9 +138,7 @@ module RSpec
|
|
123
138
|
# @private
|
124
139
|
def matches?(event_proc)
|
125
140
|
@event_proc = event_proc
|
126
|
-
|
127
|
-
@change_details.perform_change(event_proc)
|
128
|
-
@comparer.call(@change_details.actual_delta)
|
141
|
+
@change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta)
|
129
142
|
end
|
130
143
|
|
131
144
|
# @private
|
@@ -136,7 +149,7 @@ module RSpec
|
|
136
149
|
|
137
150
|
# @private
|
138
151
|
def description
|
139
|
-
"change #{@change_details.
|
152
|
+
"change #{@change_details.value_representation} " \
|
140
153
|
"#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
|
141
154
|
end
|
142
155
|
|
@@ -167,21 +180,18 @@ module RSpec
|
|
167
180
|
|
168
181
|
# @private
|
169
182
|
def matches?(event_proc)
|
170
|
-
|
171
|
-
return false unless Proc === event_proc
|
172
|
-
@change_details.perform_change(event_proc)
|
173
|
-
@change_details.changed? && matches_before? && matches_after?
|
183
|
+
perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after?
|
174
184
|
end
|
175
185
|
|
176
186
|
# @private
|
177
187
|
def description
|
178
|
-
"change #{@change_details.
|
188
|
+
"change #{@change_details.value_representation} #{change_description}"
|
179
189
|
end
|
180
190
|
|
181
191
|
# @private
|
182
192
|
def failure_message
|
183
193
|
return not_given_a_block_failure unless Proc === @event_proc
|
184
|
-
return before_value_failure unless matches_before
|
194
|
+
return before_value_failure unless @matches_before
|
185
195
|
return did_not_change_failure unless @change_details.changed?
|
186
196
|
after_value_failure
|
187
197
|
end
|
@@ -193,8 +203,17 @@ module RSpec
|
|
193
203
|
|
194
204
|
private
|
195
205
|
|
196
|
-
def
|
197
|
-
|
206
|
+
def perform_change(event_proc)
|
207
|
+
@event_proc = event_proc
|
208
|
+
@change_details.perform_change(event_proc) do |actual_before|
|
209
|
+
# pre-compute values derived from the `before` value before the
|
210
|
+
# mutation is applied, in case the specified mutation is mutation
|
211
|
+
# of a single object (rather than a changing what object a method
|
212
|
+
# returns). We need to cache these values before the `before` value
|
213
|
+
# they are based on potentially gets mutated.
|
214
|
+
@matches_before = values_match?(@expected_before, actual_before)
|
215
|
+
@actual_before_description = description_of(actual_before)
|
216
|
+
end
|
198
217
|
end
|
199
218
|
|
200
219
|
def matches_after?
|
@@ -202,30 +221,30 @@ module RSpec
|
|
202
221
|
end
|
203
222
|
|
204
223
|
def before_value_failure
|
205
|
-
"expected #{@change_details.
|
224
|
+
"expected #{@change_details.value_representation} " \
|
206
225
|
"to have initially been #{description_of @expected_before}, " \
|
207
|
-
"but was #{
|
226
|
+
"but was #{@actual_before_description}"
|
208
227
|
end
|
209
228
|
|
210
229
|
def after_value_failure
|
211
|
-
"expected #{@change_details.
|
230
|
+
"expected #{@change_details.value_representation} " \
|
212
231
|
"to have changed to #{description_of @expected_after}, " \
|
213
232
|
"but is now #{description_of @change_details.actual_after}"
|
214
233
|
end
|
215
234
|
|
216
235
|
def did_not_change_failure
|
217
|
-
"expected #{@change_details.
|
236
|
+
"expected #{@change_details.value_representation} " \
|
218
237
|
"to have changed #{change_description}, but did not change"
|
219
238
|
end
|
220
239
|
|
221
240
|
def did_change_failure
|
222
|
-
"expected #{@change_details.
|
223
|
-
"did change from #{
|
241
|
+
"expected #{@change_details.value_representation} not to have changed, but " \
|
242
|
+
"did change from #{@actual_before_description} " \
|
224
243
|
"to #{description_of @change_details.actual_after}"
|
225
244
|
end
|
226
245
|
|
227
246
|
def not_given_a_block_failure
|
228
|
-
"expected #{@change_details.
|
247
|
+
"expected #{@change_details.value_representation} to have changed " \
|
229
248
|
"#{change_description}, but was not given a block"
|
230
249
|
end
|
231
250
|
end
|
@@ -254,16 +273,13 @@ module RSpec
|
|
254
273
|
"is not supported"
|
255
274
|
end
|
256
275
|
|
257
|
-
|
258
|
-
return false unless Proc === event_proc
|
259
|
-
@change_details.perform_change(event_proc)
|
260
|
-
!@change_details.changed? && matches_before?
|
276
|
+
perform_change(event_proc) && !@change_details.changed? && @matches_before
|
261
277
|
end
|
262
278
|
|
263
279
|
# @private
|
264
280
|
def failure_message_when_negated
|
265
281
|
return not_given_a_block_failure unless Proc === @event_proc
|
266
|
-
return before_value_failure unless matches_before
|
282
|
+
return before_value_failure unless @matches_before
|
267
283
|
did_change_failure
|
268
284
|
end
|
269
285
|
|
@@ -306,10 +322,22 @@ module RSpec
|
|
306
322
|
|
307
323
|
# @private
|
308
324
|
# Encapsulates the details of the before/after values.
|
325
|
+
#
|
326
|
+
# Note that this class exposes the `actual_after` value, to allow the
|
327
|
+
# matchers above to derive failure messages, etc from the value on demand
|
328
|
+
# as needed, but it intentionally does _not_ expose the `actual_before`
|
329
|
+
# value. Some usages of the `change` matcher mutate a specific object
|
330
|
+
# returned by the value proc, which means that failure message snippets,
|
331
|
+
# etc, which are derived from the `before` value may not be accurate if
|
332
|
+
# they are lazily computed as needed. We must pre-compute them before
|
333
|
+
# applying the change in the `expect` block. To ensure that all `change`
|
334
|
+
# matchers do that properly, we do not expose the `actual_before` value.
|
335
|
+
# Instead, matchers must pass a block to `perform_change`, which yields
|
336
|
+
# the `actual_before` value before applying the change.
|
309
337
|
class ChangeDetails
|
310
|
-
attr_reader :
|
338
|
+
attr_reader :actual_after
|
311
339
|
|
312
|
-
def initialize(receiver=nil, message=nil, &block)
|
340
|
+
def initialize(matcher_name, receiver=nil, message=nil, &block)
|
313
341
|
if receiver && !message
|
314
342
|
raise(
|
315
343
|
ArgumentError,
|
@@ -318,18 +346,51 @@ module RSpec
|
|
318
346
|
"You passed an object but no message."
|
319
347
|
)
|
320
348
|
end
|
321
|
-
|
322
|
-
@
|
349
|
+
|
350
|
+
@matcher_name = matcher_name
|
351
|
+
@receiver = receiver
|
352
|
+
@message = message
|
353
|
+
@value_proc = block
|
354
|
+
end
|
355
|
+
|
356
|
+
def value_representation
|
357
|
+
@value_representation ||=
|
358
|
+
if @message
|
359
|
+
"`#{message_notation(@receiver, @message)}`"
|
360
|
+
elsif (value_block_snippet = extract_value_block_snippet)
|
361
|
+
"`#{value_block_snippet}`"
|
362
|
+
else
|
363
|
+
'result'
|
364
|
+
end
|
323
365
|
end
|
324
366
|
|
325
367
|
def perform_change(event_proc)
|
326
368
|
@actual_before = evaluate_value_proc
|
369
|
+
@before_hash = @actual_before.hash
|
370
|
+
yield @actual_before if block_given?
|
371
|
+
|
372
|
+
return false unless Proc === event_proc
|
327
373
|
event_proc.call
|
374
|
+
|
328
375
|
@actual_after = evaluate_value_proc
|
376
|
+
@actual_hash = @actual_after.hash
|
377
|
+
true
|
329
378
|
end
|
330
379
|
|
331
380
|
def changed?
|
332
|
-
|
381
|
+
# Consider it changed if either:
|
382
|
+
#
|
383
|
+
# - The before/after values are unequal
|
384
|
+
# - The before/after values have different hash values
|
385
|
+
#
|
386
|
+
# The latter case specifically handles the case when the value proc
|
387
|
+
# returns the exact same object, but it has been mutated.
|
388
|
+
#
|
389
|
+
# Note that it is not sufficient to only check the hashes; it is
|
390
|
+
# possible for two values to be unequal (and of different classes)
|
391
|
+
# but to return the same hash value. Also, some objects may change
|
392
|
+
# their hash after being compared with `==`/`!=`.
|
393
|
+
@actual_before != @actual_after || @before_hash != @actual_hash
|
333
394
|
end
|
334
395
|
|
335
396
|
def actual_delta
|
@@ -339,13 +400,26 @@ module RSpec
|
|
339
400
|
private
|
340
401
|
|
341
402
|
def evaluate_value_proc
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
403
|
+
@value_proc ? @value_proc.call : @receiver.__send__(@message)
|
404
|
+
end
|
405
|
+
|
406
|
+
def message_notation(receiver, message)
|
407
|
+
case receiver
|
408
|
+
when Module
|
409
|
+
"#{receiver}.#{message}"
|
347
410
|
else
|
348
|
-
|
411
|
+
"#{Support.class_of(receiver)}##{message}"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
416
|
+
def extract_value_block_snippet
|
417
|
+
return nil unless @value_proc
|
418
|
+
Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name)
|
419
|
+
end
|
420
|
+
else
|
421
|
+
def extract_value_block_snippet
|
422
|
+
nil
|
349
423
|
end
|
350
424
|
end
|
351
425
|
end
|
@@ -3,7 +3,6 @@ module RSpec
|
|
3
3
|
module BuiltIn
|
4
4
|
# @api private
|
5
5
|
# Base class for `and` and `or` compound matchers.
|
6
|
-
# rubocop:disable ClassLength
|
7
6
|
class Compound < BaseMatcher
|
8
7
|
# @private
|
9
8
|
attr_reader :matcher_1, :matcher_2, :evaluator
|
@@ -155,7 +154,12 @@ module RSpec
|
|
155
154
|
end
|
156
155
|
|
157
156
|
def matcher_matches?(matcher)
|
158
|
-
@match_results.fetch(matcher)
|
157
|
+
@match_results.fetch(matcher) do
|
158
|
+
raise ArgumentError, "Your #{matcher.description} has no match " \
|
159
|
+
"results, this can occur when an unexpected call stack or " \
|
160
|
+
"local jump occurs. Prehaps one of your matchers needs to " \
|
161
|
+
"declare `expects_call_stack_jump?` as `true`?"
|
162
|
+
end
|
159
163
|
end
|
160
164
|
|
161
165
|
private
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Matchers
|
3
3
|
module BuiltIn
|
4
|
+
# rubocop:disable ClassLength
|
4
5
|
# @api private
|
5
6
|
# Provides the implementation for `contain_exactly` and `match_array`.
|
6
7
|
# Not intended to be instantiated directly.
|
@@ -85,10 +86,10 @@ module RSpec
|
|
85
86
|
def convert_actual_to_an_array
|
86
87
|
if actual.respond_to?(:to_ary)
|
87
88
|
@actual = actual.to_ary
|
88
|
-
elsif
|
89
|
+
elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
|
89
90
|
@actual = actual.to_a
|
90
91
|
else
|
91
|
-
|
92
|
+
false
|
92
93
|
end
|
93
94
|
end
|
94
95
|
|
@@ -98,6 +99,19 @@ module RSpec
|
|
98
99
|
array
|
99
100
|
end
|
100
101
|
|
102
|
+
if RUBY_VERSION == "1.8.7"
|
103
|
+
def to_a_disallowed?(object)
|
104
|
+
case object
|
105
|
+
when NilClass, String then true
|
106
|
+
else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
|
107
|
+
end
|
108
|
+
end
|
109
|
+
else
|
110
|
+
def to_a_disallowed?(object)
|
111
|
+
NilClass === object
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
101
115
|
def missing_items
|
102
116
|
@missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
|
103
117
|
expected[index]
|
@@ -162,6 +176,7 @@ module RSpec
|
|
162
176
|
#
|
163
177
|
# @private
|
164
178
|
class PairingsMaximizer
|
179
|
+
# @private
|
165
180
|
Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
|
166
181
|
:indeterminate_expected_indexes, :indeterminate_actual_indexes) do
|
167
182
|
def worse_than?(other)
|
@@ -281,6 +296,7 @@ module RSpec
|
|
281
296
|
end
|
282
297
|
end
|
283
298
|
end
|
299
|
+
# rubocop:enable ClassLength
|
284
300
|
end
|
285
301
|
end
|
286
302
|
end
|