rspec-expectations 2.11.3 → 3.11.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 (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