rspec-expectations 3.8.1 → 3.12.3
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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/Changelog.md +203 -4
- data/README.md +35 -20
- data/lib/rspec/expectations/configuration.rb +15 -0
- data/lib/rspec/expectations/expectation_target.rb +42 -6
- data/lib/rspec/expectations/failure_aggregator.rb +41 -6
- data/lib/rspec/expectations/handler.rb +20 -8
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers/aliased_matcher.rb +3 -3
- data/lib/rspec/matchers/built_in/all.rb +1 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +5 -0
- data/lib/rspec/matchers/built_in/be.rb +10 -107
- data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_within.rb +2 -2
- data/lib/rspec/matchers/built_in/change.rb +26 -2
- data/lib/rspec/matchers/built_in/compound.rb +20 -1
- data/lib/rspec/matchers/built_in/contain_exactly.rb +10 -2
- data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
- data/lib/rspec/matchers/built_in/exist.rb +1 -1
- data/lib/rspec/matchers/built_in/has.rb +88 -24
- data/lib/rspec/matchers/built_in/have_attributes.rb +1 -1
- data/lib/rspec/matchers/built_in/include.rb +82 -18
- data/lib/rspec/matchers/built_in/output.rb +7 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +63 -22
- data/lib/rspec/matchers/built_in/respond_to.rb +53 -18
- data/lib/rspec/matchers/built_in/throw_symbol.rb +10 -4
- data/lib/rspec/matchers/built_in/yield.rb +26 -83
- data/lib/rspec/matchers/built_in.rb +2 -1
- data/lib/rspec/matchers/composable.rb +1 -1
- data/lib/rspec/matchers/dsl.rb +29 -11
- data/lib/rspec/matchers/english_phrasing.rb +1 -1
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
- data/lib/rspec/matchers/matcher_protocol.rb +6 -0
- data/lib/rspec/matchers.rb +78 -68
- data.tar.gz.sig +0 -0
- metadata +29 -24
- metadata.gz.sig +0 -0
@@ -143,8 +143,13 @@ module RSpec
|
|
143
143
|
end
|
144
144
|
|
145
145
|
def matches?(actual)
|
146
|
-
|
147
|
-
|
146
|
+
perform_match(actual)
|
147
|
+
rescue ArgumentError, NoMethodError
|
148
|
+
false
|
149
|
+
end
|
150
|
+
|
151
|
+
def does_not_match?(actual)
|
152
|
+
!perform_match(actual)
|
148
153
|
rescue ArgumentError, NoMethodError
|
149
154
|
false
|
150
155
|
end
|
@@ -173,114 +178,12 @@ module RSpec
|
|
173
178
|
def description
|
174
179
|
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
|
175
180
|
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# @api private
|
179
|
-
# Provides the implementation of `be_<predicate>`.
|
180
|
-
# Not intended to be instantiated directly.
|
181
|
-
class BePredicate < BaseMatcher
|
182
|
-
include BeHelpers
|
183
|
-
|
184
|
-
def initialize(*args, &block)
|
185
|
-
@expected = parse_expected(args.shift)
|
186
|
-
@args = args
|
187
|
-
@block = block
|
188
|
-
end
|
189
|
-
|
190
|
-
def matches?(actual, &block)
|
191
|
-
@actual = actual
|
192
|
-
@block ||= block
|
193
|
-
predicate_accessible? && predicate_matches?
|
194
|
-
end
|
195
|
-
|
196
|
-
def does_not_match?(actual, &block)
|
197
|
-
@actual = actual
|
198
|
-
@block ||= block
|
199
|
-
predicate_accessible? && !predicate_matches?
|
200
|
-
end
|
201
|
-
|
202
|
-
# @api private
|
203
|
-
# @return [String]
|
204
|
-
def failure_message
|
205
|
-
failure_message_expecting(true)
|
206
|
-
end
|
207
|
-
|
208
|
-
# @api private
|
209
|
-
# @return [String]
|
210
|
-
def failure_message_when_negated
|
211
|
-
failure_message_expecting(false)
|
212
|
-
end
|
213
|
-
|
214
|
-
# @api private
|
215
|
-
# @return [String]
|
216
|
-
def description
|
217
|
-
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
|
218
|
-
end
|
219
181
|
|
220
182
|
private
|
221
183
|
|
222
|
-
def
|
223
|
-
actual
|
224
|
-
|
225
|
-
|
226
|
-
# support 1.8.7, evaluate once at load time for performance
|
227
|
-
if String === methods.first
|
228
|
-
# :nocov:
|
229
|
-
def private_predicate?
|
230
|
-
@actual.private_methods.include? predicate.to_s
|
231
|
-
end
|
232
|
-
# :nocov:
|
233
|
-
else
|
234
|
-
def private_predicate?
|
235
|
-
@actual.private_methods.include? predicate
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
def predicate_matches?
|
240
|
-
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
|
241
|
-
@predicate_matches = actual.__send__(method_name, *@args, &@block)
|
242
|
-
end
|
243
|
-
|
244
|
-
def predicate
|
245
|
-
:"#{@expected}?"
|
246
|
-
end
|
247
|
-
|
248
|
-
def present_tense_predicate
|
249
|
-
:"#{@expected}s?"
|
250
|
-
end
|
251
|
-
|
252
|
-
def parse_expected(expected)
|
253
|
-
@prefix, expected = prefix_and_expected(expected)
|
254
|
-
expected
|
255
|
-
end
|
256
|
-
|
257
|
-
def prefix_and_expected(symbol)
|
258
|
-
Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
|
259
|
-
end
|
260
|
-
|
261
|
-
def prefix_to_sentence
|
262
|
-
EnglishPhrasing.split_words(@prefix)
|
263
|
-
end
|
264
|
-
|
265
|
-
def failure_message_expecting(value)
|
266
|
-
validity_message ||
|
267
|
-
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
|
268
|
-
end
|
269
|
-
|
270
|
-
def validity_message
|
271
|
-
return nil if predicate_accessible?
|
272
|
-
|
273
|
-
msg = "expected #{actual_formatted} to respond to `#{predicate}`".dup
|
274
|
-
|
275
|
-
if private_predicate?
|
276
|
-
msg << " but `#{predicate}` is a private method"
|
277
|
-
elsif predicate == :true?
|
278
|
-
msg << " or perhaps you meant `be true` or `be_truthy`"
|
279
|
-
elsif predicate == :false?
|
280
|
-
msg << " or perhaps you meant `be false` or `be_falsey`"
|
281
|
-
end
|
282
|
-
|
283
|
-
msg
|
184
|
+
def perform_match(actual)
|
185
|
+
@actual = actual
|
186
|
+
@actual.__send__ @operator, @expected
|
284
187
|
end
|
285
188
|
end
|
286
189
|
end
|
@@ -14,7 +14,11 @@ module RSpec
|
|
14
14
|
private
|
15
15
|
|
16
16
|
def match(expected, actual)
|
17
|
-
actual.instance_of?
|
17
|
+
actual.instance_of?(expected)
|
18
|
+
rescue NoMethodError
|
19
|
+
raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
|
20
|
+
"the actual object responds to #instance_of? method " \
|
21
|
+
"but a `NoMethodError` was encountered instead."
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -8,7 +8,11 @@ module RSpec
|
|
8
8
|
private
|
9
9
|
|
10
10
|
def match(expected, actual)
|
11
|
-
actual.kind_of?
|
11
|
+
actual.kind_of?(expected)
|
12
|
+
rescue NoMethodError
|
13
|
+
raise ::ArgumentError, "The #{matcher_name} matcher requires that " \
|
14
|
+
"the actual object responds to #kind_of? method " \
|
15
|
+
"but a `NoMethodError` was encountered instead."
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -23,7 +23,7 @@ module RSpec
|
|
23
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
|
@@ -50,7 +50,7 @@ module RSpec
|
|
50
50
|
# @api private
|
51
51
|
# @return [String]
|
52
52
|
def description
|
53
|
-
"be within #{@delta}#{@unit} of #{
|
53
|
+
"be within #{@delta}#{@unit} of #{expected_formatted}"
|
54
54
|
end
|
55
55
|
|
56
56
|
private
|
@@ -77,6 +77,11 @@ module RSpec
|
|
77
77
|
true
|
78
78
|
end
|
79
79
|
|
80
|
+
# @private
|
81
|
+
def supports_value_expectations?
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
80
85
|
private
|
81
86
|
|
82
87
|
def initialize(receiver=nil, message=nil, &block)
|
@@ -158,6 +163,11 @@ module RSpec
|
|
158
163
|
true
|
159
164
|
end
|
160
165
|
|
166
|
+
# @private
|
167
|
+
def supports_value_expectations?
|
168
|
+
false
|
169
|
+
end
|
170
|
+
|
161
171
|
private
|
162
172
|
|
163
173
|
def failure_reason
|
@@ -201,6 +211,11 @@ module RSpec
|
|
201
211
|
true
|
202
212
|
end
|
203
213
|
|
214
|
+
# @private
|
215
|
+
def supports_value_expectations?
|
216
|
+
false
|
217
|
+
end
|
218
|
+
|
204
219
|
private
|
205
220
|
|
206
221
|
def perform_change(event_proc)
|
@@ -337,6 +352,8 @@ module RSpec
|
|
337
352
|
class ChangeDetails
|
338
353
|
attr_reader :actual_after
|
339
354
|
|
355
|
+
UNDEFINED = Module.new.freeze
|
356
|
+
|
340
357
|
def initialize(matcher_name, receiver=nil, message=nil, &block)
|
341
358
|
if receiver && !message
|
342
359
|
raise(
|
@@ -351,6 +368,11 @@ module RSpec
|
|
351
368
|
@receiver = receiver
|
352
369
|
@message = message
|
353
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
|
354
376
|
end
|
355
377
|
|
356
378
|
def value_representation
|
@@ -373,6 +395,7 @@ module RSpec
|
|
373
395
|
event_proc.call
|
374
396
|
|
375
397
|
@actual_after = evaluate_value_proc
|
398
|
+
@actual_hash = @actual_after.hash
|
376
399
|
true
|
377
400
|
end
|
378
401
|
|
@@ -387,8 +410,9 @@ module RSpec
|
|
387
410
|
#
|
388
411
|
# Note that it is not sufficient to only check the hashes; it is
|
389
412
|
# possible for two values to be unequal (and of different classes)
|
390
|
-
# but to return the same hash value.
|
391
|
-
|
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
|
392
416
|
end
|
393
417
|
|
394
418
|
def actual_delta
|
@@ -26,11 +26,19 @@ module RSpec
|
|
26
26
|
"#{matcher_1.description} #{conjunction} #{matcher_2.description}"
|
27
27
|
end
|
28
28
|
|
29
|
+
# @api private
|
29
30
|
def supports_block_expectations?
|
30
31
|
matcher_supports_block_expectations?(matcher_1) &&
|
31
32
|
matcher_supports_block_expectations?(matcher_2)
|
32
33
|
end
|
33
34
|
|
35
|
+
# @api private
|
36
|
+
def supports_value_expectations?
|
37
|
+
matcher_supports_value_expectations?(matcher_1) &&
|
38
|
+
matcher_supports_value_expectations?(matcher_2)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
34
42
|
def expects_call_stack_jump?
|
35
43
|
NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
|
36
44
|
NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
|
@@ -102,6 +110,12 @@ module RSpec
|
|
102
110
|
false
|
103
111
|
end
|
104
112
|
|
113
|
+
def matcher_supports_value_expectations?(matcher)
|
114
|
+
matcher.supports_value_expectations?
|
115
|
+
rescue NoMethodError
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
105
119
|
def matcher_is_diffable?(matcher)
|
106
120
|
matcher.diffable?
|
107
121
|
rescue NoMethodError
|
@@ -154,7 +168,12 @@ module RSpec
|
|
154
168
|
end
|
155
169
|
|
156
170
|
def matcher_matches?(matcher)
|
157
|
-
@match_results.fetch(matcher)
|
171
|
+
@match_results.fetch(matcher) do
|
172
|
+
raise ArgumentError, "Your #{matcher.description} has no match " \
|
173
|
+
"results, this can occur when an unexpected call stack or " \
|
174
|
+
"local jump occurs. Perhaps one of your matchers needs to " \
|
175
|
+
"declare `expects_call_stack_jump?` as `true`?"
|
176
|
+
end
|
158
177
|
end
|
159
178
|
|
160
179
|
private
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module RSpec
|
2
2
|
module Matchers
|
3
3
|
module BuiltIn
|
4
|
-
# rubocop:disable ClassLength
|
4
|
+
# rubocop:disable Metrics/ClassLength
|
5
5
|
# @api private
|
6
6
|
# Provides the implementation for `contain_exactly` and `match_array`.
|
7
7
|
# Not intended to be instantiated directly.
|
@@ -31,6 +31,14 @@ module RSpec
|
|
31
31
|
"contain exactly#{list}"
|
32
32
|
end
|
33
33
|
|
34
|
+
def matches?(actual)
|
35
|
+
@pairings_maximizer = nil
|
36
|
+
@best_solution = nil
|
37
|
+
@extra_items = nil
|
38
|
+
@missing_items = nil
|
39
|
+
super(actual)
|
40
|
+
end
|
41
|
+
|
34
42
|
private
|
35
43
|
|
36
44
|
def generate_failure_message
|
@@ -296,7 +304,7 @@ module RSpec
|
|
296
304
|
end
|
297
305
|
end
|
298
306
|
end
|
299
|
-
# rubocop:enable ClassLength
|
307
|
+
# rubocop:enable Metrics/ClassLength
|
300
308
|
end
|
301
309
|
end
|
302
310
|
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Abstract class to implement `once`, `at_least` and other
|
6
|
+
# count constraints.
|
7
|
+
module CountExpectation
|
8
|
+
# @api public
|
9
|
+
# Specifies that the method is expected to match once.
|
10
|
+
def once
|
11
|
+
exactly(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @api public
|
15
|
+
# Specifies that the method is expected to match twice.
|
16
|
+
def twice
|
17
|
+
exactly(2)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api public
|
21
|
+
# Specifies that the method is expected to match thrice.
|
22
|
+
def thrice
|
23
|
+
exactly(3)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api public
|
27
|
+
# Specifies that the method is expected to match the given number of times.
|
28
|
+
def exactly(number)
|
29
|
+
set_expected_count(:==, number)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api public
|
34
|
+
# Specifies the maximum number of times the method is expected to match
|
35
|
+
def at_most(number)
|
36
|
+
set_expected_count(:<=, number)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api public
|
41
|
+
# Specifies the minimum number of times the method is expected to match
|
42
|
+
def at_least(number)
|
43
|
+
set_expected_count(:>=, number)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api public
|
48
|
+
# No-op. Provides syntactic sugar.
|
49
|
+
def times
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
# @api private
|
55
|
+
attr_reader :count_expectation_type, :expected_count
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
if RUBY_VERSION.to_f > 1.8
|
60
|
+
def cover?(count, number)
|
61
|
+
count.cover?(number)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
def cover?(count, number)
|
65
|
+
number >= count.first && number <= count.last
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def expected_count_matches?(actual_count)
|
70
|
+
@actual_count = actual_count
|
71
|
+
return @actual_count > 0 unless count_expectation_type
|
72
|
+
return cover?(expected_count, actual_count) if count_expectation_type == :<=>
|
73
|
+
|
74
|
+
@actual_count.__send__(count_expectation_type, expected_count)
|
75
|
+
end
|
76
|
+
|
77
|
+
def has_expected_count?
|
78
|
+
!!count_expectation_type
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_expected_count(relativity, n)
|
82
|
+
raise_unsupported_count_expectation if unsupported_count_expectation?(relativity)
|
83
|
+
|
84
|
+
count = count_constraint_to_number(n)
|
85
|
+
|
86
|
+
if count_expectation_type == :<= && relativity == :>=
|
87
|
+
raise_impossible_count_expectation(count) if count > expected_count
|
88
|
+
@count_expectation_type = :<=>
|
89
|
+
@expected_count = count..expected_count
|
90
|
+
elsif count_expectation_type == :>= && relativity == :<=
|
91
|
+
raise_impossible_count_expectation(count) if count < expected_count
|
92
|
+
@count_expectation_type = :<=>
|
93
|
+
@expected_count = expected_count..count
|
94
|
+
else
|
95
|
+
@count_expectation_type = relativity
|
96
|
+
@expected_count = count
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def raise_impossible_count_expectation(count)
|
101
|
+
text =
|
102
|
+
case count_expectation_type
|
103
|
+
when :<= then "at_least(#{count}).at_most(#{expected_count})"
|
104
|
+
when :>= then "at_least(#{expected_count}).at_most(#{count})"
|
105
|
+
end
|
106
|
+
raise ArgumentError, "The constraint #{text} is not possible"
|
107
|
+
end
|
108
|
+
|
109
|
+
def raise_unsupported_count_expectation
|
110
|
+
text =
|
111
|
+
case count_expectation_type
|
112
|
+
when :<= then "at_least"
|
113
|
+
when :>= then "at_most"
|
114
|
+
when :<=> then "at_least/at_most combination"
|
115
|
+
else "count"
|
116
|
+
end
|
117
|
+
raise ArgumentError, "Multiple #{text} constraints are not supported"
|
118
|
+
end
|
119
|
+
|
120
|
+
def count_constraint_to_number(n)
|
121
|
+
case n
|
122
|
+
when Numeric then n
|
123
|
+
when :once then 1
|
124
|
+
when :twice then 2
|
125
|
+
when :thrice then 3
|
126
|
+
else
|
127
|
+
raise ArgumentError, "Expected a number, :once, :twice or :thrice," \
|
128
|
+
" but got #{n}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def unsupported_count_expectation?(relativity)
|
133
|
+
return true if count_expectation_type == :==
|
134
|
+
return true if count_expectation_type == :<=>
|
135
|
+
(count_expectation_type == :<= && relativity == :<=) ||
|
136
|
+
(count_expectation_type == :>= && relativity == :>=)
|
137
|
+
end
|
138
|
+
|
139
|
+
def count_expectation_description
|
140
|
+
"#{human_readable_expectation_type}#{human_readable_count(expected_count)}"
|
141
|
+
end
|
142
|
+
|
143
|
+
def count_failure_reason(action)
|
144
|
+
"#{count_expectation_description}" \
|
145
|
+
" but #{action}#{human_readable_count(@actual_count)}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def human_readable_expectation_type
|
149
|
+
case count_expectation_type
|
150
|
+
when :<= then ' at most'
|
151
|
+
when :>= then ' at least'
|
152
|
+
when :<=> then ' between'
|
153
|
+
else ''
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def human_readable_count(count)
|
158
|
+
case count
|
159
|
+
when Range then " #{count.first} and #{count.last} times"
|
160
|
+
when nil then ''
|
161
|
+
when 1 then ' once'
|
162
|
+
when 2 then ' twice'
|
163
|
+
else " #{count} times"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -2,12 +2,15 @@ module RSpec
|
|
2
2
|
module Matchers
|
3
3
|
module BuiltIn
|
4
4
|
# @api private
|
5
|
-
# Provides the implementation for
|
6
|
-
# Not intended to be
|
7
|
-
class
|
5
|
+
# Provides the implementation for dynamic predicate matchers.
|
6
|
+
# Not intended to be inherited directly.
|
7
|
+
class DynamicPredicate < BaseMatcher
|
8
|
+
include BeHelpers
|
9
|
+
|
8
10
|
def initialize(method_name, *args, &block)
|
9
11
|
@method_name, @args, @block = method_name, args, block
|
10
12
|
end
|
13
|
+
ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
|
11
14
|
|
12
15
|
# @private
|
13
16
|
def matches?(actual, &block)
|
@@ -20,31 +23,31 @@ module RSpec
|
|
20
23
|
def does_not_match?(actual, &block)
|
21
24
|
@actual = actual
|
22
25
|
@block ||= block
|
23
|
-
predicate_accessible? &&
|
26
|
+
predicate_accessible? && predicate_matches?(false)
|
24
27
|
end
|
25
28
|
|
26
29
|
# @api private
|
27
30
|
# @return [String]
|
28
31
|
def failure_message
|
29
|
-
|
32
|
+
failure_message_expecting(true)
|
30
33
|
end
|
31
34
|
|
32
35
|
# @api private
|
33
36
|
# @return [String]
|
34
37
|
def failure_message_when_negated
|
35
|
-
|
38
|
+
failure_message_expecting(false)
|
36
39
|
end
|
37
40
|
|
38
41
|
# @api private
|
39
42
|
# @return [String]
|
40
43
|
def description
|
41
|
-
|
44
|
+
"#{method_description}#{args_to_sentence}"
|
42
45
|
end
|
43
46
|
|
44
47
|
private
|
45
48
|
|
46
49
|
def predicate_accessible?
|
47
|
-
|
50
|
+
@actual.respond_to? predicate
|
48
51
|
end
|
49
52
|
|
50
53
|
# support 1.8.7, evaluate once at load time for performance
|
@@ -60,44 +63,105 @@ module RSpec
|
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
|
-
def
|
64
|
-
@actual.
|
66
|
+
def predicate_result
|
67
|
+
@predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
68
|
-
|
70
|
+
def predicate_method_name
|
71
|
+
predicate
|
69
72
|
end
|
70
73
|
|
71
|
-
def
|
74
|
+
def predicate_matches?(value=true)
|
75
|
+
if RSpec::Expectations.configuration.strict_predicate_matchers?
|
76
|
+
value == predicate_result
|
77
|
+
else
|
78
|
+
value == !!predicate_result
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def root
|
72
83
|
# On 1.9, there appears to be a bug where String#match can return `false`
|
73
84
|
# rather than the match data object. Changing to Regex#match appears to
|
74
85
|
# work around this bug. For an example of this bug, see:
|
75
86
|
# https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
|
76
|
-
|
87
|
+
self.class::REGEX.match(@method_name.to_s).captures.first
|
77
88
|
end
|
78
89
|
|
79
90
|
def method_description
|
80
|
-
@method_name
|
91
|
+
EnglishPhrasing.split_words(@method_name)
|
81
92
|
end
|
82
93
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
94
|
+
def failure_message_expecting(value)
|
95
|
+
validity_message ||
|
96
|
+
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to #{expectation_of value}, got #{description_of @predicate_result}"
|
86
97
|
end
|
87
98
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
99
|
+
def expectation_of(value)
|
100
|
+
if RSpec::Expectations.configuration.strict_predicate_matchers?
|
101
|
+
"return #{value}"
|
102
|
+
elsif value
|
103
|
+
"be truthy"
|
104
|
+
else
|
105
|
+
"be falsey"
|
106
|
+
end
|
91
107
|
end
|
92
108
|
|
93
109
|
def validity_message
|
110
|
+
return nil if predicate_accessible?
|
111
|
+
|
112
|
+
"expected #{actual_formatted} to respond to `#{predicate}`#{failure_to_respond_explanation}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def failure_to_respond_explanation
|
94
116
|
if private_predicate?
|
95
|
-
"
|
96
|
-
elsif !predicate_exists?
|
97
|
-
"expected #{@actual} to respond to `#{predicate}`"
|
117
|
+
" but `#{predicate}` is a private method"
|
98
118
|
end
|
99
119
|
end
|
100
120
|
end
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
# Provides the implementation for `has_<predicate>`.
|
124
|
+
# Not intended to be instantiated directly.
|
125
|
+
class Has < DynamicPredicate
|
126
|
+
# :nodoc:
|
127
|
+
REGEX = Matchers::HAS_REGEX
|
128
|
+
private
|
129
|
+
def predicate
|
130
|
+
@predicate ||= :"has_#{root}?"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# @api private
|
135
|
+
# Provides the implementation of `be_<predicate>`.
|
136
|
+
# Not intended to be instantiated directly.
|
137
|
+
class BePredicate < DynamicPredicate
|
138
|
+
# :nodoc:
|
139
|
+
REGEX = Matchers::BE_PREDICATE_REGEX
|
140
|
+
private
|
141
|
+
def predicate
|
142
|
+
@predicate ||= :"#{root}?"
|
143
|
+
end
|
144
|
+
|
145
|
+
def predicate_method_name
|
146
|
+
actual.respond_to?(predicate) ? predicate : present_tense_predicate
|
147
|
+
end
|
148
|
+
|
149
|
+
def failure_to_respond_explanation
|
150
|
+
super || if predicate == :true?
|
151
|
+
" or perhaps you meant `be true` or `be_truthy`"
|
152
|
+
elsif predicate == :false?
|
153
|
+
" or perhaps you meant `be false` or `be_falsey`"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def predicate_accessible?
|
158
|
+
super || actual.respond_to?(present_tense_predicate)
|
159
|
+
end
|
160
|
+
|
161
|
+
def present_tense_predicate
|
162
|
+
:"#{root}s?"
|
163
|
+
end
|
164
|
+
end
|
101
165
|
end
|
102
166
|
end
|
103
167
|
end
|