rspec-expectations 3.0.4 → 3.12.3

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 (59) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +530 -5
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +73 -31
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +96 -1
  10. data/lib/rspec/expectations/expectation_target.rb +82 -38
  11. data/lib/rspec/expectations/fail_with.rb +11 -6
  12. data/lib/rspec/expectations/failure_aggregator.rb +229 -0
  13. data/lib/rspec/expectations/handler.rb +36 -15
  14. data/lib/rspec/expectations/minitest_integration.rb +43 -2
  15. data/lib/rspec/expectations/syntax.rb +5 -5
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +15 -1
  18. data/lib/rspec/matchers/aliased_matcher.rb +79 -4
  19. data/lib/rspec/matchers/built_in/all.rb +11 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +111 -28
  21. data/lib/rspec/matchers/built_in/be.rb +28 -114
  22. data/lib/rspec/matchers/built_in/be_between.rb +1 -1
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +5 -12
  26. data/lib/rspec/matchers/built_in/change.rb +171 -63
  27. data/lib/rspec/matchers/built_in/compound.rb +201 -30
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +73 -12
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/eq.rb +3 -38
  31. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  32. data/lib/rspec/matchers/built_in/equal.rb +3 -3
  33. data/lib/rspec/matchers/built_in/exist.rb +7 -3
  34. data/lib/rspec/matchers/built_in/has.rb +93 -30
  35. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  36. data/lib/rspec/matchers/built_in/include.rb +133 -25
  37. data/lib/rspec/matchers/built_in/match.rb +79 -2
  38. data/lib/rspec/matchers/built_in/operators.rb +14 -5
  39. data/lib/rspec/matchers/built_in/output.rb +59 -2
  40. data/lib/rspec/matchers/built_in/raise_error.rb +130 -27
  41. data/lib/rspec/matchers/built_in/respond_to.rb +117 -15
  42. data/lib/rspec/matchers/built_in/satisfy.rb +28 -14
  43. data/lib/rspec/matchers/built_in/{start_and_end_with.rb → start_or_end_with.rb} +20 -8
  44. data/lib/rspec/matchers/built_in/throw_symbol.rb +15 -5
  45. data/lib/rspec/matchers/built_in/yield.rb +129 -156
  46. data/lib/rspec/matchers/built_in.rb +5 -3
  47. data/lib/rspec/matchers/composable.rb +24 -36
  48. data/lib/rspec/matchers/dsl.rb +203 -37
  49. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  50. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  51. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  52. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  53. data/lib/rspec/matchers/matcher_delegator.rb +3 -4
  54. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  55. data/lib/rspec/matchers.rb +267 -144
  56. data.tar.gz.sig +0 -0
  57. metadata +71 -49
  58. metadata.gz.sig +0 -0
  59. data/lib/rspec/matchers/pretty.rb +0 -77
@@ -4,13 +4,11 @@ module RSpec
4
4
  # @api private
5
5
  # Provides the implementation for `change`.
6
6
  # Not intended to be instantiated directly.
7
- class Change
8
- include Composable
9
-
7
+ class Change < BaseMatcher
10
8
  # @api public
11
9
  # Specifies the delta of the expected change.
12
10
  def by(expected_delta)
13
- ChangeRelatively.new(@change_details, expected_delta, :by) do |actual_delta|
11
+ ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta|
14
12
  values_match?(expected_delta, actual_delta)
15
13
  end
16
14
  end
@@ -18,7 +16,7 @@ module RSpec
18
16
  # @api public
19
17
  # Specifies a minimum delta of the expected change.
20
18
  def by_at_least(minimum)
21
- ChangeRelatively.new(@change_details, minimum, :by_at_least) do |actual_delta|
19
+ ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta|
22
20
  actual_delta >= minimum
23
21
  end
24
22
  end
@@ -26,7 +24,7 @@ module RSpec
26
24
  # @api public
27
25
  # Specifies a maximum delta of the expected change.
28
26
  def by_at_most(maximum)
29
- ChangeRelatively.new(@change_details, maximum, :by_at_most) do |actual_delta|
27
+ ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta|
30
28
  actual_delta <= maximum
31
29
  end
32
30
  end
@@ -34,45 +32,44 @@ module RSpec
34
32
  # @api public
35
33
  # Specifies the new value you expect.
36
34
  def to(value)
37
- ChangeToValue.new(@change_details, value)
35
+ ChangeToValue.new(change_details, value)
38
36
  end
39
37
 
40
38
  # @api public
41
39
  # Specifies the original value.
42
40
  def from(value)
43
- ChangeFromValue.new(@change_details, value)
41
+ ChangeFromValue.new(change_details, value)
44
42
  end
45
43
 
46
44
  # @private
47
45
  def matches?(event_proc)
48
- @event_proc = event_proc
49
- return false unless Proc === event_proc
50
46
  raise_block_syntax_error if block_given?
51
- @change_details.perform_change(event_proc)
52
- @change_details.changed?
47
+ perform_change(event_proc) && change_details.changed?
53
48
  end
54
49
 
55
50
  def does_not_match?(event_proc)
56
51
  raise_block_syntax_error if block_given?
57
- !matches?(event_proc) && Proc === event_proc
52
+ perform_change(event_proc) && !change_details.changed?
58
53
  end
59
54
 
60
55
  # @api private
61
56
  # @return [String]
62
57
  def failure_message
63
- "expected #{@change_details.message} to have changed, but #{positive_failure_reason}"
58
+ "expected #{change_details.value_representation} to have changed, " \
59
+ "but #{positive_failure_reason}"
64
60
  end
65
61
 
66
62
  # @api private
67
63
  # @return [String]
68
64
  def failure_message_when_negated
69
- "expected #{@change_details.message} not to have changed, but #{negative_failure_reason}"
65
+ "expected #{change_details.value_representation} not to have changed, " \
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
@@ -80,33 +77,55 @@ module RSpec
80
77
  true
81
78
  end
82
79
 
80
+ # @private
81
+ def supports_value_expectations?
82
+ false
83
+ end
84
+
83
85
  private
84
86
 
85
87
  def initialize(receiver=nil, message=nil, &block)
86
- @change_details = ChangeDetails.new(receiver, message, &block)
88
+ @receiver = receiver
89
+ @message = message
90
+ @block = block
91
+ end
92
+
93
+ def change_details
94
+ @change_details ||= ChangeDetails.new(matcher_name, @receiver, @message, &@block)
95
+ end
96
+
97
+ def perform_change(event_proc)
98
+ @event_proc = event_proc
99
+ change_details.perform_change(event_proc) do |actual_before|
100
+ # pre-compute values derived from the `before` value before the
101
+ # mutation is applied, in case the specified mutation is mutation
102
+ # of a single object (rather than a changing what object a method
103
+ # returns). We need to cache these values before the `before` value
104
+ # they are based on potentially gets mutated.
105
+ @actual_before_description = description_of(actual_before)
106
+ end
87
107
  end
88
108
 
89
109
  def raise_block_syntax_error
90
- raise SyntaxError, "The block passed to the `change` matcher must " \
91
- "use `{ ... }` instead of do/end"
110
+ raise SyntaxError, "Block not received by the `change` matcher. " \
111
+ "Perhaps you want to use `{ ... }` instead of do/end?"
92
112
  end
93
113
 
94
114
  def positive_failure_reason
95
115
  return "was not given a block" unless Proc === @event_proc
96
- "is still #{description_of @change_details.actual_before}"
116
+ "is still #{@actual_before_description}"
97
117
  end
98
118
 
99
119
  def negative_failure_reason
100
120
  return "was not given a block" unless Proc === @event_proc
101
- "did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
121
+ "did change from #{@actual_before_description} " \
122
+ "to #{description_of change_details.actual_after}"
102
123
  end
103
124
  end
104
125
 
105
126
  # Used to specify a relative change.
106
127
  # @api private
107
- class ChangeRelatively
108
- include Composable
109
-
128
+ class ChangeRelatively < BaseMatcher
110
129
  def initialize(change_details, expected_delta, relativity, &comparer)
111
130
  @change_details = change_details
112
131
  @expected_delta = expected_delta
@@ -116,25 +135,27 @@ module RSpec
116
135
 
117
136
  # @private
118
137
  def failure_message
119
- "expected #{@change_details.message} to have changed #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}, but #{failure_reason}"
138
+ "expected #{@change_details.value_representation} to have changed " \
139
+ "#{@relativity.to_s.tr('_', ' ')} " \
140
+ "#{description_of @expected_delta}, but #{failure_reason}"
120
141
  end
121
142
 
122
143
  # @private
123
144
  def matches?(event_proc)
124
145
  @event_proc = event_proc
125
- return false unless Proc === event_proc
126
- @change_details.perform_change(event_proc)
127
- @comparer.call(@change_details.actual_delta)
146
+ @change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta)
128
147
  end
129
148
 
130
149
  # @private
131
150
  def does_not_match?(_event_proc)
132
- raise NotImplementedError, "`expect { }.not_to change { }.#{@relativity}()` is not supported"
151
+ raise NotImplementedError, "`expect { }.not_to change " \
152
+ "{ }.#{@relativity}()` is not supported"
133
153
  end
134
154
 
135
155
  # @private
136
156
  def description
137
- "change #{@change_details.message} #{@relativity.to_s.gsub("_", " ")} #{description_of @expected_delta}"
157
+ "change #{@change_details.value_representation} " \
158
+ "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
138
159
  end
139
160
 
140
161
  # @private
@@ -142,6 +163,11 @@ module RSpec
142
163
  true
143
164
  end
144
165
 
166
+ # @private
167
+ def supports_value_expectations?
168
+ false
169
+ end
170
+
145
171
  private
146
172
 
147
173
  def failure_reason
@@ -152,8 +178,7 @@ module RSpec
152
178
 
153
179
  # @api private
154
180
  # Base class for specifying a change from and/or to specific values.
155
- class SpecificValuesChange
156
- include Composable
181
+ class SpecificValuesChange < BaseMatcher
157
182
  # @private
158
183
  MATCH_ANYTHING = ::Object.ancestors.last
159
184
 
@@ -165,21 +190,18 @@ module RSpec
165
190
 
166
191
  # @private
167
192
  def matches?(event_proc)
168
- @event_proc = event_proc
169
- return false unless Proc === event_proc
170
- @change_details.perform_change(event_proc)
171
- @change_details.changed? && matches_before? && matches_after?
193
+ perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after?
172
194
  end
173
195
 
174
196
  # @private
175
197
  def description
176
- "change #{@change_details.message} #{change_description}"
198
+ "change #{@change_details.value_representation} #{change_description}"
177
199
  end
178
200
 
179
201
  # @private
180
202
  def failure_message
181
203
  return not_given_a_block_failure unless Proc === @event_proc
182
- return before_value_failure unless matches_before?
204
+ return before_value_failure unless @matches_before
183
205
  return did_not_change_failure unless @change_details.changed?
184
206
  after_value_failure
185
207
  end
@@ -189,10 +211,24 @@ module RSpec
189
211
  true
190
212
  end
191
213
 
214
+ # @private
215
+ def supports_value_expectations?
216
+ false
217
+ end
218
+
192
219
  private
193
220
 
194
- def matches_before?
195
- values_match?(@expected_before, @change_details.actual_before)
221
+ def perform_change(event_proc)
222
+ @event_proc = event_proc
223
+ @change_details.perform_change(event_proc) do |actual_before|
224
+ # pre-compute values derived from the `before` value before the
225
+ # mutation is applied, in case the specified mutation is mutation
226
+ # of a single object (rather than a changing what object a method
227
+ # returns). We need to cache these values before the `before` value
228
+ # they are based on potentially gets mutated.
229
+ @matches_before = values_match?(@expected_before, actual_before)
230
+ @actual_before_description = description_of(actual_before)
231
+ end
196
232
  end
197
233
 
198
234
  def matches_after?
@@ -200,23 +236,31 @@ module RSpec
200
236
  end
201
237
 
202
238
  def before_value_failure
203
- "expected #{@change_details.message} to have initially been #{description_of @expected_before}, but was #{description_of @change_details.actual_before}"
239
+ "expected #{@change_details.value_representation} " \
240
+ "to have initially been #{description_of @expected_before}, " \
241
+ "but was #{@actual_before_description}"
204
242
  end
205
243
 
206
244
  def after_value_failure
207
- "expected #{@change_details.message} to have changed to #{description_of @expected_after}, but is now #{description_of @change_details.actual_after}"
245
+ "expected #{@change_details.value_representation} " \
246
+ "to have changed to #{description_of @expected_after}, " \
247
+ "but is now #{description_of @change_details.actual_after}"
208
248
  end
209
249
 
210
250
  def did_not_change_failure
211
- "expected #{@change_details.message} to have changed #{change_description}, but did not change"
251
+ "expected #{@change_details.value_representation} " \
252
+ "to have changed #{change_description}, but did not change"
212
253
  end
213
254
 
214
255
  def did_change_failure
215
- "expected #{@change_details.message} not to have changed, but did change from #{description_of @change_details.actual_before} to #{description_of @change_details.actual_after}"
256
+ "expected #{@change_details.value_representation} not to have changed, but " \
257
+ "did change from #{@actual_before_description} " \
258
+ "to #{description_of @change_details.actual_after}"
216
259
  end
217
260
 
218
261
  def not_given_a_block_failure
219
- "expected #{@change_details.message} to have changed #{change_description}, but was not given a block"
262
+ "expected #{@change_details.value_representation} to have changed " \
263
+ "#{change_description}, but was not given a block"
220
264
  end
221
265
  end
222
266
 
@@ -240,19 +284,17 @@ module RSpec
240
284
  # @private
241
285
  def does_not_match?(event_proc)
242
286
  if @description_suffix
243
- raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
287
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
288
+ "is not supported"
244
289
  end
245
290
 
246
- @event_proc = event_proc
247
- return false unless Proc === event_proc
248
- @change_details.perform_change(event_proc)
249
- !@change_details.changed? && matches_before?
291
+ perform_change(event_proc) && !@change_details.changed? && @matches_before
250
292
  end
251
293
 
252
294
  # @private
253
295
  def failure_message_when_negated
254
296
  return not_given_a_block_failure unless Proc === @event_proc
255
- return before_value_failure unless matches_before?
297
+ return before_value_failure unless @matches_before
256
298
  did_change_failure
257
299
  end
258
300
 
@@ -282,7 +324,8 @@ module RSpec
282
324
 
283
325
  # @private
284
326
  def does_not_match?(_event_proc)
285
- raise NotImplementedError, "`expect { }.not_to change { }.to()` is not supported"
327
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
328
+ "is not supported"
286
329
  end
287
330
 
288
331
  private
@@ -294,10 +337,24 @@ module RSpec
294
337
 
295
338
  # @private
296
339
  # Encapsulates the details of the before/after values.
340
+ #
341
+ # Note that this class exposes the `actual_after` value, to allow the
342
+ # matchers above to derive failure messages, etc from the value on demand
343
+ # as needed, but it intentionally does _not_ expose the `actual_before`
344
+ # value. Some usages of the `change` matcher mutate a specific object
345
+ # returned by the value proc, which means that failure message snippets,
346
+ # etc, which are derived from the `before` value may not be accurate if
347
+ # they are lazily computed as needed. We must pre-compute them before
348
+ # applying the change in the `expect` block. To ensure that all `change`
349
+ # matchers do that properly, we do not expose the `actual_before` value.
350
+ # Instead, matchers must pass a block to `perform_change`, which yields
351
+ # the `actual_before` value before applying the change.
297
352
  class ChangeDetails
298
- attr_reader :message, :actual_before, :actual_after
353
+ attr_reader :actual_after
299
354
 
300
- def initialize(receiver=nil, message=nil, &block)
355
+ UNDEFINED = Module.new.freeze
356
+
357
+ def initialize(matcher_name, receiver=nil, message=nil, &block)
301
358
  if receiver && !message
302
359
  raise(
303
360
  ArgumentError,
@@ -306,18 +363,56 @@ module RSpec
306
363
  "You passed an object but no message."
307
364
  )
308
365
  end
309
- @message = message ? "##{message}" : "result"
310
- @value_proc = block || lambda { receiver.__send__(message) }
366
+
367
+ @matcher_name = matcher_name
368
+ @receiver = receiver
369
+ @message = message
370
+ @value_proc = block
371
+ # TODO: temporary measure to mute warning of access to an initialized
372
+ # instance variable when a deprecated implicit block expectation
373
+ # syntax is used. This may be removed once `fail` is used, and the
374
+ # matcher never issues this warning.
375
+ @actual_after = UNDEFINED
376
+ end
377
+
378
+ def value_representation
379
+ @value_representation ||=
380
+ if @message
381
+ "`#{message_notation(@receiver, @message)}`"
382
+ elsif (value_block_snippet = extract_value_block_snippet)
383
+ "`#{value_block_snippet}`"
384
+ else
385
+ 'result'
386
+ end
311
387
  end
312
388
 
313
389
  def perform_change(event_proc)
314
390
  @actual_before = evaluate_value_proc
391
+ @before_hash = @actual_before.hash
392
+ yield @actual_before if block_given?
393
+
394
+ return false unless Proc === event_proc
315
395
  event_proc.call
396
+
316
397
  @actual_after = evaluate_value_proc
398
+ @actual_hash = @actual_after.hash
399
+ true
317
400
  end
318
401
 
319
402
  def changed?
320
- @actual_before != @actual_after
403
+ # Consider it changed if either:
404
+ #
405
+ # - The before/after values are unequal
406
+ # - The before/after values have different hash values
407
+ #
408
+ # The latter case specifically handles the case when the value proc
409
+ # returns the exact same object, but it has been mutated.
410
+ #
411
+ # Note that it is not sufficient to only check the hashes; it is
412
+ # possible for two values to be unequal (and of different classes)
413
+ # but to return the same hash value. Also, some objects may change
414
+ # their hash after being compared with `==`/`!=`.
415
+ @actual_before != @actual_after || @before_hash != @actual_hash
321
416
  end
322
417
 
323
418
  def actual_delta
@@ -327,13 +422,26 @@ module RSpec
327
422
  private
328
423
 
329
424
  def evaluate_value_proc
330
- case val = @value_proc.call
331
- when IO # enumerable, but we don't want to dup it.
332
- val
333
- when Enumerable, String
334
- val.dup
425
+ @value_proc ? @value_proc.call : @receiver.__send__(@message)
426
+ end
427
+
428
+ def message_notation(receiver, message)
429
+ case receiver
430
+ when Module
431
+ "#{receiver}.#{message}"
335
432
  else
336
- val
433
+ "#{Support.class_of(receiver)}##{message}"
434
+ end
435
+ end
436
+
437
+ if RSpec::Support::RubyFeatures.ripper_supported?
438
+ def extract_value_block_snippet
439
+ return nil unless @value_proc
440
+ Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name)
441
+ end
442
+ else
443
+ def extract_value_block_snippet
444
+ nil
337
445
  end
338
446
  end
339
447
  end