rspec-expectations 3.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +5 -0
  4. data/.document +5 -0
  5. data/.yardopts +6 -0
  6. data/Changelog.md +1156 -0
  7. data/LICENSE.md +25 -0
  8. data/README.md +305 -0
  9. data/lib/rspec/expectations.rb +82 -0
  10. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  11. data/lib/rspec/expectations/configuration.rb +215 -0
  12. data/lib/rspec/expectations/expectation_target.rb +127 -0
  13. data/lib/rspec/expectations/fail_with.rb +39 -0
  14. data/lib/rspec/expectations/failure_aggregator.rb +194 -0
  15. data/lib/rspec/expectations/handler.rb +170 -0
  16. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  17. data/lib/rspec/expectations/syntax.rb +132 -0
  18. data/lib/rspec/expectations/version.rb +8 -0
  19. data/lib/rspec/matchers.rb +1034 -0
  20. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  21. data/lib/rspec/matchers/built_in.rb +52 -0
  22. data/lib/rspec/matchers/built_in/all.rb +86 -0
  23. data/lib/rspec/matchers/built_in/base_matcher.rb +193 -0
  24. data/lib/rspec/matchers/built_in/be.rb +288 -0
  25. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  26. data/lib/rspec/matchers/built_in/be_instance_of.rb +26 -0
  27. data/lib/rspec/matchers/built_in/be_kind_of.rb +20 -0
  28. data/lib/rspec/matchers/built_in/be_within.rb +72 -0
  29. data/lib/rspec/matchers/built_in/change.rb +428 -0
  30. data/lib/rspec/matchers/built_in/compound.rb +271 -0
  31. data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
  32. data/lib/rspec/matchers/built_in/cover.rb +24 -0
  33. data/lib/rspec/matchers/built_in/eq.rb +40 -0
  34. data/lib/rspec/matchers/built_in/eql.rb +34 -0
  35. data/lib/rspec/matchers/built_in/equal.rb +81 -0
  36. data/lib/rspec/matchers/built_in/exist.rb +90 -0
  37. data/lib/rspec/matchers/built_in/has.rb +103 -0
  38. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  39. data/lib/rspec/matchers/built_in/include.rb +149 -0
  40. data/lib/rspec/matchers/built_in/match.rb +106 -0
  41. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  42. data/lib/rspec/matchers/built_in/output.rb +200 -0
  43. data/lib/rspec/matchers/built_in/raise_error.rb +230 -0
  44. data/lib/rspec/matchers/built_in/respond_to.rb +165 -0
  45. data/lib/rspec/matchers/built_in/satisfy.rb +60 -0
  46. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  47. data/lib/rspec/matchers/built_in/throw_symbol.rb +132 -0
  48. data/lib/rspec/matchers/built_in/yield.rb +432 -0
  49. data/lib/rspec/matchers/composable.rb +171 -0
  50. data/lib/rspec/matchers/dsl.rb +527 -0
  51. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  52. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +73 -0
  53. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  54. data/lib/rspec/matchers/generated_descriptions.rb +41 -0
  55. data/lib/rspec/matchers/matcher_delegator.rb +35 -0
  56. data/lib/rspec/matchers/matcher_protocol.rb +99 -0
  57. metadata +215 -0
  58. metadata.gz.sig +0 -0
@@ -0,0 +1,428 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `change`.
6
+ # Not intended to be instantiated directly.
7
+ class Change < BaseMatcher
8
+ # @api public
9
+ # Specifies the delta of the expected change.
10
+ def by(expected_delta)
11
+ ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta|
12
+ values_match?(expected_delta, actual_delta)
13
+ end
14
+ end
15
+
16
+ # @api public
17
+ # Specifies a minimum delta of the expected change.
18
+ def by_at_least(minimum)
19
+ ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta|
20
+ actual_delta >= minimum
21
+ end
22
+ end
23
+
24
+ # @api public
25
+ # Specifies a maximum delta of the expected change.
26
+ def by_at_most(maximum)
27
+ ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta|
28
+ actual_delta <= maximum
29
+ end
30
+ end
31
+
32
+ # @api public
33
+ # Specifies the new value you expect.
34
+ def to(value)
35
+ ChangeToValue.new(change_details, value)
36
+ end
37
+
38
+ # @api public
39
+ # Specifies the original value.
40
+ def from(value)
41
+ ChangeFromValue.new(change_details, value)
42
+ end
43
+
44
+ # @private
45
+ def matches?(event_proc)
46
+ raise_block_syntax_error if block_given?
47
+ perform_change(event_proc) && change_details.changed?
48
+ end
49
+
50
+ def does_not_match?(event_proc)
51
+ raise_block_syntax_error if block_given?
52
+ perform_change(event_proc) && !change_details.changed?
53
+ end
54
+
55
+ # @api private
56
+ # @return [String]
57
+ def failure_message
58
+ "expected #{change_details.value_representation} to have changed, " \
59
+ "but #{positive_failure_reason}"
60
+ end
61
+
62
+ # @api private
63
+ # @return [String]
64
+ def failure_message_when_negated
65
+ "expected #{change_details.value_representation} not to have changed, " \
66
+ "but #{negative_failure_reason}"
67
+ end
68
+
69
+ # @api private
70
+ # @return [String]
71
+ def description
72
+ "change #{change_details.value_representation}"
73
+ end
74
+
75
+ # @private
76
+ def supports_block_expectations?
77
+ true
78
+ end
79
+
80
+ private
81
+
82
+ def initialize(receiver=nil, message=nil, &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
102
+ end
103
+
104
+ def raise_block_syntax_error
105
+ raise SyntaxError, "Block not received by the `change` matcher. " \
106
+ "Perhaps you want to use `{ ... }` instead of do/end?"
107
+ end
108
+
109
+ def positive_failure_reason
110
+ return "was not given a block" unless Proc === @event_proc
111
+ "is still #{@actual_before_description}"
112
+ end
113
+
114
+ def negative_failure_reason
115
+ return "was not given a block" unless Proc === @event_proc
116
+ "did change from #{@actual_before_description} " \
117
+ "to #{description_of change_details.actual_after}"
118
+ end
119
+ end
120
+
121
+ # Used to specify a relative change.
122
+ # @api private
123
+ class ChangeRelatively < BaseMatcher
124
+ def initialize(change_details, expected_delta, relativity, &comparer)
125
+ @change_details = change_details
126
+ @expected_delta = expected_delta
127
+ @relativity = relativity
128
+ @comparer = comparer
129
+ end
130
+
131
+ # @private
132
+ def failure_message
133
+ "expected #{@change_details.value_representation} to have changed " \
134
+ "#{@relativity.to_s.tr('_', ' ')} " \
135
+ "#{description_of @expected_delta}, but #{failure_reason}"
136
+ end
137
+
138
+ # @private
139
+ def matches?(event_proc)
140
+ @event_proc = event_proc
141
+ @change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta)
142
+ end
143
+
144
+ # @private
145
+ def does_not_match?(_event_proc)
146
+ raise NotImplementedError, "`expect { }.not_to change " \
147
+ "{ }.#{@relativity}()` is not supported"
148
+ end
149
+
150
+ # @private
151
+ def description
152
+ "change #{@change_details.value_representation} " \
153
+ "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
154
+ end
155
+
156
+ # @private
157
+ def supports_block_expectations?
158
+ true
159
+ end
160
+
161
+ private
162
+
163
+ def failure_reason
164
+ return "was not given a block" unless Proc === @event_proc
165
+ "was changed by #{description_of @change_details.actual_delta}"
166
+ end
167
+ end
168
+
169
+ # @api private
170
+ # Base class for specifying a change from and/or to specific values.
171
+ class SpecificValuesChange < BaseMatcher
172
+ # @private
173
+ MATCH_ANYTHING = ::Object.ancestors.last
174
+
175
+ def initialize(change_details, from, to)
176
+ @change_details = change_details
177
+ @expected_before = from
178
+ @expected_after = to
179
+ end
180
+
181
+ # @private
182
+ def matches?(event_proc)
183
+ perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after?
184
+ end
185
+
186
+ # @private
187
+ def description
188
+ "change #{@change_details.value_representation} #{change_description}"
189
+ end
190
+
191
+ # @private
192
+ def failure_message
193
+ return not_given_a_block_failure unless Proc === @event_proc
194
+ return before_value_failure unless @matches_before
195
+ return did_not_change_failure unless @change_details.changed?
196
+ after_value_failure
197
+ end
198
+
199
+ # @private
200
+ def supports_block_expectations?
201
+ true
202
+ end
203
+
204
+ private
205
+
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
217
+ end
218
+
219
+ def matches_after?
220
+ values_match?(@expected_after, @change_details.actual_after)
221
+ end
222
+
223
+ def before_value_failure
224
+ "expected #{@change_details.value_representation} " \
225
+ "to have initially been #{description_of @expected_before}, " \
226
+ "but was #{@actual_before_description}"
227
+ end
228
+
229
+ def after_value_failure
230
+ "expected #{@change_details.value_representation} " \
231
+ "to have changed to #{description_of @expected_after}, " \
232
+ "but is now #{description_of @change_details.actual_after}"
233
+ end
234
+
235
+ def did_not_change_failure
236
+ "expected #{@change_details.value_representation} " \
237
+ "to have changed #{change_description}, but did not change"
238
+ end
239
+
240
+ def did_change_failure
241
+ "expected #{@change_details.value_representation} not to have changed, but " \
242
+ "did change from #{@actual_before_description} " \
243
+ "to #{description_of @change_details.actual_after}"
244
+ end
245
+
246
+ def not_given_a_block_failure
247
+ "expected #{@change_details.value_representation} to have changed " \
248
+ "#{change_description}, but was not given a block"
249
+ end
250
+ end
251
+
252
+ # @api private
253
+ # Used to specify a change from a specific value
254
+ # (and, optionally, to a specific value).
255
+ class ChangeFromValue < SpecificValuesChange
256
+ def initialize(change_details, expected_before)
257
+ @description_suffix = nil
258
+ super(change_details, expected_before, MATCH_ANYTHING)
259
+ end
260
+
261
+ # @api public
262
+ # Specifies the new value you expect.
263
+ def to(value)
264
+ @expected_after = value
265
+ @description_suffix = " to #{description_of value}"
266
+ self
267
+ end
268
+
269
+ # @private
270
+ def does_not_match?(event_proc)
271
+ if @description_suffix
272
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
273
+ "is not supported"
274
+ end
275
+
276
+ perform_change(event_proc) && !@change_details.changed? && @matches_before
277
+ end
278
+
279
+ # @private
280
+ def failure_message_when_negated
281
+ return not_given_a_block_failure unless Proc === @event_proc
282
+ return before_value_failure unless @matches_before
283
+ did_change_failure
284
+ end
285
+
286
+ private
287
+
288
+ def change_description
289
+ "from #{description_of @expected_before}#{@description_suffix}"
290
+ end
291
+ end
292
+
293
+ # @api private
294
+ # Used to specify a change to a specific value
295
+ # (and, optionally, from a specific value).
296
+ class ChangeToValue < SpecificValuesChange
297
+ def initialize(change_details, expected_after)
298
+ @description_suffix = nil
299
+ super(change_details, MATCH_ANYTHING, expected_after)
300
+ end
301
+
302
+ # @api public
303
+ # Specifies the original value.
304
+ def from(value)
305
+ @expected_before = value
306
+ @description_suffix = " from #{description_of value}"
307
+ self
308
+ end
309
+
310
+ # @private
311
+ def does_not_match?(_event_proc)
312
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
313
+ "is not supported"
314
+ end
315
+
316
+ private
317
+
318
+ def change_description
319
+ "to #{description_of @expected_after}#{@description_suffix}"
320
+ end
321
+ end
322
+
323
+ # @private
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.
337
+ class ChangeDetails
338
+ attr_reader :actual_after
339
+
340
+ def initialize(matcher_name, receiver=nil, message=nil, &block)
341
+ if receiver && !message
342
+ raise(
343
+ ArgumentError,
344
+ "`change` requires either an object and message " \
345
+ "(`change(obj, :msg)`) or a block (`change { }`). " \
346
+ "You passed an object but no message."
347
+ )
348
+ end
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
365
+ end
366
+
367
+ def perform_change(event_proc)
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
373
+ event_proc.call
374
+
375
+ @actual_after = evaluate_value_proc
376
+ @actual_hash = @actual_after.hash
377
+ true
378
+ end
379
+
380
+ def changed?
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
394
+ end
395
+
396
+ def actual_delta
397
+ @actual_after - @actual_before
398
+ end
399
+
400
+ private
401
+
402
+ def evaluate_value_proc
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}"
410
+ else
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
423
+ end
424
+ end
425
+ end
426
+ end
427
+ end
428
+ end
@@ -0,0 +1,271 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Base class for `and` and `or` compound matchers.
6
+ class Compound < BaseMatcher
7
+ # @private
8
+ attr_reader :matcher_1, :matcher_2, :evaluator
9
+
10
+ def initialize(matcher_1, matcher_2)
11
+ @matcher_1 = matcher_1
12
+ @matcher_2 = matcher_2
13
+ end
14
+
15
+ # @private
16
+ def does_not_match?(_actual)
17
+ raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \
18
+ "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \
19
+ "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \
20
+ "use `expect(...).to matcher.#{conjunction} matcher`."
21
+ end
22
+
23
+ # @api private
24
+ # @return [String]
25
+ def description
26
+ "#{matcher_1.description} #{conjunction} #{matcher_2.description}"
27
+ end
28
+
29
+ def supports_block_expectations?
30
+ matcher_supports_block_expectations?(matcher_1) &&
31
+ matcher_supports_block_expectations?(matcher_2)
32
+ end
33
+
34
+ def expects_call_stack_jump?
35
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
36
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
37
+ end
38
+
39
+ # @api private
40
+ # @return [Boolean]
41
+ def diffable?
42
+ matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2)
43
+ end
44
+
45
+ # @api private
46
+ # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
47
+ def expected
48
+ return nil unless evaluator
49
+ ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
50
+ end
51
+
52
+ protected
53
+
54
+ def diffable_matcher_list
55
+ list = []
56
+ list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches?
57
+ list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches?
58
+ list
59
+ end
60
+
61
+ private
62
+
63
+ def initialize_copy(other)
64
+ @matcher_1 = @matcher_1.clone
65
+ @matcher_2 = @matcher_2.clone
66
+ super
67
+ end
68
+
69
+ def match(_expected, actual)
70
+ evaluator_klass = if supports_block_expectations? && Proc === actual
71
+ NestedEvaluator
72
+ else
73
+ SequentialEvaluator
74
+ end
75
+
76
+ @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2)
77
+ end
78
+
79
+ def indent_multiline_message(message)
80
+ message.lines.map do |line|
81
+ line =~ /\S/ ? ' ' + line : line
82
+ end.join
83
+ end
84
+
85
+ def compound_failure_message
86
+ "#{indent_multiline_message(matcher_1.failure_message.sub(/\n+\z/, ''))}" \
87
+ "\n\n...#{conjunction}:" \
88
+ "\n\n#{indent_multiline_message(matcher_2.failure_message.sub(/\A\n+/, ''))}"
89
+ end
90
+
91
+ def matcher_1_matches?
92
+ evaluator.matcher_matches?(matcher_1)
93
+ end
94
+
95
+ def matcher_2_matches?
96
+ evaluator.matcher_matches?(matcher_2)
97
+ end
98
+
99
+ def matcher_supports_block_expectations?(matcher)
100
+ matcher.supports_block_expectations?
101
+ rescue NoMethodError
102
+ false
103
+ end
104
+
105
+ def matcher_is_diffable?(matcher)
106
+ matcher.diffable?
107
+ rescue NoMethodError
108
+ false
109
+ end
110
+
111
+ def diffable_matcher_list_for(matcher)
112
+ return [] unless matcher_is_diffable?(matcher)
113
+ return matcher.diffable_matcher_list if Compound === matcher
114
+ [matcher]
115
+ end
116
+
117
+ # For value expectations, we can evaluate the matchers sequentially.
118
+ class SequentialEvaluator
119
+ def initialize(actual, *)
120
+ @actual = actual
121
+ end
122
+
123
+ def matcher_matches?(matcher)
124
+ matcher.matches?(@actual)
125
+ end
126
+ end
127
+
128
+ # Normally, we evaluate the matching sequentially. For an expression like
129
+ # `expect(x).to foo.and bar`, this becomes:
130
+ #
131
+ # expect(x).to foo
132
+ # expect(x).to bar
133
+ #
134
+ # For block expectations, we need to nest them instead, so that
135
+ # `expect { x }.to foo.and bar` becomes:
136
+ #
137
+ # expect {
138
+ # expect { x }.to foo
139
+ # }.to bar
140
+ #
141
+ # This is necessary so that the `expect` block is only executed once.
142
+ class NestedEvaluator
143
+ def initialize(actual, matcher_1, matcher_2)
144
+ @actual = actual
145
+ @matcher_1 = matcher_1
146
+ @matcher_2 = matcher_2
147
+ @match_results = {}
148
+
149
+ inner, outer = order_block_matchers
150
+
151
+ @match_results[outer] = outer.matches?(Proc.new do |*args|
152
+ @match_results[inner] = inner.matches?(inner_matcher_block(args))
153
+ end)
154
+ end
155
+
156
+ def matcher_matches?(matcher)
157
+ @match_results.fetch(matcher)
158
+ end
159
+
160
+ private
161
+
162
+ # Some block matchers (such as `yield_xyz`) pass args to the `expect` block.
163
+ # When such a matcher is used as the outer matcher, we need to forward the
164
+ # the args on to the `expect` block.
165
+ def inner_matcher_block(outer_args)
166
+ return @actual if outer_args.empty?
167
+
168
+ Proc.new do |*inner_args|
169
+ unless inner_args.empty?
170
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
171
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
172
+ "since both matchers pass arguments to the block."
173
+ end
174
+
175
+ @actual.call(*outer_args)
176
+ end
177
+ end
178
+
179
+ # For a matcher like `raise_error` or `throw_symbol`, where the block will jump
180
+ # up the call stack, we need to order things so that it is the inner matcher.
181
+ # For example, we need it to be this:
182
+ #
183
+ # expect {
184
+ # expect {
185
+ # x += 1
186
+ # raise "boom"
187
+ # }.to raise_error("boom")
188
+ # }.to change { x }.by(1)
189
+ #
190
+ # ...rather than:
191
+ #
192
+ # expect {
193
+ # expect {
194
+ # x += 1
195
+ # raise "boom"
196
+ # }.to change { x }.by(1)
197
+ # }.to raise_error("boom")
198
+ #
199
+ # In the latter case, the after-block logic in the `change` matcher would never
200
+ # get executed because the `raise "boom"` line would jump to the `rescue` in the
201
+ # `raise_error` logic, so only the former case will work properly.
202
+ #
203
+ # This method figures out which matcher should be the inner matcher and which
204
+ # should be the outer matcher.
205
+ def order_block_matchers
206
+ return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2)
207
+ return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1)
208
+
209
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
210
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
211
+ "because they both expect a call stack jump."
212
+ end
213
+
214
+ def self.matcher_expects_call_stack_jump?(matcher)
215
+ matcher.expects_call_stack_jump?
216
+ rescue NoMethodError
217
+ false
218
+ end
219
+ end
220
+
221
+ # @api public
222
+ # Matcher used to represent a compound `and` expectation.
223
+ class And < self
224
+ # @api private
225
+ # @return [String]
226
+ def failure_message
227
+ if matcher_1_matches?
228
+ matcher_2.failure_message
229
+ elsif matcher_2_matches?
230
+ matcher_1.failure_message
231
+ else
232
+ compound_failure_message
233
+ end
234
+ end
235
+
236
+ private
237
+
238
+ def match(*)
239
+ super
240
+ matcher_1_matches? && matcher_2_matches?
241
+ end
242
+
243
+ def conjunction
244
+ "and"
245
+ end
246
+ end
247
+
248
+ # @api public
249
+ # Matcher used to represent a compound `or` expectation.
250
+ class Or < self
251
+ # @api private
252
+ # @return [String]
253
+ def failure_message
254
+ compound_failure_message
255
+ end
256
+
257
+ private
258
+
259
+ def match(*)
260
+ super
261
+ matcher_1_matches? || matcher_2_matches?
262
+ end
263
+
264
+ def conjunction
265
+ "or"
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end