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.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +138 -2
  5. data/README.md +37 -20
  6. data/lib/rspec/expectations.rb +2 -1
  7. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  8. data/lib/rspec/expectations/configuration.rb +14 -0
  9. data/lib/rspec/expectations/expectation_target.rb +2 -2
  10. data/lib/rspec/expectations/fail_with.rb +9 -1
  11. data/lib/rspec/expectations/handler.rb +2 -2
  12. data/lib/rspec/expectations/minitest_integration.rb +1 -1
  13. data/lib/rspec/expectations/syntax.rb +2 -2
  14. data/lib/rspec/expectations/version.rb +1 -1
  15. data/lib/rspec/matchers.rb +97 -97
  16. data/lib/rspec/matchers/built_in/all.rb +1 -0
  17. data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
  18. data/lib/rspec/matchers/built_in/be.rb +2 -2
  19. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  20. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  21. data/lib/rspec/matchers/built_in/change.rb +127 -53
  22. data/lib/rspec/matchers/built_in/compound.rb +6 -2
  23. data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
  24. data/lib/rspec/matchers/built_in/exist.rb +5 -1
  25. data/lib/rspec/matchers/built_in/has.rb +1 -1
  26. data/lib/rspec/matchers/built_in/include.rb +6 -0
  27. data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
  28. data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
  29. data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
  30. data/lib/rspec/matchers/built_in/yield.rb +43 -30
  31. data/lib/rspec/matchers/composable.rb +6 -20
  32. data/lib/rspec/matchers/dsl.rb +72 -4
  33. data/lib/rspec/matchers/english_phrasing.rb +3 -3
  34. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
  35. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  36. metadata +22 -18
  37. 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? expected
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? expected
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(@change_details, expected_delta, :by) do |actual_delta|
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(@change_details, minimum, :by_at_least) do |actual_delta|
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(@change_details, maximum, :by_at_most) do |actual_delta|
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(@change_details, value)
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(@change_details, value)
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
- @change_details.perform_change(event_proc)
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
- !matches?(event_proc) && Proc === event_proc
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 #{@change_details.message} to have changed, " \
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 #{@change_details.message} not to have changed, " \
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 #{@change_details.message}"
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
- @change_details = ChangeDetails.new(receiver, message, &block)
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 #{description_of @change_details.actual_before}"
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 #{description_of @change_details.actual_before} " \
102
- "to #{description_of @change_details.actual_after}"
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.message} to have changed " \
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
- return false unless Proc === event_proc
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.message} " \
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
- @event_proc = event_proc
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.message} #{change_description}"
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 matches_before?
197
- values_match?(@expected_before, @change_details.actual_before)
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.message} " \
224
+ "expected #{@change_details.value_representation} " \
206
225
  "to have initially been #{description_of @expected_before}, " \
207
- "but was #{description_of @change_details.actual_before}"
226
+ "but was #{@actual_before_description}"
208
227
  end
209
228
 
210
229
  def after_value_failure
211
- "expected #{@change_details.message} " \
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.message} " \
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.message} not to have changed, but " \
223
- "did change from #{description_of @change_details.actual_before} " \
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.message} to have changed " \
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
- @event_proc = event_proc
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 :message, :actual_before, :actual_after
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
- @message = message ? "##{message}" : "result"
322
- @value_proc = block || lambda { receiver.__send__(message) }
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
- @actual_before != @actual_after
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
- case val = @value_proc.call
343
- when IO # enumerable, but we don't want to dup it.
344
- val
345
- when Enumerable, String
346
- val.dup
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
- val
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 should_enumerate?(actual) && actual.respond_to?(:to_a)
89
+ elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
89
90
  @actual = actual.to_a
90
91
  else
91
- return false
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