rspec-expectations 2.11.3 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +1026 -21
  6. data/{License.txt → LICENSE.md} +5 -3
  7. data/README.md +174 -78
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +230 -0
  10. data/lib/rspec/expectations/expectation_target.rb +130 -55
  11. data/lib/rspec/expectations/fail_with.rb +17 -33
  12. data/lib/rspec/expectations/failure_aggregator.rb +212 -0
  13. data/lib/rspec/expectations/handler.rb +163 -29
  14. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  15. data/lib/rspec/expectations/syntax.rb +68 -54
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +59 -24
  18. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  19. data/lib/rspec/matchers/built_in/all.rb +86 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +150 -20
  21. data/lib/rspec/matchers/built_in/be.rb +115 -109
  22. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +16 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +10 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +43 -17
  26. data/lib/rspec/matchers/built_in/change.rb +392 -75
  27. data/lib/rspec/matchers/built_in/compound.rb +290 -0
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/cover.rb +3 -0
  31. data/lib/rspec/matchers/built_in/eq.rb +26 -8
  32. data/lib/rspec/matchers/built_in/eql.rb +19 -8
  33. data/lib/rspec/matchers/built_in/equal.rb +56 -19
  34. data/lib/rspec/matchers/built_in/exist.rb +74 -10
  35. data/lib/rspec/matchers/built_in/has.rb +141 -22
  36. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  37. data/lib/rspec/matchers/built_in/include.rb +175 -20
  38. data/lib/rspec/matchers/built_in/match.rb +95 -1
  39. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  40. data/lib/rspec/matchers/built_in/output.rb +207 -0
  41. data/lib/rspec/matchers/built_in/raise_error.rb +212 -38
  42. data/lib/rspec/matchers/built_in/respond_to.rb +155 -29
  43. data/lib/rspec/matchers/built_in/satisfy.rb +39 -9
  44. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  45. data/lib/rspec/matchers/built_in/throw_symbol.rb +58 -14
  46. data/lib/rspec/matchers/built_in/yield.rb +252 -98
  47. data/lib/rspec/matchers/built_in.rb +47 -33
  48. data/lib/rspec/matchers/composable.rb +171 -0
  49. data/lib/rspec/matchers/dsl.rb +530 -10
  50. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  51. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  52. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  53. data/lib/rspec/matchers/generated_descriptions.rb +15 -10
  54. data/lib/rspec/matchers/matcher_delegator.rb +35 -0
  55. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  56. data/lib/rspec/matchers.rb +604 -252
  57. data.tar.gz.sig +0 -0
  58. metadata +178 -278
  59. metadata.gz.sig +0 -0
  60. data/features/README.md +0 -49
  61. data/features/Upgrade.md +0 -53
  62. data/features/built_in_matchers/README.md +0 -90
  63. data/features/built_in_matchers/be.feature +0 -173
  64. data/features/built_in_matchers/be_within.feature +0 -46
  65. data/features/built_in_matchers/cover.feature +0 -45
  66. data/features/built_in_matchers/end_with.feature +0 -46
  67. data/features/built_in_matchers/equality.feature +0 -145
  68. data/features/built_in_matchers/exist.feature +0 -43
  69. data/features/built_in_matchers/expect_change.feature +0 -59
  70. data/features/built_in_matchers/expect_error.feature +0 -138
  71. data/features/built_in_matchers/have.feature +0 -103
  72. data/features/built_in_matchers/include.feature +0 -121
  73. data/features/built_in_matchers/match.feature +0 -50
  74. data/features/built_in_matchers/operators.feature +0 -221
  75. data/features/built_in_matchers/predicates.feature +0 -128
  76. data/features/built_in_matchers/respond_to.feature +0 -78
  77. data/features/built_in_matchers/satisfy.feature +0 -31
  78. data/features/built_in_matchers/start_with.feature +0 -46
  79. data/features/built_in_matchers/throw_symbol.feature +0 -85
  80. data/features/built_in_matchers/types.feature +0 -114
  81. data/features/built_in_matchers/yield.feature +0 -146
  82. data/features/custom_matchers/access_running_example.feature +0 -53
  83. data/features/custom_matchers/define_diffable_matcher.feature +0 -27
  84. data/features/custom_matchers/define_matcher.feature +0 -340
  85. data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
  86. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
  87. data/features/customized_message.feature +0 -22
  88. data/features/diffing.feature +0 -85
  89. data/features/implicit_docstrings.feature +0 -52
  90. data/features/step_definitions/additional_cli_steps.rb +0 -22
  91. data/features/support/env.rb +0 -5
  92. data/features/syntax_configuration.feature +0 -68
  93. data/features/test_frameworks/test_unit.feature +0 -46
  94. data/lib/rspec/expectations/deprecation.rb +0 -38
  95. data/lib/rspec/expectations/differ.rb +0 -81
  96. data/lib/rspec/expectations/errors.rb +0 -9
  97. data/lib/rspec/expectations/extensions/array.rb +0 -9
  98. data/lib/rspec/expectations/extensions/object.rb +0 -39
  99. data/lib/rspec/expectations/extensions.rb +0 -2
  100. data/lib/rspec/matchers/be_close.rb +0 -9
  101. data/lib/rspec/matchers/built_in/have.rb +0 -108
  102. data/lib/rspec/matchers/built_in/match_array.rb +0 -45
  103. data/lib/rspec/matchers/built_in/start_and_end_with.rb +0 -48
  104. data/lib/rspec/matchers/compatibility.rb +0 -14
  105. data/lib/rspec/matchers/configuration.rb +0 -66
  106. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  107. data/lib/rspec/matchers/matcher.rb +0 -299
  108. data/lib/rspec/matchers/method_missing.rb +0 -12
  109. data/lib/rspec/matchers/operator_matcher.rb +0 -84
  110. data/lib/rspec/matchers/pretty.rb +0 -60
  111. data/lib/rspec-expectations.rb +0 -1
  112. data/spec/rspec/expectations/differ_spec.rb +0 -153
  113. data/spec/rspec/expectations/expectation_target_spec.rb +0 -65
  114. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  115. data/spec/rspec/expectations/fail_with_spec.rb +0 -70
  116. data/spec/rspec/expectations/handler_spec.rb +0 -206
  117. data/spec/rspec/matchers/base_matcher_spec.rb +0 -60
  118. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  119. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -40
  120. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -37
  121. data/spec/rspec/matchers/be_spec.rb +0 -452
  122. data/spec/rspec/matchers/be_within_spec.rb +0 -80
  123. data/spec/rspec/matchers/change_spec.rb +0 -528
  124. data/spec/rspec/matchers/configuration_spec.rb +0 -202
  125. data/spec/rspec/matchers/cover_spec.rb +0 -69
  126. data/spec/rspec/matchers/description_generation_spec.rb +0 -176
  127. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  128. data/spec/rspec/matchers/eq_spec.rb +0 -54
  129. data/spec/rspec/matchers/eql_spec.rb +0 -41
  130. data/spec/rspec/matchers/equal_spec.rb +0 -60
  131. data/spec/rspec/matchers/exist_spec.rb +0 -110
  132. data/spec/rspec/matchers/has_spec.rb +0 -118
  133. data/spec/rspec/matchers/have_spec.rb +0 -461
  134. data/spec/rspec/matchers/include_spec.rb +0 -367
  135. data/spec/rspec/matchers/match_array_spec.rb +0 -124
  136. data/spec/rspec/matchers/match_spec.rb +0 -61
  137. data/spec/rspec/matchers/matcher_spec.rb +0 -434
  138. data/spec/rspec/matchers/matchers_spec.rb +0 -31
  139. data/spec/rspec/matchers/method_missing_spec.rb +0 -24
  140. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -221
  141. data/spec/rspec/matchers/raise_error_spec.rb +0 -344
  142. data/spec/rspec/matchers/respond_to_spec.rb +0 -295
  143. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  144. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -182
  145. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  146. data/spec/rspec/matchers/yield_spec.rb +0 -402
  147. data/spec/spec_helper.rb +0 -27
  148. data/spec/support/classes.rb +0 -56
  149. data/spec/support/in_sub_process.rb +0 -31
  150. data/spec/support/matchers.rb +0 -22
  151. data/spec/support/ruby_version.rb +0 -10
  152. data/spec/support/shared_examples.rb +0 -13
@@ -1,131 +1,448 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
- class Change
5
- def initialize(receiver=nil, message=nil, &block)
6
- @message = message
7
- @value_proc = block || lambda {receiver.__send__(message)}
8
- @expected_after = @expected_before = @minimum = @maximum = @expected_delta = nil
9
- @eval_before = @eval_after = false
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
10
14
  end
11
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
12
45
  def matches?(event_proc)
13
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
14
54
 
15
- @actual_before = evaluate_value_proc
16
- event_proc.call
17
- @actual_after = evaluate_value_proc
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
18
61
 
19
- (!change_expected? || changed?) && matches_before? && matches_after? && matches_expected_delta? && matches_min? && matches_max?
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}"
20
67
  end
21
- alias == matches?
22
68
 
23
- def raise_block_syntax_error
24
- raise SyntaxError.new(<<-MESSAGE)
25
- block passed to should or should_not change must use {} instead of do/end
26
- MESSAGE
69
+ # @api private
70
+ # @return [String]
71
+ def description
72
+ "change #{change_details.value_representation}"
27
73
  end
28
74
 
29
- def evaluate_value_proc
30
- case val = @value_proc.call
31
- when Enumerable
32
- val.dup
33
- else
34
- val
35
- end
75
+ # @private
76
+ def supports_block_expectations?
77
+ true
36
78
  end
37
79
 
38
- def failure_message_for_should
39
- if @eval_before && !expected_matches_actual?(@expected_before, @actual_before)
40
- "#{message} should have initially been #{@expected_before.inspect}, but was #{@actual_before.inspect}"
41
- elsif @eval_after && !expected_matches_actual?(@expected_after, @actual_after)
42
- "#{message} should have been changed to #{@expected_after.inspect}, but is now #{@actual_after.inspect}"
43
- elsif @expected_delta
44
- "#{message} should have been changed by #{@expected_delta.inspect}, but was changed by #{actual_delta.inspect}"
45
- elsif @minimum
46
- "#{message} should have been changed by at least #{@minimum.inspect}, but was changed by #{actual_delta.inspect}"
47
- elsif @maximum
48
- "#{message} should have been changed by at most #{@maximum.inspect}, but was changed by #{actual_delta.inspect}"
49
- else
50
- "#{message} should have changed, but is still #{@actual_before.inspect}"
80
+ # @private
81
+ def supports_value_expectations?
82
+ false
83
+ end
84
+
85
+ private
86
+
87
+ def initialize(receiver=nil, message=nil, &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)
51
106
  end
52
107
  end
53
108
 
54
- def actual_delta
55
- @actual_after - @actual_before
109
+ def raise_block_syntax_error
110
+ raise SyntaxError, "Block not received by the `change` matcher. " \
111
+ "Perhaps you want to use `{ ... }` instead of do/end?"
56
112
  end
57
113
 
58
- def failure_message_for_should_not
59
- "#{message} should not have changed, but did change from #{@actual_before.inspect} to #{@actual_after.inspect}"
114
+ def positive_failure_reason
115
+ return "was not given a block" unless Proc === @event_proc
116
+ "is still #{@actual_before_description}"
60
117
  end
61
118
 
62
- def by(expected_delta)
119
+ def negative_failure_reason
120
+ return "was not given a block" unless Proc === @event_proc
121
+ "did change from #{@actual_before_description} " \
122
+ "to #{description_of change_details.actual_after}"
123
+ end
124
+ end
125
+
126
+ # Used to specify a relative change.
127
+ # @api private
128
+ class ChangeRelatively < BaseMatcher
129
+ def initialize(change_details, expected_delta, relativity, &comparer)
130
+ @change_details = change_details
63
131
  @expected_delta = expected_delta
64
- self
132
+ @relativity = relativity
133
+ @comparer = comparer
65
134
  end
66
135
 
67
- def by_at_least(minimum)
68
- @minimum = minimum
69
- self
136
+ # @private
137
+ def failure_message
138
+ "expected #{@change_details.value_representation} to have changed " \
139
+ "#{@relativity.to_s.tr('_', ' ')} " \
140
+ "#{description_of @expected_delta}, but #{failure_reason}"
70
141
  end
71
142
 
72
- def by_at_most(maximum)
73
- @maximum = maximum
74
- self
75
- end
143
+ # @private
144
+ def matches?(event_proc)
145
+ @event_proc = event_proc
146
+ @change_details.perform_change(event_proc) && @comparer.call(@change_details.actual_delta)
147
+ end
148
+
149
+ # @private
150
+ def does_not_match?(_event_proc)
151
+ raise NotImplementedError, "`expect { }.not_to change " \
152
+ "{ }.#{@relativity}()` is not supported"
153
+ end
154
+
155
+ # @private
156
+ def description
157
+ "change #{@change_details.value_representation} " \
158
+ "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
159
+ end
160
+
161
+ # @private
162
+ def supports_block_expectations?
163
+ true
164
+ end
165
+
166
+ # @private
167
+ def supports_value_expectations?
168
+ false
169
+ end
170
+
171
+ private
172
+
173
+ def failure_reason
174
+ return "was not given a block" unless Proc === @event_proc
175
+ "was changed by #{description_of @change_details.actual_delta}"
176
+ end
177
+ end
178
+
179
+ # @api private
180
+ # Base class for specifying a change from and/or to specific values.
181
+ class SpecificValuesChange < BaseMatcher
182
+ # @private
183
+ MATCH_ANYTHING = ::Object.ancestors.last
184
+
185
+ def initialize(change_details, from, to)
186
+ @change_details = change_details
187
+ @expected_before = from
188
+ @expected_after = to
189
+ end
190
+
191
+ # @private
192
+ def matches?(event_proc)
193
+ perform_change(event_proc) && @change_details.changed? && @matches_before && matches_after?
194
+ end
195
+
196
+ # @private
197
+ def description
198
+ "change #{@change_details.value_representation} #{change_description}"
199
+ end
200
+
201
+ # @private
202
+ def failure_message
203
+ return not_given_a_block_failure unless Proc === @event_proc
204
+ return before_value_failure unless @matches_before
205
+ return did_not_change_failure unless @change_details.changed?
206
+ after_value_failure
207
+ end
208
+
209
+ # @private
210
+ def supports_block_expectations?
211
+ true
212
+ end
213
+
214
+ # @private
215
+ def supports_value_expectations?
216
+ false
217
+ end
218
+
219
+ private
220
+
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
232
+ end
76
233
 
77
- def to(to)
78
- @eval_after = true
79
- @expected_after = to
234
+ def matches_after?
235
+ values_match?(@expected_after, @change_details.actual_after)
236
+ end
237
+
238
+ def before_value_failure
239
+ "expected #{@change_details.value_representation} " \
240
+ "to have initially been #{description_of @expected_before}, " \
241
+ "but was #{@actual_before_description}"
242
+ end
243
+
244
+ def after_value_failure
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}"
248
+ end
249
+
250
+ def did_not_change_failure
251
+ "expected #{@change_details.value_representation} " \
252
+ "to have changed #{change_description}, but did not change"
253
+ end
254
+
255
+ def did_change_failure
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}"
259
+ end
260
+
261
+ def not_given_a_block_failure
262
+ "expected #{@change_details.value_representation} to have changed " \
263
+ "#{change_description}, but was not given a block"
264
+ end
265
+ end
266
+
267
+ # @api private
268
+ # Used to specify a change from a specific value
269
+ # (and, optionally, to a specific value).
270
+ class ChangeFromValue < SpecificValuesChange
271
+ def initialize(change_details, expected_before)
272
+ @description_suffix = nil
273
+ super(change_details, expected_before, MATCH_ANYTHING)
274
+ end
275
+
276
+ # @api public
277
+ # Specifies the new value you expect.
278
+ def to(value)
279
+ @expected_after = value
280
+ @description_suffix = " to #{description_of value}"
80
281
  self
81
282
  end
82
283
 
83
- def from (before)
84
- @eval_before = true
85
- @expected_before = before
284
+ # @private
285
+ def does_not_match?(event_proc)
286
+ if @description_suffix
287
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
288
+ "is not supported"
289
+ end
290
+
291
+ perform_change(event_proc) && !@change_details.changed? && @matches_before
292
+ end
293
+
294
+ # @private
295
+ def failure_message_when_negated
296
+ return not_given_a_block_failure unless Proc === @event_proc
297
+ return before_value_failure unless @matches_before
298
+ did_change_failure
299
+ end
300
+
301
+ private
302
+
303
+ def change_description
304
+ "from #{description_of @expected_before}#{@description_suffix}"
305
+ end
306
+ end
307
+
308
+ # @api private
309
+ # Used to specify a change to a specific value
310
+ # (and, optionally, from a specific value).
311
+ class ChangeToValue < SpecificValuesChange
312
+ def initialize(change_details, expected_after)
313
+ @description_suffix = nil
314
+ super(change_details, MATCH_ANYTHING, expected_after)
315
+ end
316
+
317
+ # @api public
318
+ # Specifies the original value.
319
+ def from(value)
320
+ @expected_before = value
321
+ @description_suffix = " from #{description_of value}"
86
322
  self
87
323
  end
88
324
 
89
- def description
90
- "change ##{message}"
325
+ # @private
326
+ def does_not_match?(_event_proc)
327
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
328
+ "is not supported"
91
329
  end
92
330
 
93
- private
331
+ private
94
332
 
95
- def message
96
- @message || "result"
333
+ def change_description
334
+ "to #{description_of @expected_after}#{@description_suffix}"
97
335
  end
336
+ end
337
+
338
+ # @private
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.
352
+ class ChangeDetails
353
+ attr_reader :actual_after
354
+
355
+ UNDEFINED = Module.new.freeze
356
+
357
+ def initialize(matcher_name, receiver=nil, message=nil, &block)
358
+ if receiver && !message
359
+ raise(
360
+ ArgumentError,
361
+ "`change` requires either an object and message " \
362
+ "(`change(obj, :msg)`) or a block (`change { }`). " \
363
+ "You passed an object but no message."
364
+ )
365
+ end
98
366
 
99
- def change_expected?
100
- @expected_delta != 0
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
101
376
  end
102
377
 
103
- def changed?
104
- @actual_before != @actual_after
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
105
387
  end
106
388
 
107
- def matches_before?
108
- @eval_before ? expected_matches_actual?(@expected_before, @actual_before) : true
389
+ def perform_change(event_proc)
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
395
+ event_proc.call
396
+
397
+ @actual_after = evaluate_value_proc
398
+ @actual_hash = @actual_after.hash
399
+ true
109
400
  end
110
401
 
111
- def matches_after?
112
- @eval_after ? expected_matches_actual?(@expected_after, @actual_after) : true
402
+ def changed?
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
113
416
  end
114
417
 
115
- def matches_expected_delta?
116
- @expected_delta ? (@actual_before + @expected_delta == @actual_after) : true
418
+ def actual_delta
419
+ @actual_after - @actual_before
117
420
  end
118
421
 
119
- def matches_min?
120
- @minimum ? (@actual_after - @actual_before >= @minimum) : true
422
+ private
423
+
424
+ def evaluate_value_proc
425
+ @value_proc ? @value_proc.call : @receiver.__send__(@message)
121
426
  end
122
427
 
123
- def matches_max?
124
- @maximum ? (@actual_after - @actual_before <= @maximum) : true
428
+ def message_notation(receiver, message)
429
+ case receiver
430
+ when Module
431
+ "#{receiver}.#{message}"
432
+ else
433
+ "#{Support.class_of(receiver)}##{message}"
434
+ end
125
435
  end
126
436
 
127
- def expected_matches_actual?(expected, actual)
128
- expected === actual
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
445
+ end
129
446
  end
130
447
  end
131
448
  end