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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +976 -25
- data/{License.txt → LICENSE.md} +5 -3
- data/README.md +162 -26
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +230 -0
- data/lib/rspec/expectations/expectation_target.rb +127 -51
- data/lib/rspec/expectations/fail_with.rb +17 -57
- data/lib/rspec/expectations/failure_aggregator.rb +229 -0
- data/lib/rspec/expectations/handler.rb +146 -32
- data/lib/rspec/expectations/minitest_integration.rb +58 -0
- data/lib/rspec/expectations/syntax.rb +68 -100
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/expectations.rb +58 -23
- data/lib/rspec/matchers/aliased_matcher.rb +116 -0
- data/lib/rspec/matchers/built_in/all.rb +86 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +191 -20
- data/lib/rspec/matchers/built_in/be.rb +114 -114
- data/lib/rspec/matchers/built_in/be_between.rb +77 -0
- data/lib/rspec/matchers/built_in/be_instance_of.rb +15 -4
- data/lib/rspec/matchers/built_in/be_kind_of.rb +10 -1
- data/lib/rspec/matchers/built_in/be_within.rb +35 -18
- data/lib/rspec/matchers/built_in/change.rb +389 -80
- data/lib/rspec/matchers/built_in/compound.rb +290 -0
- data/lib/rspec/matchers/built_in/contain_exactly.rb +310 -0
- data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
- data/lib/rspec/matchers/built_in/cover.rb +3 -0
- data/lib/rspec/matchers/built_in/eq.rb +30 -8
- data/lib/rspec/matchers/built_in/eql.rb +23 -8
- data/lib/rspec/matchers/built_in/equal.rb +55 -22
- data/lib/rspec/matchers/built_in/exist.rb +74 -10
- data/lib/rspec/matchers/built_in/has.rb +141 -22
- data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
- data/lib/rspec/matchers/built_in/include.rb +184 -32
- data/lib/rspec/matchers/built_in/match.rb +95 -1
- data/lib/rspec/matchers/built_in/operators.rb +128 -0
- data/lib/rspec/matchers/built_in/output.rb +207 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +192 -44
- data/lib/rspec/matchers/built_in/respond_to.rb +154 -28
- data/lib/rspec/matchers/built_in/satisfy.rb +39 -9
- data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
- data/lib/rspec/matchers/built_in/throw_symbol.rb +58 -14
- data/lib/rspec/matchers/built_in/yield.rb +240 -161
- data/lib/rspec/matchers/built_in.rb +47 -33
- data/lib/rspec/matchers/composable.rb +171 -0
- data/lib/rspec/matchers/dsl.rb +531 -10
- data/lib/rspec/matchers/english_phrasing.rb +58 -0
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/generated_descriptions.rb +14 -8
- data/lib/rspec/matchers/matcher_delegator.rb +61 -0
- data/lib/rspec/matchers/matcher_protocol.rb +105 -0
- data/lib/rspec/matchers/multi_matcher_diff.rb +82 -0
- data/lib/rspec/matchers.rb +520 -173
- data.tar.gz.sig +0 -0
- metadata +141 -242
- metadata.gz.sig +2 -0
- data/features/README.md +0 -48
- data/features/Upgrade.md +0 -53
- data/features/built_in_matchers/README.md +0 -90
- data/features/built_in_matchers/be.feature +0 -175
- data/features/built_in_matchers/be_within.feature +0 -48
- data/features/built_in_matchers/cover.feature +0 -47
- data/features/built_in_matchers/end_with.feature +0 -48
- data/features/built_in_matchers/equality.feature +0 -139
- data/features/built_in_matchers/exist.feature +0 -45
- data/features/built_in_matchers/expect_change.feature +0 -59
- data/features/built_in_matchers/expect_error.feature +0 -144
- data/features/built_in_matchers/have.feature +0 -109
- data/features/built_in_matchers/include.feature +0 -174
- data/features/built_in_matchers/match.feature +0 -52
- data/features/built_in_matchers/operators.feature +0 -227
- data/features/built_in_matchers/predicates.feature +0 -137
- data/features/built_in_matchers/respond_to.feature +0 -84
- data/features/built_in_matchers/satisfy.feature +0 -33
- data/features/built_in_matchers/start_with.feature +0 -48
- data/features/built_in_matchers/throw_symbol.feature +0 -91
- data/features/built_in_matchers/types.feature +0 -116
- data/features/built_in_matchers/yield.feature +0 -161
- data/features/custom_matchers/access_running_example.feature +0 -53
- data/features/custom_matchers/define_diffable_matcher.feature +0 -27
- data/features/custom_matchers/define_matcher.feature +0 -368
- data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
- data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
- data/features/customized_message.feature +0 -22
- data/features/diffing.feature +0 -85
- data/features/implicit_docstrings.feature +0 -52
- data/features/step_definitions/additional_cli_steps.rb +0 -22
- data/features/support/env.rb +0 -14
- data/features/syntax_configuration.feature +0 -71
- data/features/test_frameworks/test_unit.feature +0 -44
- data/lib/rspec/expectations/deprecation.rb +0 -17
- data/lib/rspec/expectations/differ.rb +0 -133
- data/lib/rspec/expectations/errors.rb +0 -9
- data/lib/rspec/expectations/extensions/array.rb +0 -9
- data/lib/rspec/expectations/extensions/object.rb +0 -29
- data/lib/rspec/expectations/extensions.rb +0 -2
- data/lib/rspec/matchers/be_close.rb +0 -9
- data/lib/rspec/matchers/built_in/have.rb +0 -124
- data/lib/rspec/matchers/built_in/match_array.rb +0 -51
- data/lib/rspec/matchers/built_in/start_and_end_with.rb +0 -48
- data/lib/rspec/matchers/compatibility.rb +0 -14
- data/lib/rspec/matchers/configuration.rb +0 -108
- data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
- data/lib/rspec/matchers/matcher.rb +0 -300
- data/lib/rspec/matchers/method_missing.rb +0 -12
- data/lib/rspec/matchers/operator_matcher.rb +0 -109
- data/lib/rspec/matchers/pretty.rb +0 -70
- data/lib/rspec/matchers/test_unit_integration.rb +0 -11
- data/lib/rspec-expectations.rb +0 -1
- data/spec/rspec/expectations/differ_spec.rb +0 -192
- data/spec/rspec/expectations/expectation_target_spec.rb +0 -82
- data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
- data/spec/rspec/expectations/fail_with_spec.rb +0 -114
- data/spec/rspec/expectations/handler_spec.rb +0 -227
- data/spec/rspec/expectations/syntax_spec.rb +0 -139
- data/spec/rspec/matchers/base_matcher_spec.rb +0 -62
- data/spec/rspec/matchers/be_close_spec.rb +0 -22
- data/spec/rspec/matchers/be_instance_of_spec.rb +0 -63
- data/spec/rspec/matchers/be_kind_of_spec.rb +0 -41
- data/spec/rspec/matchers/be_spec.rb +0 -516
- data/spec/rspec/matchers/be_within_spec.rb +0 -137
- data/spec/rspec/matchers/change_spec.rb +0 -553
- data/spec/rspec/matchers/configuration_spec.rb +0 -206
- data/spec/rspec/matchers/cover_spec.rb +0 -69
- data/spec/rspec/matchers/description_generation_spec.rb +0 -190
- data/spec/rspec/matchers/dsl_spec.rb +0 -57
- data/spec/rspec/matchers/eq_spec.rb +0 -60
- data/spec/rspec/matchers/eql_spec.rb +0 -41
- data/spec/rspec/matchers/equal_spec.rb +0 -78
- data/spec/rspec/matchers/exist_spec.rb +0 -124
- data/spec/rspec/matchers/has_spec.rb +0 -122
- data/spec/rspec/matchers/have_spec.rb +0 -455
- data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
- data/spec/rspec/matchers/include_spec.rb +0 -531
- data/spec/rspec/matchers/match_array_spec.rb +0 -194
- data/spec/rspec/matchers/match_spec.rb +0 -61
- data/spec/rspec/matchers/matcher_spec.rb +0 -471
- data/spec/rspec/matchers/matchers_spec.rb +0 -37
- data/spec/rspec/matchers/method_missing_spec.rb +0 -28
- data/spec/rspec/matchers/operator_matcher_spec.rb +0 -223
- data/spec/rspec/matchers/raise_error_spec.rb +0 -485
- data/spec/rspec/matchers/respond_to_spec.rb +0 -292
- data/spec/rspec/matchers/satisfy_spec.rb +0 -44
- data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -186
- data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
- data/spec/rspec/matchers/yield_spec.rb +0 -514
- data/spec/spec_helper.rb +0 -54
- data/spec/support/classes.rb +0 -56
- data/spec/support/in_sub_process.rb +0 -38
- data/spec/support/matchers.rb +0 -22
- data/spec/support/ruby_version.rb +0 -10
- 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
|
-
|
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
|
-
|
10
|
-
|
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 = @
|
26
|
+
@tolerance = @expected.abs * @delta / 100.0
|
27
27
|
@unit = '%'
|
28
28
|
self
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
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
|
-
|
36
|
-
|
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 #{
|
53
|
+
"be within #{@delta}#{@unit} of #{expected_formatted}"
|
41
54
|
end
|
42
55
|
|
43
|
-
|
56
|
+
private
|
44
57
|
|
45
|
-
def
|
46
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
69
|
+
# @api private
|
70
|
+
# @return [String]
|
71
|
+
def description
|
72
|
+
"change #{change_details.value_representation}"
|
27
73
|
end
|
28
74
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
val.dup
|
33
|
-
else
|
34
|
-
val
|
35
|
-
end
|
75
|
+
# @private
|
76
|
+
def supports_block_expectations?
|
77
|
+
true
|
36
78
|
end
|
37
79
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
55
|
-
|
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
|
59
|
-
"
|
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
|
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
|
-
|
132
|
+
@relativity = relativity
|
133
|
+
@comparer = comparer
|
65
134
|
end
|
66
135
|
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
84
|
-
|
85
|
-
@
|
86
|
-
|
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
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
104
|
-
@
|
234
|
+
def matches_after?
|
235
|
+
values_match?(@expected_after, @change_details.actual_after)
|
105
236
|
end
|
106
237
|
|
107
|
-
def
|
108
|
-
@
|
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
|
112
|
-
@
|
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
|
116
|
-
|
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
|
120
|
-
@
|
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
|
-
|
124
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
132
|
-
|
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
|
-
|
136
|
-
|
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
|