rspec-expectations 3.9.3 → 3.10.2
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Changelog.md +52 -1
- data/README.md +1 -1
- data/lib/rspec/expectations/configuration.rb +15 -0
- data/lib/rspec/expectations/expectation_target.rb +3 -5
- data/lib/rspec/expectations/failure_aggregator.rb +23 -5
- data/lib/rspec/expectations/handler.rb +18 -6
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/matchers/built_in/be.rb +0 -133
- data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
- data/lib/rspec/matchers/built_in/has.rb +89 -45
- data/lib/rspec/matchers/built_in/include.rb +72 -15
- data/lib/rspec/matchers/built_in/raise_error.rb +51 -16
- data/lib/rspec/matchers/built_in/respond_to.rb +46 -45
- data/lib/rspec/matchers/built_in/yield.rb +6 -92
- data/lib/rspec/matchers/built_in.rb +2 -1
- data/lib/rspec/matchers.rb +50 -62
- data.tar.gz.sig +0 -0
- metadata +10 -9
- metadata.gz.sig +0 -0
@@ -2,20 +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
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
CODE
|
14
|
-
else
|
15
|
-
def initialize(method_name, *args, &block)
|
16
|
-
@method_name, @args, @block = method_name, args, block
|
17
|
-
end
|
5
|
+
# Provides the implementation for dynamic predicate matchers.
|
6
|
+
# Not intended to be inherited directly.
|
7
|
+
class DynamicPredicate < BaseMatcher
|
8
|
+
include BeHelpers
|
9
|
+
|
10
|
+
def initialize(method_name, *args, &block)
|
11
|
+
@method_name, @args, @block = method_name, args, block
|
18
12
|
end
|
13
|
+
ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
|
19
14
|
|
20
15
|
# @private
|
21
16
|
def matches?(actual, &block)
|
@@ -28,31 +23,31 @@ module RSpec
|
|
28
23
|
def does_not_match?(actual, &block)
|
29
24
|
@actual = actual
|
30
25
|
@block ||= block
|
31
|
-
predicate_accessible? &&
|
26
|
+
predicate_accessible? && predicate_matches?(false)
|
32
27
|
end
|
33
28
|
|
34
29
|
# @api private
|
35
30
|
# @return [String]
|
36
31
|
def failure_message
|
37
|
-
|
32
|
+
failure_message_expecting(true)
|
38
33
|
end
|
39
34
|
|
40
35
|
# @api private
|
41
36
|
# @return [String]
|
42
37
|
def failure_message_when_negated
|
43
|
-
|
38
|
+
failure_message_expecting(false)
|
44
39
|
end
|
45
40
|
|
46
41
|
# @api private
|
47
42
|
# @return [String]
|
48
43
|
def description
|
49
|
-
|
44
|
+
"#{method_description}#{args_to_sentence}"
|
50
45
|
end
|
51
46
|
|
52
47
|
private
|
53
48
|
|
54
49
|
def predicate_accessible?
|
55
|
-
|
50
|
+
@actual.respond_to? predicate
|
56
51
|
end
|
57
52
|
|
58
53
|
# support 1.8.7, evaluate once at load time for performance
|
@@ -68,56 +63,105 @@ module RSpec
|
|
68
63
|
end
|
69
64
|
end
|
70
65
|
|
71
|
-
def
|
72
|
-
@actual.
|
66
|
+
def predicate_result
|
67
|
+
@predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
|
73
68
|
end
|
74
69
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
CODE
|
85
|
-
else
|
86
|
-
def predicate_matches?
|
87
|
-
@actual.__send__(predicate, *@args, &@block)
|
70
|
+
def predicate_method_name
|
71
|
+
predicate
|
72
|
+
end
|
73
|
+
|
74
|
+
def predicate_matches?(value=true)
|
75
|
+
if RSpec::Expectations.configuration.strict_predicate_matchers?
|
76
|
+
value == predicate_result
|
77
|
+
else
|
78
|
+
value == !!predicate_result
|
88
79
|
end
|
89
80
|
end
|
90
81
|
|
91
|
-
def
|
82
|
+
def root
|
92
83
|
# On 1.9, there appears to be a bug where String#match can return `false`
|
93
84
|
# rather than the match data object. Changing to Regex#match appears to
|
94
85
|
# work around this bug. For an example of this bug, see:
|
95
86
|
# https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
|
96
|
-
|
87
|
+
self.class::REGEX.match(@method_name.to_s).captures.first
|
97
88
|
end
|
98
89
|
|
99
90
|
def method_description
|
100
|
-
@method_name
|
91
|
+
EnglishPhrasing.split_words(@method_name)
|
101
92
|
end
|
102
93
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
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}"
|
106
97
|
end
|
107
98
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
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
|
111
107
|
end
|
112
108
|
|
113
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
|
114
116
|
if private_predicate?
|
115
|
-
"
|
116
|
-
elsif !predicate_exists?
|
117
|
-
"expected #{@actual} to respond to `#{predicate}`"
|
117
|
+
" but `#{predicate}` is a private method"
|
118
118
|
end
|
119
119
|
end
|
120
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
|
121
165
|
end
|
122
166
|
end
|
123
167
|
end
|
@@ -1,13 +1,17 @@
|
|
1
|
+
require 'rspec/matchers/built_in/count_expectation'
|
2
|
+
|
1
3
|
module RSpec
|
2
4
|
module Matchers
|
3
5
|
module BuiltIn
|
4
6
|
# @api private
|
5
7
|
# Provides the implementation for `include`.
|
6
8
|
# Not intended to be instantiated directly.
|
7
|
-
class Include < BaseMatcher
|
9
|
+
class Include < BaseMatcher # rubocop:disable Metrics/ClassLength
|
10
|
+
include CountExpectation
|
8
11
|
# @private
|
9
12
|
attr_reader :expecteds
|
10
13
|
|
14
|
+
# @api private
|
11
15
|
def initialize(*expecteds)
|
12
16
|
@expecteds = expecteds
|
13
17
|
end
|
@@ -15,21 +19,29 @@ module RSpec
|
|
15
19
|
# @api private
|
16
20
|
# @return [Boolean]
|
17
21
|
def matches?(actual)
|
18
|
-
|
19
|
-
|
22
|
+
check_actual?(actual) &&
|
23
|
+
if check_expected_count?
|
24
|
+
expected_count_matches?(count_inclusions)
|
25
|
+
else
|
26
|
+
perform_match { |v| v }
|
27
|
+
end
|
20
28
|
end
|
21
29
|
|
22
30
|
# @api private
|
23
31
|
# @return [Boolean]
|
24
32
|
def does_not_match?(actual)
|
25
|
-
|
26
|
-
|
33
|
+
check_actual?(actual) &&
|
34
|
+
if check_expected_count?
|
35
|
+
!expected_count_matches?(count_inclusions)
|
36
|
+
else
|
37
|
+
perform_match { |v| !v }
|
38
|
+
end
|
27
39
|
end
|
28
40
|
|
29
41
|
# @api private
|
30
42
|
# @return [String]
|
31
43
|
def description
|
32
|
-
improve_hash_formatting("include#{readable_list_of(expecteds)}")
|
44
|
+
improve_hash_formatting("include#{readable_list_of(expecteds)}#{count_expectation_description}")
|
33
45
|
end
|
34
46
|
|
35
47
|
# @api private
|
@@ -62,12 +74,33 @@ module RSpec
|
|
62
74
|
|
63
75
|
private
|
64
76
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
77
|
+
def check_actual?(actual)
|
78
|
+
actual = actual.to_hash if convert_to_hash?(actual)
|
79
|
+
@actual = actual
|
80
|
+
@actual.respond_to?(:include?)
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_expected_count?
|
84
|
+
case
|
85
|
+
when !has_expected_count?
|
86
|
+
return false
|
87
|
+
when expecteds.size != 1
|
88
|
+
raise NotImplementedError, 'Count constraint supported only when testing for a single value being included'
|
89
|
+
when actual.is_a?(Hash)
|
90
|
+
raise NotImplementedError, 'Count constraint on hash keys not implemented'
|
70
91
|
end
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def format_failure_message(preposition)
|
96
|
+
msg = if actual.respond_to?(:include?)
|
97
|
+
"expected #{description_of @actual} #{preposition}" \
|
98
|
+
" include#{readable_list_of @divergent_items}" \
|
99
|
+
"#{count_failure_reason('it is included') if has_expected_count?}"
|
100
|
+
else
|
101
|
+
"#{yield}, but it does not respond to `include?`"
|
102
|
+
end
|
103
|
+
improve_hash_formatting(msg)
|
71
104
|
end
|
72
105
|
|
73
106
|
def readable_list_of(items)
|
@@ -79,10 +112,9 @@ module RSpec
|
|
79
112
|
end
|
80
113
|
end
|
81
114
|
|
82
|
-
def perform_match(
|
83
|
-
@actual = actual
|
115
|
+
def perform_match(&block)
|
84
116
|
@divergent_items = excluded_from_actual(&block)
|
85
|
-
|
117
|
+
@divergent_items.empty?
|
86
118
|
end
|
87
119
|
|
88
120
|
def excluded_from_actual
|
@@ -107,7 +139,10 @@ module RSpec
|
|
107
139
|
end
|
108
140
|
|
109
141
|
def actual_hash_includes?(expected_key, expected_value)
|
110
|
-
actual_value =
|
142
|
+
actual_value =
|
143
|
+
actual.fetch(expected_key) do
|
144
|
+
actual.find(Proc.new { return false }) { |actual_key, _| values_match?(expected_key, actual_key) }[1]
|
145
|
+
end
|
111
146
|
values_match?(expected_value, actual_value)
|
112
147
|
end
|
113
148
|
|
@@ -131,6 +166,28 @@ module RSpec
|
|
131
166
|
actual.any? { |value| values_match?(expected_item, value) }
|
132
167
|
end
|
133
168
|
|
169
|
+
if RUBY_VERSION < '1.9'
|
170
|
+
def count_enumerable(expected_item)
|
171
|
+
actual.select { |value| values_match?(expected_item, value) }.size
|
172
|
+
end
|
173
|
+
else
|
174
|
+
def count_enumerable(expected_item)
|
175
|
+
actual.count { |value| values_match?(expected_item, value) }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def count_inclusions
|
180
|
+
@divergent_items = expected
|
181
|
+
case actual
|
182
|
+
when String
|
183
|
+
actual.scan(expected.first).length
|
184
|
+
when Enumerable
|
185
|
+
count_enumerable(Hash === expected ? expected : expected.first)
|
186
|
+
else
|
187
|
+
raise NotImplementedError, 'Count constraints are implemented for Enumerable and String values only'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
134
191
|
def diff_would_wrongly_highlight_matched_item?
|
135
192
|
return false unless actual.is_a?(String) && expected.is_a?(Array)
|
136
193
|
|
@@ -9,13 +9,20 @@ module RSpec
|
|
9
9
|
class RaiseError
|
10
10
|
include Composable
|
11
11
|
|
12
|
-
|
12
|
+
# Used as a sentinel value to be able to tell when the user did not pass an
|
13
|
+
# argument. We can't use `nil` for that because we need to warn when `nil` is
|
14
|
+
# passed in a different way. It's an Object, not a Module, since Module's `===`
|
15
|
+
# does not evaluate to true when compared to itself.
|
16
|
+
UndefinedValue = Object.new.freeze
|
17
|
+
|
18
|
+
def initialize(expected_error_or_message, expected_message, &block)
|
13
19
|
@block = block
|
14
20
|
@actual_error = nil
|
15
|
-
@warn_about_bare_error = expected_error_or_message
|
21
|
+
@warn_about_bare_error = UndefinedValue === expected_error_or_message
|
22
|
+
@warn_about_nil_error = expected_error_or_message.nil?
|
16
23
|
|
17
24
|
case expected_error_or_message
|
18
|
-
when nil
|
25
|
+
when nil, UndefinedValue
|
19
26
|
@expected_error = Exception
|
20
27
|
@expected_message = expected_message
|
21
28
|
when String
|
@@ -52,14 +59,17 @@ module RSpec
|
|
52
59
|
given_proc.call
|
53
60
|
rescue Exception => @actual_error
|
54
61
|
if values_match?(@expected_error, @actual_error) ||
|
55
|
-
values_match?(@expected_error,
|
62
|
+
values_match?(@expected_error, actual_error_message)
|
56
63
|
@raised_expected_error = true
|
57
64
|
@with_expected_message = verify_message
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
61
|
-
|
62
|
-
|
68
|
+
unless negative_expectation
|
69
|
+
warn_about_bare_error! if warn_about_bare_error?
|
70
|
+
warn_about_nil_error! if warn_about_nil_error?
|
71
|
+
eval_block if ready_to_eval_block?
|
72
|
+
end
|
63
73
|
|
64
74
|
expectation_matched?
|
65
75
|
end
|
@@ -67,7 +77,7 @@ module RSpec
|
|
67
77
|
|
68
78
|
# @private
|
69
79
|
def does_not_match?(given_proc)
|
70
|
-
|
80
|
+
warn_for_negative_false_positives!
|
71
81
|
!matches?(given_proc, :negative_expectation) && Proc === given_proc
|
72
82
|
end
|
73
83
|
|
@@ -83,7 +93,7 @@ module RSpec
|
|
83
93
|
# @api private
|
84
94
|
# @return [String]
|
85
95
|
def failure_message
|
86
|
-
@eval_block ?
|
96
|
+
@eval_block ? actual_error_message : "expected #{expected_error}#{given_error}"
|
87
97
|
end
|
88
98
|
|
89
99
|
# @api private
|
@@ -100,6 +110,12 @@ module RSpec
|
|
100
110
|
|
101
111
|
private
|
102
112
|
|
113
|
+
def actual_error_message
|
114
|
+
return nil unless @actual_error
|
115
|
+
|
116
|
+
@actual_error.respond_to?(:original_message) ? @actual_error.original_message : @actual_error.message
|
117
|
+
end
|
118
|
+
|
103
119
|
def expectation_matched?
|
104
120
|
error_and_message_match? && block_matches?
|
105
121
|
end
|
@@ -128,32 +144,38 @@ module RSpec
|
|
128
144
|
|
129
145
|
def verify_message
|
130
146
|
return true if @expected_message.nil?
|
131
|
-
values_match?(@expected_message,
|
147
|
+
values_match?(@expected_message, actual_error_message.to_s)
|
132
148
|
end
|
133
149
|
|
134
|
-
def
|
150
|
+
def warn_for_negative_false_positives!
|
135
151
|
expression = if expecting_specific_exception? && @expected_message
|
136
152
|
"`expect { }.not_to raise_error(SpecificErrorClass, message)`"
|
137
153
|
elsif expecting_specific_exception?
|
138
154
|
"`expect { }.not_to raise_error(SpecificErrorClass)`"
|
139
155
|
elsif @expected_message
|
140
156
|
"`expect { }.not_to raise_error(message)`"
|
157
|
+
elsif @warn_about_nil_error
|
158
|
+
"`expect { }.not_to raise_error(nil)`"
|
141
159
|
end
|
142
160
|
|
143
161
|
return unless expression
|
144
162
|
|
145
|
-
warn_about_negative_false_positive expression
|
163
|
+
warn_about_negative_false_positive! expression
|
146
164
|
end
|
147
165
|
|
148
166
|
def handle_warning(message)
|
149
167
|
RSpec::Expectations.configuration.false_positives_handler.call(message)
|
150
168
|
end
|
151
169
|
|
152
|
-
def
|
170
|
+
def warn_about_bare_error?
|
153
171
|
@warn_about_bare_error && @block.nil?
|
154
172
|
end
|
155
173
|
|
156
|
-
def
|
174
|
+
def warn_about_nil_error?
|
175
|
+
@warn_about_nil_error
|
176
|
+
end
|
177
|
+
|
178
|
+
def warn_about_bare_error!
|
157
179
|
handle_warning("Using the `raise_error` matcher without providing a specific " \
|
158
180
|
"error or message risks false positives, since `raise_error` " \
|
159
181
|
"will match when Ruby raises a `NoMethodError`, `NameError` or " \
|
@@ -166,11 +188,24 @@ module RSpec
|
|
166
188
|
"_positives = :nothing`")
|
167
189
|
end
|
168
190
|
|
169
|
-
def
|
191
|
+
def warn_about_nil_error!
|
192
|
+
handle_warning("Using the `raise_error` matcher with a `nil` error is probably " \
|
193
|
+
"unintentional, it risks false positives, since `raise_error` " \
|
194
|
+
"will match when Ruby raises a `NoMethodError`, `NameError` or " \
|
195
|
+
"`ArgumentError`, potentially allowing the expectation to pass " \
|
196
|
+
"without even executing the method you are intending to call. " \
|
197
|
+
"#{warning}"\
|
198
|
+
"Instead consider providing a specific error class or message. " \
|
199
|
+
"This message can be suppressed by setting: " \
|
200
|
+
"`RSpec::Expectations.configuration.on_potential_false" \
|
201
|
+
"_positives = :nothing`")
|
202
|
+
end
|
203
|
+
|
204
|
+
def warn_about_negative_false_positive!(expression)
|
170
205
|
handle_warning("Using #{expression} risks false positives, since literally " \
|
171
206
|
"any other error would cause the expectation to pass, " \
|
172
|
-
"including those raised by Ruby (e.g. NoMethodError
|
173
|
-
"and ArgumentError), meaning the code you are intending to test " \
|
207
|
+
"including those raised by Ruby (e.g. `NoMethodError`, `NameError` " \
|
208
|
+
"and `ArgumentError`), meaning the code you are intending to test " \
|
174
209
|
"may not even get reached. Instead consider using " \
|
175
210
|
"`expect { }.not_to raise_error` or `expect { }.to raise_error" \
|
176
211
|
"(DifferentSpecificErrorClass)`. This message can be suppressed by " \
|
@@ -1,7 +1,5 @@
|
|
1
1
|
RSpec::Support.require_rspec_support "method_signature_verifier"
|
2
2
|
|
3
|
-
# TODO: Refactor this file to be under our class length
|
4
|
-
# rubocop:disable ClassLength
|
5
3
|
module RSpec
|
6
4
|
module Matchers
|
7
5
|
module BuiltIn
|
@@ -118,49 +116,15 @@ module RSpec
|
|
118
116
|
end
|
119
117
|
end
|
120
118
|
|
121
|
-
def setup_method_signature_expectation
|
122
|
-
expectation = Support::MethodSignatureExpectation.new
|
123
|
-
|
124
|
-
if @expected_arity.is_a?(Range)
|
125
|
-
expectation.min_count = @expected_arity.min
|
126
|
-
expectation.max_count = @expected_arity.max
|
127
|
-
else
|
128
|
-
expectation.min_count = @expected_arity
|
129
|
-
end
|
130
|
-
|
131
|
-
expectation.keywords = @expected_keywords
|
132
|
-
expectation.expect_unlimited_arguments = @unlimited_arguments
|
133
|
-
expectation.expect_arbitrary_keywords = @arbitrary_keywords
|
134
|
-
|
135
|
-
expectation
|
136
|
-
end
|
137
|
-
|
138
119
|
def matches_arity?(actual, name)
|
139
|
-
|
140
|
-
|
141
|
-
return true if
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
return true if @ignoring_method_signature_failure
|
148
|
-
raise ArgumentError, "The #{matcher_name} matcher requires that " \
|
149
|
-
"the actual object define the method(s) in " \
|
150
|
-
"order to check arity, but the method " \
|
151
|
-
"`#{name}` is not defined. Remove the arity " \
|
152
|
-
"check or define the method to continue."
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def method_signature_for(actual, name)
|
157
|
-
method_handle = Support.method_handle_for(actual, name)
|
158
|
-
|
159
|
-
if name == :new && method_handle.owner === ::Class && ::Class === actual
|
160
|
-
Support::MethodSignature.new(actual.instance_method(:initialize))
|
161
|
-
else
|
162
|
-
Support::MethodSignature.new(method_handle)
|
163
|
-
end
|
120
|
+
ArityCheck.new(@expected_arity, @expected_keywords, @arbitrary_keywords, @unlimited_arguments).matches?(actual, name)
|
121
|
+
rescue NameError
|
122
|
+
return true if @ignoring_method_signature_failure
|
123
|
+
raise ArgumentError, "The #{matcher_name} matcher requires that " \
|
124
|
+
"the actual object define the method(s) in " \
|
125
|
+
"order to check arity, but the method " \
|
126
|
+
"`#{name}` is not defined. Remove the arity " \
|
127
|
+
"check or define the method to continue."
|
164
128
|
end
|
165
129
|
|
166
130
|
def with_arity
|
@@ -192,8 +156,45 @@ module RSpec
|
|
192
156
|
def pp_names
|
193
157
|
@names.length == 1 ? "##{@names.first}" : description_of(@names)
|
194
158
|
end
|
159
|
+
|
160
|
+
# @private
|
161
|
+
class ArityCheck
|
162
|
+
def initialize(expected_arity, expected_keywords, arbitrary_keywords, unlimited_arguments)
|
163
|
+
expectation = Support::MethodSignatureExpectation.new
|
164
|
+
|
165
|
+
if expected_arity.is_a?(Range)
|
166
|
+
expectation.min_count = expected_arity.min
|
167
|
+
expectation.max_count = expected_arity.max
|
168
|
+
else
|
169
|
+
expectation.min_count = expected_arity
|
170
|
+
end
|
171
|
+
|
172
|
+
expectation.keywords = expected_keywords
|
173
|
+
expectation.expect_unlimited_arguments = unlimited_arguments
|
174
|
+
expectation.expect_arbitrary_keywords = arbitrary_keywords
|
175
|
+
@expectation = expectation
|
176
|
+
end
|
177
|
+
|
178
|
+
def matches?(actual, name)
|
179
|
+
return true if @expectation.empty?
|
180
|
+
verifier_for(actual, name).with_expectation(@expectation).valid?
|
181
|
+
end
|
182
|
+
|
183
|
+
def verifier_for(actual, name)
|
184
|
+
Support::StrictSignatureVerifier.new(method_signature_for(actual, name))
|
185
|
+
end
|
186
|
+
|
187
|
+
def method_signature_for(actual, name)
|
188
|
+
method_handle = Support.method_handle_for(actual, name)
|
189
|
+
|
190
|
+
if name == :new && method_handle.owner === ::Class && ::Class === actual
|
191
|
+
Support::MethodSignature.new(actual.instance_method(:initialize))
|
192
|
+
else
|
193
|
+
Support::MethodSignature.new(method_handle)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
195
197
|
end
|
196
198
|
end
|
197
199
|
end
|
198
200
|
end
|
199
|
-
# rubocop:enable ClassLength
|