rspec-expectations 2.14.0 → 3.13.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 (155) 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 +976 -25
  6. data/{License.txt → LICENSE.md} +5 -3
  7. data/README.md +162 -26
  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 +127 -51
  11. data/lib/rspec/expectations/fail_with.rb +17 -57
  12. data/lib/rspec/expectations/failure_aggregator.rb +229 -0
  13. data/lib/rspec/expectations/handler.rb +146 -32
  14. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  15. data/lib/rspec/expectations/syntax.rb +68 -100
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +58 -23
  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 +191 -20
  21. data/lib/rspec/matchers/built_in/be.rb +114 -114
  22. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +15 -4
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +10 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +35 -18
  26. data/lib/rspec/matchers/built_in/change.rb +389 -80
  27. data/lib/rspec/matchers/built_in/compound.rb +290 -0
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +310 -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 +30 -8
  32. data/lib/rspec/matchers/built_in/eql.rb +23 -8
  33. data/lib/rspec/matchers/built_in/equal.rb +55 -22
  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 +184 -32
  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 +192 -44
  42. data/lib/rspec/matchers/built_in/respond_to.rb +154 -28
  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 +240 -161
  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 +531 -10
  50. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  51. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  52. data/lib/rspec/matchers/generated_descriptions.rb +14 -8
  53. data/lib/rspec/matchers/matcher_delegator.rb +61 -0
  54. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  55. data/lib/rspec/matchers/multi_matcher_diff.rb +82 -0
  56. data/lib/rspec/matchers.rb +520 -173
  57. data.tar.gz.sig +0 -0
  58. metadata +141 -242
  59. metadata.gz.sig +2 -0
  60. data/features/README.md +0 -48
  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 -175
  64. data/features/built_in_matchers/be_within.feature +0 -48
  65. data/features/built_in_matchers/cover.feature +0 -47
  66. data/features/built_in_matchers/end_with.feature +0 -48
  67. data/features/built_in_matchers/equality.feature +0 -139
  68. data/features/built_in_matchers/exist.feature +0 -45
  69. data/features/built_in_matchers/expect_change.feature +0 -59
  70. data/features/built_in_matchers/expect_error.feature +0 -144
  71. data/features/built_in_matchers/have.feature +0 -109
  72. data/features/built_in_matchers/include.feature +0 -174
  73. data/features/built_in_matchers/match.feature +0 -52
  74. data/features/built_in_matchers/operators.feature +0 -227
  75. data/features/built_in_matchers/predicates.feature +0 -137
  76. data/features/built_in_matchers/respond_to.feature +0 -84
  77. data/features/built_in_matchers/satisfy.feature +0 -33
  78. data/features/built_in_matchers/start_with.feature +0 -48
  79. data/features/built_in_matchers/throw_symbol.feature +0 -91
  80. data/features/built_in_matchers/types.feature +0 -116
  81. data/features/built_in_matchers/yield.feature +0 -161
  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 -368
  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 -14
  92. data/features/syntax_configuration.feature +0 -71
  93. data/features/test_frameworks/test_unit.feature +0 -44
  94. data/lib/rspec/expectations/deprecation.rb +0 -17
  95. data/lib/rspec/expectations/differ.rb +0 -133
  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 -29
  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 -124
  102. data/lib/rspec/matchers/built_in/match_array.rb +0 -51
  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 -108
  106. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  107. data/lib/rspec/matchers/matcher.rb +0 -300
  108. data/lib/rspec/matchers/method_missing.rb +0 -12
  109. data/lib/rspec/matchers/operator_matcher.rb +0 -109
  110. data/lib/rspec/matchers/pretty.rb +0 -70
  111. data/lib/rspec/matchers/test_unit_integration.rb +0 -11
  112. data/lib/rspec-expectations.rb +0 -1
  113. data/spec/rspec/expectations/differ_spec.rb +0 -192
  114. data/spec/rspec/expectations/expectation_target_spec.rb +0 -82
  115. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  116. data/spec/rspec/expectations/fail_with_spec.rb +0 -114
  117. data/spec/rspec/expectations/handler_spec.rb +0 -227
  118. data/spec/rspec/expectations/syntax_spec.rb +0 -139
  119. data/spec/rspec/matchers/base_matcher_spec.rb +0 -62
  120. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  121. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -63
  122. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -41
  123. data/spec/rspec/matchers/be_spec.rb +0 -516
  124. data/spec/rspec/matchers/be_within_spec.rb +0 -137
  125. data/spec/rspec/matchers/change_spec.rb +0 -553
  126. data/spec/rspec/matchers/configuration_spec.rb +0 -206
  127. data/spec/rspec/matchers/cover_spec.rb +0 -69
  128. data/spec/rspec/matchers/description_generation_spec.rb +0 -190
  129. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  130. data/spec/rspec/matchers/eq_spec.rb +0 -60
  131. data/spec/rspec/matchers/eql_spec.rb +0 -41
  132. data/spec/rspec/matchers/equal_spec.rb +0 -78
  133. data/spec/rspec/matchers/exist_spec.rb +0 -124
  134. data/spec/rspec/matchers/has_spec.rb +0 -122
  135. data/spec/rspec/matchers/have_spec.rb +0 -455
  136. data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
  137. data/spec/rspec/matchers/include_spec.rb +0 -531
  138. data/spec/rspec/matchers/match_array_spec.rb +0 -194
  139. data/spec/rspec/matchers/match_spec.rb +0 -61
  140. data/spec/rspec/matchers/matcher_spec.rb +0 -471
  141. data/spec/rspec/matchers/matchers_spec.rb +0 -37
  142. data/spec/rspec/matchers/method_missing_spec.rb +0 -28
  143. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -223
  144. data/spec/rspec/matchers/raise_error_spec.rb +0 -485
  145. data/spec/rspec/matchers/respond_to_spec.rb +0 -292
  146. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  147. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -186
  148. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  149. data/spec/rspec/matchers/yield_spec.rb +0 -514
  150. data/spec/spec_helper.rb +0 -54
  151. data/spec/support/classes.rb +0 -56
  152. data/spec/support/in_sub_process.rb +0 -38
  153. data/spec/support/matchers.rb +0 -22
  154. data/spec/support/ruby_version.rb +0 -10
  155. data/spec/support/shared_examples.rb +0 -13
@@ -1,19 +1,16 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
- class BeWithin
4
+ # @api private
5
+ # Provides the implementation for `be_within`.
6
+ # Not intended to be instantiated directly.
7
+ class BeWithin < BaseMatcher
5
8
  def initialize(delta)
6
9
  @delta = delta
7
10
  end
8
11
 
9
- def matches?(actual)
10
- @actual = actual
11
- raise needs_expected unless defined? @expected
12
- raise needs_subtractable unless @actual.respond_to? :-
13
- (@actual - @expected).abs <= @tolerance
14
- end
15
- alias == matches?
16
-
12
+ # @api public
13
+ # Sets the expected value.
17
14
  def of(expected)
18
15
  @expected = expected
19
16
  @tolerance = @delta
@@ -21,34 +18,54 @@ module RSpec
21
18
  self
22
19
  end
23
20
 
21
+ # @api public
22
+ # Sets the expected value, and makes the matcher do
23
+ # a percent comparison.
24
24
  def percent_of(expected)
25
25
  @expected = expected
26
- @tolerance = @delta * @expected.abs / 100.0
26
+ @tolerance = @expected.abs * @delta / 100.0
27
27
  @unit = '%'
28
28
  self
29
29
  end
30
30
 
31
- def failure_message_for_should
32
- "expected #{@actual} to #{description}"
31
+ # @private
32
+ def matches?(actual)
33
+ @actual = actual
34
+ raise needs_expected unless defined? @expected
35
+ numeric? && (@actual - @expected).abs <= @tolerance
36
+ end
37
+
38
+ # @api private
39
+ # @return [String]
40
+ def failure_message
41
+ "expected #{actual_formatted} to #{description}#{not_numeric_clause}"
33
42
  end
34
43
 
35
- def failure_message_for_should_not
36
- "expected #{@actual} not to #{description}"
44
+ # @api private
45
+ # @return [String]
46
+ def failure_message_when_negated
47
+ "expected #{actual_formatted} not to #{description}"
37
48
  end
38
49
 
50
+ # @api private
51
+ # @return [String]
39
52
  def description
40
- "be within #{@delta}#{@unit} of #{@expected}"
53
+ "be within #{@delta}#{@unit} of #{expected_formatted}"
41
54
  end
42
55
 
43
- private
56
+ private
44
57
 
45
- def needs_subtractable
46
- ArgumentError.new "The actual value (#{@actual.inspect}) must respond to `-`"
58
+ def numeric?
59
+ @actual.respond_to?(:-)
47
60
  end
48
61
 
49
62
  def needs_expected
50
63
  ArgumentError.new "You must set an expected value using #of: be_within(#{@delta}).of(expected_value)"
51
64
  end
65
+
66
+ def not_numeric_clause
67
+ ", but it could not be treated as a numeric value" unless numeric?
68
+ end
52
69
  end
53
70
  end
54
71
  end
@@ -1,139 +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, String
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 #{failure_message_for_expected_after}, 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
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)
75
147
  end
76
148
 
77
- def to(to)
78
- @eval_after = true
79
- @expected_after = to
80
- self
149
+ # @private
150
+ def does_not_match?(_event_proc)
151
+ raise NotImplementedError, "`expect { }.not_to change " \
152
+ "{ }.#{@relativity}()` is not supported"
81
153
  end
82
154
 
83
- def from (before)
84
- @eval_before = true
85
- @expected_before = before
86
- self
155
+ # @private
156
+ def description
157
+ "change #{@change_details.value_representation} " \
158
+ "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
87
159
  end
88
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
89
197
  def description
90
- "change ##{message}"
198
+ "change #{@change_details.value_representation} #{change_description}"
91
199
  end
92
200
 
93
- private
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
94
208
 
95
- def failure_message_for_expected_after
96
- if RSpec::Matchers.is_a_matcher?(@expected_after)
97
- @expected_after.description
98
- else
99
- @expected_after.inspect
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)
100
231
  end
101
232
  end
102
233
 
103
- def message
104
- @message || "result"
234
+ def matches_after?
235
+ values_match?(@expected_after, @change_details.actual_after)
105
236
  end
106
237
 
107
- def change_expected?
108
- @expected_delta != 0
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}"
109
242
  end
110
243
 
111
- def changed?
112
- @actual_before != @actual_after
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}"
113
248
  end
114
249
 
115
- def matches_before?
116
- @eval_before ? expected_matches_actual?(@expected_before, @actual_before) : true
250
+ def did_not_change_failure
251
+ "expected #{@change_details.value_representation} " \
252
+ "to have changed #{change_description}, but did not change"
117
253
  end
118
254
 
119
- def matches_after?
120
- @eval_after ? expected_matches_actual?(@expected_after, @actual_after) : true
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}"
281
+ self
121
282
  end
122
283
 
123
- def matches_expected_delta?
124
- @expected_delta ? (@actual_before + @expected_delta == @actual_after) : true
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
125
292
  end
126
293
 
127
- def matches_min?
128
- @minimum ? (@actual_after - @actual_before >= @minimum) : true
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
129
299
  end
130
300
 
131
- def matches_max?
132
- @maximum ? (@actual_after - @actual_before <= @maximum) : true
301
+ private
302
+
303
+ def change_description
304
+ "from #{description_of @expected_before}#{@description_suffix}"
133
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}"
322
+ self
323
+ end
324
+
325
+ # @private
326
+ def does_not_match?(_event_proc)
327
+ raise NotImplementedError, "`expect { }.not_to change { }.to()` " \
328
+ "is not supported"
329
+ end
330
+
331
+ private
332
+
333
+ def change_description
334
+ "to #{description_of @expected_after}#{@description_suffix}"
335
+ end
336
+ end
134
337
 
135
- def expected_matches_actual?(expected, actual)
136
- expected === actual
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
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
387
+ end
388
+
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
400
+ end
401
+
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
416
+ end
417
+
418
+ def actual_delta
419
+ @actual_after - @actual_before
420
+ end
421
+
422
+ private
423
+
424
+ def evaluate_value_proc
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}"
432
+ else
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
445
+ end
137
446
  end
138
447
  end
139
448
  end