rspec-expectations 3.8.6
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.tar.gz.sig +5 -0
- data/.document +5 -0
- data/.yardopts +6 -0
- data/Changelog.md +1156 -0
- data/LICENSE.md +25 -0
- data/README.md +305 -0
- data/lib/rspec/expectations.rb +82 -0
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +215 -0
- data/lib/rspec/expectations/expectation_target.rb +127 -0
- data/lib/rspec/expectations/fail_with.rb +39 -0
- data/lib/rspec/expectations/failure_aggregator.rb +194 -0
- data/lib/rspec/expectations/handler.rb +170 -0
- data/lib/rspec/expectations/minitest_integration.rb +58 -0
- data/lib/rspec/expectations/syntax.rb +132 -0
- data/lib/rspec/expectations/version.rb +8 -0
- data/lib/rspec/matchers.rb +1034 -0
- data/lib/rspec/matchers/aliased_matcher.rb +116 -0
- data/lib/rspec/matchers/built_in.rb +52 -0
- data/lib/rspec/matchers/built_in/all.rb +86 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +193 -0
- data/lib/rspec/matchers/built_in/be.rb +288 -0
- data/lib/rspec/matchers/built_in/be_between.rb +77 -0
- data/lib/rspec/matchers/built_in/be_instance_of.rb +26 -0
- data/lib/rspec/matchers/built_in/be_kind_of.rb +20 -0
- data/lib/rspec/matchers/built_in/be_within.rb +72 -0
- data/lib/rspec/matchers/built_in/change.rb +428 -0
- data/lib/rspec/matchers/built_in/compound.rb +271 -0
- data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
- data/lib/rspec/matchers/built_in/cover.rb +24 -0
- data/lib/rspec/matchers/built_in/eq.rb +40 -0
- data/lib/rspec/matchers/built_in/eql.rb +34 -0
- data/lib/rspec/matchers/built_in/equal.rb +81 -0
- data/lib/rspec/matchers/built_in/exist.rb +90 -0
- data/lib/rspec/matchers/built_in/has.rb +103 -0
- data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
- data/lib/rspec/matchers/built_in/include.rb +149 -0
- data/lib/rspec/matchers/built_in/match.rb +106 -0
- data/lib/rspec/matchers/built_in/operators.rb +128 -0
- data/lib/rspec/matchers/built_in/output.rb +200 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +230 -0
- data/lib/rspec/matchers/built_in/respond_to.rb +165 -0
- data/lib/rspec/matchers/built_in/satisfy.rb +60 -0
- data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
- data/lib/rspec/matchers/built_in/throw_symbol.rb +132 -0
- data/lib/rspec/matchers/built_in/yield.rb +432 -0
- data/lib/rspec/matchers/composable.rb +171 -0
- data/lib/rspec/matchers/dsl.rb +527 -0
- data/lib/rspec/matchers/english_phrasing.rb +58 -0
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +73 -0
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/generated_descriptions.rb +41 -0
- data/lib/rspec/matchers/matcher_delegator.rb +35 -0
- data/lib/rspec/matchers/matcher_protocol.rb +99 -0
- metadata +215 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
RSpec::Support.require_rspec_support "method_signature_verifier"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Matchers
|
5
|
+
module BuiltIn
|
6
|
+
# @api private
|
7
|
+
# Provides the implementation for `respond_to`.
|
8
|
+
# Not intended to be instantiated directly.
|
9
|
+
class RespondTo < BaseMatcher
|
10
|
+
def initialize(*names)
|
11
|
+
@names = names
|
12
|
+
@expected_arity = nil
|
13
|
+
@expected_keywords = []
|
14
|
+
@unlimited_arguments = nil
|
15
|
+
@arbitrary_keywords = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api public
|
19
|
+
# Specifies the number of expected arguments.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# expect(obj).to respond_to(:message).with(3).arguments
|
23
|
+
def with(n)
|
24
|
+
@expected_arity = n
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api public
|
29
|
+
# Specifies keyword arguments, if any.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# expect(obj).to respond_to(:message).with_keywords(:color, :shape)
|
33
|
+
# @example with an expected number of arguments
|
34
|
+
# expect(obj).to respond_to(:message).with(3).arguments.and_keywords(:color, :shape)
|
35
|
+
def with_keywords(*keywords)
|
36
|
+
@expected_keywords = keywords
|
37
|
+
self
|
38
|
+
end
|
39
|
+
alias :and_keywords :with_keywords
|
40
|
+
|
41
|
+
# @api public
|
42
|
+
# Specifies that the method accepts any keyword, i.e. the method has
|
43
|
+
# a splatted keyword parameter of the form **kw_args.
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# expect(obj).to respond_to(:message).with_any_keywords
|
47
|
+
def with_any_keywords
|
48
|
+
@arbitrary_keywords = true
|
49
|
+
self
|
50
|
+
end
|
51
|
+
alias :and_any_keywords :with_any_keywords
|
52
|
+
|
53
|
+
# @api public
|
54
|
+
# Specifies that the number of arguments has no upper limit, i.e. the
|
55
|
+
# method has a splatted parameter of the form *args.
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# expect(obj).to respond_to(:message).with_unlimited_arguments
|
59
|
+
def with_unlimited_arguments
|
60
|
+
@unlimited_arguments = true
|
61
|
+
self
|
62
|
+
end
|
63
|
+
alias :and_unlimited_arguments :with_unlimited_arguments
|
64
|
+
|
65
|
+
# @api public
|
66
|
+
# No-op. Intended to be used as syntactic sugar when using `with`.
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# expect(obj).to respond_to(:message).with(3).arguments
|
70
|
+
def argument
|
71
|
+
self
|
72
|
+
end
|
73
|
+
alias :arguments :argument
|
74
|
+
|
75
|
+
# @private
|
76
|
+
def matches?(actual)
|
77
|
+
find_failing_method_names(actual, :reject).empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
# @private
|
81
|
+
def does_not_match?(actual)
|
82
|
+
find_failing_method_names(actual, :select).empty?
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
# @return [String]
|
87
|
+
def failure_message
|
88
|
+
"expected #{actual_formatted} to respond to #{@failing_method_names.map { |name| description_of(name) }.join(', ')}#{with_arity}"
|
89
|
+
end
|
90
|
+
|
91
|
+
# @api private
|
92
|
+
# @return [String]
|
93
|
+
def failure_message_when_negated
|
94
|
+
failure_message.sub(/to respond to/, 'not to respond to')
|
95
|
+
end
|
96
|
+
|
97
|
+
# @api private
|
98
|
+
# @return [String]
|
99
|
+
def description
|
100
|
+
"respond to #{pp_names}#{with_arity}"
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def find_failing_method_names(actual, filter_method)
|
106
|
+
@actual = actual
|
107
|
+
@failing_method_names = @names.__send__(filter_method) do |name|
|
108
|
+
@actual.respond_to?(name) && matches_arity?(actual, name)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def matches_arity?(actual, name)
|
113
|
+
expectation = Support::MethodSignatureExpectation.new
|
114
|
+
|
115
|
+
if @expected_arity.is_a?(Range)
|
116
|
+
expectation.min_count = @expected_arity.min
|
117
|
+
expectation.max_count = @expected_arity.max
|
118
|
+
else
|
119
|
+
expectation.min_count = @expected_arity
|
120
|
+
end
|
121
|
+
|
122
|
+
expectation.keywords = @expected_keywords
|
123
|
+
expectation.expect_unlimited_arguments = @unlimited_arguments
|
124
|
+
expectation.expect_arbitrary_keywords = @arbitrary_keywords
|
125
|
+
|
126
|
+
return true if expectation.empty?
|
127
|
+
|
128
|
+
signature = Support::MethodSignature.new(Support.method_handle_for(actual, name))
|
129
|
+
|
130
|
+
Support::StrictSignatureVerifier.new(signature).with_expectation(expectation).valid?
|
131
|
+
end
|
132
|
+
|
133
|
+
def with_arity
|
134
|
+
str = ''.dup
|
135
|
+
str << " with #{with_arity_string}" if @expected_arity
|
136
|
+
str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0
|
137
|
+
str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments
|
138
|
+
str << " #{str.length == 0 ? 'with' : 'and'} any keywords" if @arbitrary_keywords
|
139
|
+
str
|
140
|
+
end
|
141
|
+
|
142
|
+
def with_arity_string
|
143
|
+
"#{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def with_keywords_string
|
147
|
+
kw_str = case @expected_keywords.count
|
148
|
+
when 1
|
149
|
+
@expected_keywords.first.inspect
|
150
|
+
when 2
|
151
|
+
@expected_keywords.map(&:inspect).join(' and ')
|
152
|
+
else
|
153
|
+
"#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}"
|
154
|
+
end
|
155
|
+
|
156
|
+
"keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def pp_names
|
160
|
+
@names.length == 1 ? "##{@names.first}" : description_of(@names)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `satisfy`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class Satisfy < BaseMatcher
|
8
|
+
def initialize(description=nil, &block)
|
9
|
+
@description = description
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def matches?(actual, &block)
|
15
|
+
@block = block if block
|
16
|
+
@actual = actual
|
17
|
+
@block.call(actual)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @private
|
21
|
+
def description
|
22
|
+
@description ||= "satisfy #{block_representation}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
# @return [String]
|
27
|
+
def failure_message
|
28
|
+
"expected #{actual_formatted} to #{description}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
# @return [String]
|
33
|
+
def failure_message_when_negated
|
34
|
+
"expected #{actual_formatted} not to #{description}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
40
|
+
def block_representation
|
41
|
+
if (block_snippet = extract_block_snippet)
|
42
|
+
"expression `#{block_snippet}`"
|
43
|
+
else
|
44
|
+
'block'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_block_snippet
|
49
|
+
return nil unless @block
|
50
|
+
Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
def block_representation
|
54
|
+
'block'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Base class for the `end_with` and `start_with` matchers.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class StartOrEndWith < BaseMatcher
|
8
|
+
def initialize(*expected)
|
9
|
+
@actual_does_not_have_ordered_elements = false
|
10
|
+
@expected = expected.length == 1 ? expected.first : expected
|
11
|
+
end
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
# @return [String]
|
15
|
+
def failure_message
|
16
|
+
super.tap do |msg|
|
17
|
+
if @actual_does_not_have_ordered_elements
|
18
|
+
msg << ", but it does not have ordered elements"
|
19
|
+
elsif !actual.respond_to?(:[])
|
20
|
+
msg << ", but it cannot be indexed using #[]"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
# @return [String]
|
27
|
+
def description
|
28
|
+
return super unless Hash === expected
|
29
|
+
english_name = EnglishPhrasing.split_words(self.class.matcher_name)
|
30
|
+
description_of_expected = surface_descriptions_in(expected).inspect
|
31
|
+
"#{english_name} #{description_of_expected}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def match(_expected, actual)
|
37
|
+
return false unless actual.respond_to?(:[])
|
38
|
+
|
39
|
+
begin
|
40
|
+
return true if subsets_comparable? && subset_matches?
|
41
|
+
element_matches?
|
42
|
+
rescue ArgumentError
|
43
|
+
@actual_does_not_have_ordered_elements = true
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def subsets_comparable?
|
49
|
+
# Structs support the Enumerable interface but don't really have
|
50
|
+
# the semantics of a subset of a larger set...
|
51
|
+
return false if Struct === expected
|
52
|
+
|
53
|
+
expected.respond_to?(:length)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# For RSpec 3.1, the base class was named `StartAndEndWith`. For SemVer reasons,
|
58
|
+
# we still provide this constant until 4.0.
|
59
|
+
# @deprecated Use StartOrEndWith instead.
|
60
|
+
# @private
|
61
|
+
StartAndEndWith = StartOrEndWith
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
# Provides the implementation for `start_with`.
|
65
|
+
# Not intended to be instantiated directly.
|
66
|
+
class StartWith < StartOrEndWith
|
67
|
+
private
|
68
|
+
|
69
|
+
def subset_matches?
|
70
|
+
values_match?(expected, actual[0, expected.length])
|
71
|
+
end
|
72
|
+
|
73
|
+
def element_matches?
|
74
|
+
values_match?(expected, actual[0])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
# Provides the implementation for `end_with`.
|
80
|
+
# Not intended to be instantiated directly.
|
81
|
+
class EndWith < StartOrEndWith
|
82
|
+
private
|
83
|
+
|
84
|
+
def subset_matches?
|
85
|
+
values_match?(expected, actual[-expected.length, expected.length])
|
86
|
+
end
|
87
|
+
|
88
|
+
def element_matches?
|
89
|
+
values_match?(expected, actual[-1])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `throw_symbol`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class ThrowSymbol
|
8
|
+
include Composable
|
9
|
+
|
10
|
+
def initialize(expected_symbol=nil, expected_arg=nil)
|
11
|
+
@expected_symbol = expected_symbol
|
12
|
+
@expected_arg = expected_arg
|
13
|
+
@caught_symbol = @caught_arg = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# rubocop:disable MethodLength
|
17
|
+
# @private
|
18
|
+
def matches?(given_proc)
|
19
|
+
@block = given_proc
|
20
|
+
return false unless Proc === given_proc
|
21
|
+
|
22
|
+
begin
|
23
|
+
if @expected_symbol.nil?
|
24
|
+
given_proc.call
|
25
|
+
else
|
26
|
+
@caught_arg = catch :proc_did_not_throw_anything do
|
27
|
+
catch @expected_symbol do
|
28
|
+
given_proc.call
|
29
|
+
throw :proc_did_not_throw_anything, :nothing_thrown
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if @caught_arg == :nothing_thrown
|
34
|
+
@caught_arg = nil
|
35
|
+
else
|
36
|
+
@caught_symbol = @expected_symbol
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Ruby 1.8 uses NameError with `symbol'
|
41
|
+
# Ruby 1.9 uses ArgumentError with :symbol
|
42
|
+
rescue NameError, ArgumentError => e
|
43
|
+
unless (match_data = e.message.match(/uncaught throw (`|\:)([a-zA-Z0-9_]*)(')?/))
|
44
|
+
other_exception = e
|
45
|
+
raise
|
46
|
+
end
|
47
|
+
@caught_symbol = match_data.captures[1].to_sym
|
48
|
+
rescue => other_exception
|
49
|
+
raise
|
50
|
+
ensure
|
51
|
+
# rubocop:disable EnsureReturn
|
52
|
+
unless other_exception
|
53
|
+
if @expected_symbol.nil?
|
54
|
+
return !!@caught_symbol
|
55
|
+
else
|
56
|
+
if @expected_arg.nil?
|
57
|
+
return @caught_symbol == @expected_symbol
|
58
|
+
else
|
59
|
+
return (@caught_symbol == @expected_symbol) && values_match?(@expected_arg, @caught_arg)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
# rubocop:enable EnsureReturn
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rubocop:enable MethodLength
|
67
|
+
|
68
|
+
def does_not_match?(given_proc)
|
69
|
+
!matches?(given_proc) && Proc === given_proc
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
# @return [String]
|
74
|
+
def failure_message
|
75
|
+
"expected #{expected} to be thrown, #{actual_result}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
# @return [String]
|
80
|
+
def failure_message_when_negated
|
81
|
+
"expected #{expected('no Symbol')}#{' not' if @expected_symbol} to be thrown, #{actual_result}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# @api private
|
85
|
+
# @return [String]
|
86
|
+
def description
|
87
|
+
"throw #{expected}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# @api private
|
91
|
+
# Indicates this matcher matches against a block.
|
92
|
+
# @return [True]
|
93
|
+
def supports_block_expectations?
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
def expects_call_stack_jump?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def actual_result
|
104
|
+
return "but was not a block" unless Proc === @block
|
105
|
+
"got #{caught}"
|
106
|
+
end
|
107
|
+
|
108
|
+
def expected(symbol_desc='a Symbol')
|
109
|
+
throw_description(@expected_symbol || symbol_desc, @expected_arg)
|
110
|
+
end
|
111
|
+
|
112
|
+
def caught
|
113
|
+
throw_description(@caught_symbol || 'nothing', @caught_arg)
|
114
|
+
end
|
115
|
+
|
116
|
+
def throw_description(symbol, arg)
|
117
|
+
symbol_description = symbol.is_a?(String) ? symbol : description_of(symbol)
|
118
|
+
|
119
|
+
arg_description = if arg
|
120
|
+
" with #{description_of arg}"
|
121
|
+
elsif @expected_arg && @caught_symbol == @expected_symbol
|
122
|
+
" with no argument"
|
123
|
+
else
|
124
|
+
""
|
125
|
+
end
|
126
|
+
|
127
|
+
symbol_description + arg_description
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|