rspec-expectations 3.0.4 → 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/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +530 -5
- data/{License.txt → LICENSE.md} +5 -4
- data/README.md +73 -31
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +96 -1
- data/lib/rspec/expectations/expectation_target.rb +82 -38
- data/lib/rspec/expectations/fail_with.rb +11 -6
- data/lib/rspec/expectations/failure_aggregator.rb +229 -0
- data/lib/rspec/expectations/handler.rb +36 -15
- data/lib/rspec/expectations/minitest_integration.rb +43 -2
- data/lib/rspec/expectations/syntax.rb +5 -5
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/expectations.rb +15 -1
- data/lib/rspec/matchers/aliased_matcher.rb +79 -4
- data/lib/rspec/matchers/built_in/all.rb +11 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +111 -28
- data/lib/rspec/matchers/built_in/be.rb +28 -114
- data/lib/rspec/matchers/built_in/be_between.rb +1 -1
- 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 +5 -12
- data/lib/rspec/matchers/built_in/change.rb +171 -63
- data/lib/rspec/matchers/built_in/compound.rb +201 -30
- data/lib/rspec/matchers/built_in/contain_exactly.rb +73 -12
- data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
- data/lib/rspec/matchers/built_in/eq.rb +3 -38
- data/lib/rspec/matchers/built_in/eql.rb +2 -2
- data/lib/rspec/matchers/built_in/equal.rb +3 -3
- data/lib/rspec/matchers/built_in/exist.rb +7 -3
- data/lib/rspec/matchers/built_in/has.rb +93 -30
- data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
- data/lib/rspec/matchers/built_in/include.rb +133 -25
- data/lib/rspec/matchers/built_in/match.rb +79 -2
- data/lib/rspec/matchers/built_in/operators.rb +14 -5
- data/lib/rspec/matchers/built_in/output.rb +59 -2
- data/lib/rspec/matchers/built_in/raise_error.rb +130 -27
- data/lib/rspec/matchers/built_in/respond_to.rb +117 -15
- data/lib/rspec/matchers/built_in/satisfy.rb +28 -14
- data/lib/rspec/matchers/built_in/{start_and_end_with.rb → start_or_end_with.rb} +20 -8
- data/lib/rspec/matchers/built_in/throw_symbol.rb +15 -5
- data/lib/rspec/matchers/built_in/yield.rb +129 -156
- data/lib/rspec/matchers/built_in.rb +5 -3
- data/lib/rspec/matchers/composable.rb +24 -36
- data/lib/rspec/matchers/dsl.rb +203 -37
- data/lib/rspec/matchers/english_phrasing.rb +58 -0
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/generated_descriptions.rb +1 -2
- data/lib/rspec/matchers/matcher_delegator.rb +3 -4
- data/lib/rspec/matchers/matcher_protocol.rb +105 -0
- data/lib/rspec/matchers.rb +267 -144
- data.tar.gz.sig +0 -0
- metadata +71 -49
- metadata.gz.sig +0 -0
- data/lib/rspec/matchers/pretty.rb +0 -77
@@ -48,14 +48,14 @@ MESSAGE
|
|
48
48
|
|
49
49
|
def actual_inspected
|
50
50
|
if LITERAL_SINGLETONS.include?(actual)
|
51
|
-
|
51
|
+
actual_formatted
|
52
52
|
else
|
53
53
|
inspect_object(actual)
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
57
|
def simple_failure_message
|
58
|
-
"\nexpected #{
|
58
|
+
"\nexpected #{expected_formatted}\n got #{actual_inspected}\n"
|
59
59
|
end
|
60
60
|
|
61
61
|
def detailed_failure_message
|
@@ -73,7 +73,7 @@ MESSAGE
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def inspect_object(o)
|
76
|
-
"#<#{o.class}:#{o.object_id}> => #{o
|
76
|
+
"#<#{o.class}:#{o.object_id}> => #{RSpec::Support::ObjectFormatter.format(o)}"
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
@@ -28,13 +28,13 @@ module RSpec
|
|
28
28
|
# @api private
|
29
29
|
# @return [String]
|
30
30
|
def failure_message
|
31
|
-
"expected #{
|
31
|
+
"expected #{actual_formatted} to exist#{@test.validity_message}"
|
32
32
|
end
|
33
33
|
|
34
34
|
# @api private
|
35
35
|
# @return [String]
|
36
36
|
def failure_message_when_negated
|
37
|
-
"expected #{
|
37
|
+
"expected #{actual_formatted} not to exist#{@test.validity_message}"
|
38
38
|
end
|
39
39
|
|
40
40
|
# @api private
|
@@ -77,7 +77,11 @@ module RSpec
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def predicates
|
80
|
-
@predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) }
|
80
|
+
@predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def deprecated(predicate, actual)
|
84
|
+
predicate == :exists? && (File == actual || FileTest == actual || Dir == actual)
|
81
85
|
end
|
82
86
|
end
|
83
87
|
end
|
@@ -2,14 +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
|
-
include
|
5
|
+
# Provides the implementation for dynamic predicate matchers.
|
6
|
+
# Not intended to be inherited directly.
|
7
|
+
class DynamicPredicate < BaseMatcher
|
8
|
+
include BeHelpers
|
9
9
|
|
10
10
|
def initialize(method_name, *args, &block)
|
11
11
|
@method_name, @args, @block = method_name, args, block
|
12
12
|
end
|
13
|
+
ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
|
13
14
|
|
14
15
|
# @private
|
15
16
|
def matches?(actual, &block)
|
@@ -22,83 +23,145 @@ module RSpec
|
|
22
23
|
def does_not_match?(actual, &block)
|
23
24
|
@actual = actual
|
24
25
|
@block ||= block
|
25
|
-
predicate_accessible? &&
|
26
|
+
predicate_accessible? && predicate_matches?(false)
|
26
27
|
end
|
27
28
|
|
28
29
|
# @api private
|
29
30
|
# @return [String]
|
30
31
|
def failure_message
|
31
|
-
|
32
|
+
failure_message_expecting(true)
|
32
33
|
end
|
33
34
|
|
34
35
|
# @api private
|
35
36
|
# @return [String]
|
36
37
|
def failure_message_when_negated
|
37
|
-
|
38
|
+
failure_message_expecting(false)
|
38
39
|
end
|
39
40
|
|
40
41
|
# @api private
|
41
42
|
# @return [String]
|
42
43
|
def description
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
# @private
|
47
|
-
def supports_block_expectations?
|
48
|
-
false
|
44
|
+
"#{method_description}#{args_to_sentence}"
|
49
45
|
end
|
50
46
|
|
51
47
|
private
|
52
48
|
|
53
49
|
def predicate_accessible?
|
54
|
-
|
50
|
+
@actual.respond_to? predicate
|
55
51
|
end
|
56
52
|
|
57
53
|
# support 1.8.7, evaluate once at load time for performance
|
58
54
|
if String === methods.first
|
55
|
+
# :nocov:
|
59
56
|
def private_predicate?
|
60
57
|
@actual.private_methods.include? predicate.to_s
|
61
58
|
end
|
59
|
+
# :nocov:
|
62
60
|
else
|
63
61
|
def private_predicate?
|
64
62
|
@actual.private_methods.include? predicate
|
65
63
|
end
|
66
64
|
end
|
67
65
|
|
68
|
-
def
|
69
|
-
@actual.
|
66
|
+
def predicate_result
|
67
|
+
@predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
|
70
68
|
end
|
71
69
|
|
72
|
-
def
|
73
|
-
|
70
|
+
def predicate_method_name
|
71
|
+
predicate
|
74
72
|
end
|
75
73
|
|
76
|
-
def
|
77
|
-
|
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
|
83
|
+
# On 1.9, there appears to be a bug where String#match can return `false`
|
84
|
+
# rather than the match data object. Changing to Regex#match appears to
|
85
|
+
# work around this bug. For an example of this bug, see:
|
86
|
+
# https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
|
87
|
+
self.class::REGEX.match(@method_name.to_s).captures.first
|
78
88
|
end
|
79
89
|
|
80
90
|
def method_description
|
81
|
-
@method_name
|
91
|
+
EnglishPhrasing.split_words(@method_name)
|
82
92
|
end
|
83
93
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
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}"
|
87
97
|
end
|
88
98
|
|
89
|
-
def
|
90
|
-
|
91
|
-
|
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
|
92
107
|
end
|
93
108
|
|
94
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
|
95
116
|
if private_predicate?
|
96
|
-
"
|
97
|
-
elsif !predicate_exists?
|
98
|
-
"expected #{@actual} to respond to `#{predicate}`"
|
117
|
+
" but `#{predicate}` is a private method"
|
99
118
|
end
|
100
119
|
end
|
101
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
|
102
165
|
end
|
103
166
|
end
|
104
167
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `have_attributes`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class HaveAttributes < BaseMatcher
|
8
|
+
# @private
|
9
|
+
attr_reader :respond_to_failed
|
10
|
+
|
11
|
+
def initialize(expected)
|
12
|
+
@expected = expected
|
13
|
+
@values = {}
|
14
|
+
@respond_to_failed = false
|
15
|
+
@negated = false
|
16
|
+
end
|
17
|
+
|
18
|
+
# @private
|
19
|
+
def actual
|
20
|
+
@values
|
21
|
+
end
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
# @return [Boolean]
|
25
|
+
def matches?(actual)
|
26
|
+
@actual = actual
|
27
|
+
@negated = false
|
28
|
+
return false unless respond_to_attributes?
|
29
|
+
perform_match(:all?)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
# @return [Boolean]
|
34
|
+
def does_not_match?(actual)
|
35
|
+
@actual = actual
|
36
|
+
@negated = true
|
37
|
+
return false unless respond_to_attributes?
|
38
|
+
perform_match(:none?)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
# @return [String]
|
43
|
+
def description
|
44
|
+
described_items = surface_descriptions_in(expected)
|
45
|
+
improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
# @return [Boolean]
|
50
|
+
def diffable?
|
51
|
+
!@respond_to_failed && !@negated
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @return [String]
|
56
|
+
def failure_message
|
57
|
+
respond_to_failure_message_or do
|
58
|
+
"expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
# @return [String]
|
64
|
+
def failure_message_when_negated
|
65
|
+
respond_to_failure_message_or { "expected #{actual_formatted} not to #{description}" }
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def cache_all_values
|
71
|
+
@values = {}
|
72
|
+
expected.each do |attribute_key, _attribute_value|
|
73
|
+
actual_value = @actual.__send__(attribute_key)
|
74
|
+
@values[attribute_key] = actual_value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def perform_match(predicate)
|
79
|
+
cache_all_values
|
80
|
+
expected.__send__(predicate) do |attribute_key, attribute_value|
|
81
|
+
actual_has_attribute?(attribute_key, attribute_value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def actual_has_attribute?(attribute_key, attribute_value)
|
86
|
+
values_match?(attribute_value, @values.fetch(attribute_key))
|
87
|
+
end
|
88
|
+
|
89
|
+
def respond_to_attributes?
|
90
|
+
matches = respond_to_matcher.matches?(@actual)
|
91
|
+
@respond_to_failed = !matches
|
92
|
+
matches
|
93
|
+
end
|
94
|
+
|
95
|
+
def respond_to_matcher
|
96
|
+
@respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments.tap { |m| m.ignoring_method_signature_failure! }
|
97
|
+
end
|
98
|
+
|
99
|
+
def respond_to_failure_message_or
|
100
|
+
if respond_to_failed
|
101
|
+
respond_to_matcher.failure_message
|
102
|
+
else
|
103
|
+
improve_hash_formatting(yield)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def formatted_values
|
108
|
+
values = RSpec::Support::ObjectFormatter.format(@values)
|
109
|
+
improve_hash_formatting(values)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -1,73 +1,136 @@
|
|
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
|
8
|
-
|
9
|
-
|
9
|
+
class Include < BaseMatcher # rubocop:disable Metrics/ClassLength
|
10
|
+
include CountExpectation
|
11
|
+
# @private
|
12
|
+
attr_reader :expecteds
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def initialize(*expecteds)
|
16
|
+
@expecteds = expecteds
|
10
17
|
end
|
11
18
|
|
12
19
|
# @api private
|
13
20
|
# @return [Boolean]
|
14
21
|
def matches?(actual)
|
15
|
-
|
16
|
-
|
22
|
+
check_actual?(actual) &&
|
23
|
+
if check_expected_count?
|
24
|
+
expected_count_matches?(count_inclusions)
|
25
|
+
else
|
26
|
+
perform_match { |v| v }
|
27
|
+
end
|
17
28
|
end
|
18
29
|
|
19
30
|
# @api private
|
20
31
|
# @return [Boolean]
|
21
32
|
def does_not_match?(actual)
|
22
|
-
|
23
|
-
|
33
|
+
check_actual?(actual) &&
|
34
|
+
if check_expected_count?
|
35
|
+
!expected_count_matches?(count_inclusions)
|
36
|
+
else
|
37
|
+
perform_match { |v| !v }
|
38
|
+
end
|
24
39
|
end
|
25
40
|
|
26
41
|
# @api private
|
27
42
|
# @return [String]
|
28
43
|
def description
|
29
|
-
|
30
|
-
improve_hash_formatting "include#{to_sentence(described_items)}"
|
44
|
+
improve_hash_formatting("include#{readable_list_of(expecteds)}#{count_expectation_description}")
|
31
45
|
end
|
32
46
|
|
33
47
|
# @api private
|
34
48
|
# @return [String]
|
35
49
|
def failure_message
|
36
|
-
|
50
|
+
format_failure_message("to") { super }
|
37
51
|
end
|
38
52
|
|
39
53
|
# @api private
|
40
54
|
# @return [String]
|
41
55
|
def failure_message_when_negated
|
42
|
-
|
56
|
+
format_failure_message("not to") { super }
|
43
57
|
end
|
44
58
|
|
45
59
|
# @api private
|
46
60
|
# @return [Boolean]
|
47
61
|
def diffable?
|
48
|
-
|
62
|
+
!diff_would_wrongly_highlight_matched_item?
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
# @return [Array, Hash]
|
67
|
+
def expected
|
68
|
+
if expecteds.one? && Hash === expecteds.first
|
69
|
+
expecteds.first
|
70
|
+
else
|
71
|
+
expecteds
|
72
|
+
end
|
49
73
|
end
|
50
74
|
|
51
75
|
private
|
52
76
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
77
|
+
def check_actual?(actual)
|
78
|
+
actual = actual.to_hash if convert_to_hash?(actual)
|
79
|
+
@actual = actual
|
80
|
+
@actual.respond_to?(:include?)
|
56
81
|
end
|
57
82
|
|
58
|
-
def
|
59
|
-
|
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'
|
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)
|
104
|
+
end
|
60
105
|
|
61
|
-
|
106
|
+
def readable_list_of(items)
|
107
|
+
described_items = surface_descriptions_in(items)
|
108
|
+
if described_items.all? { |item| item.is_a?(Hash) }
|
109
|
+
" #{described_items.inject(:merge).inspect}"
|
110
|
+
else
|
111
|
+
EnglishPhrasing.list(described_items)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def perform_match(&block)
|
116
|
+
@divergent_items = excluded_from_actual(&block)
|
117
|
+
@divergent_items.empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
def excluded_from_actual
|
121
|
+
return [] unless @actual.respond_to?(:include?)
|
122
|
+
|
123
|
+
expecteds.inject([]) do |memo, expected_item|
|
62
124
|
if comparing_hash_to_a_subset?(expected_item)
|
63
|
-
expected_item.
|
64
|
-
actual_hash_includes?(key, value)
|
125
|
+
expected_item.each do |(key, value)|
|
126
|
+
memo << { key => value } unless yield actual_hash_includes?(key, value)
|
65
127
|
end
|
66
128
|
elsif comparing_hash_keys?(expected_item)
|
67
|
-
actual_hash_has_key?(expected_item)
|
129
|
+
memo << expected_item unless yield actual_hash_has_key?(expected_item)
|
68
130
|
else
|
69
|
-
actual_collection_includes?(expected_item)
|
131
|
+
memo << expected_item unless yield actual_collection_includes?(expected_item)
|
70
132
|
end
|
133
|
+
memo
|
71
134
|
end
|
72
135
|
end
|
73
136
|
|
@@ -76,7 +139,10 @@ module RSpec
|
|
76
139
|
end
|
77
140
|
|
78
141
|
def actual_hash_includes?(expected_key, expected_value)
|
79
|
-
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
|
80
146
|
values_match?(expected_value, actual_value)
|
81
147
|
end
|
82
148
|
|
@@ -87,8 +153,15 @@ module RSpec
|
|
87
153
|
def actual_hash_has_key?(expected_key)
|
88
154
|
# We check `key?` first for perf:
|
89
155
|
# `key?` is O(1), but `any?` is O(N).
|
90
|
-
|
91
|
-
|
156
|
+
|
157
|
+
has_exact_key =
|
158
|
+
begin
|
159
|
+
actual.key?(expected_key)
|
160
|
+
rescue
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
164
|
+
has_exact_key || actual.keys.any? { |key| values_match?(expected_key, key) }
|
92
165
|
end
|
93
166
|
|
94
167
|
def actual_collection_includes?(expected_item)
|
@@ -99,6 +172,41 @@ module RSpec
|
|
99
172
|
|
100
173
|
actual.any? { |value| values_match?(expected_item, value) }
|
101
174
|
end
|
175
|
+
|
176
|
+
if RUBY_VERSION < '1.9'
|
177
|
+
def count_enumerable(expected_item)
|
178
|
+
actual.select { |value| values_match?(expected_item, value) }.size
|
179
|
+
end
|
180
|
+
else
|
181
|
+
def count_enumerable(expected_item)
|
182
|
+
actual.count { |value| values_match?(expected_item, value) }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def count_inclusions
|
187
|
+
@divergent_items = expected
|
188
|
+
case actual
|
189
|
+
when String
|
190
|
+
actual.scan(expected.first).length
|
191
|
+
when Enumerable
|
192
|
+
count_enumerable(Hash === expected ? expected : expected.first)
|
193
|
+
else
|
194
|
+
raise NotImplementedError, 'Count constraints are implemented for Enumerable and String values only'
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def diff_would_wrongly_highlight_matched_item?
|
199
|
+
return false unless actual.is_a?(String) && expected.is_a?(Array)
|
200
|
+
|
201
|
+
lines = actual.split("\n")
|
202
|
+
expected.any? do |str|
|
203
|
+
actual.include?(str) && lines.none? { |line| line == str }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def convert_to_hash?(obj)
|
208
|
+
!obj.respond_to?(:include?) && obj.respond_to?(:to_hash)
|
209
|
+
end
|
102
210
|
end
|
103
211
|
end
|
104
212
|
end
|
@@ -5,10 +5,19 @@ module RSpec
|
|
5
5
|
# Provides the implementation for `match`.
|
6
6
|
# Not intended to be instantiated directly.
|
7
7
|
class Match < BaseMatcher
|
8
|
+
def initialize(expected)
|
9
|
+
super(expected)
|
10
|
+
|
11
|
+
@expected_captures = nil
|
12
|
+
end
|
8
13
|
# @api private
|
9
14
|
# @return [String]
|
10
15
|
def description
|
11
|
-
|
16
|
+
if @expected_captures && @expected.match(actual)
|
17
|
+
"match #{surface_descriptions_in(expected).inspect} with captures #{surface_descriptions_in(@expected_captures).inspect}"
|
18
|
+
else
|
19
|
+
"match #{surface_descriptions_in(expected).inspect}"
|
20
|
+
end
|
12
21
|
end
|
13
22
|
|
14
23
|
# @api private
|
@@ -17,12 +26,80 @@ module RSpec
|
|
17
26
|
true
|
18
27
|
end
|
19
28
|
|
29
|
+
# Used to specify the captures we match against
|
30
|
+
# @return [self]
|
31
|
+
def with_captures(*captures)
|
32
|
+
@expected_captures = captures
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
20
36
|
private
|
21
37
|
|
22
38
|
def match(expected, actual)
|
39
|
+
return match_captures(expected, actual) if @expected_captures
|
23
40
|
return true if values_match?(expected, actual)
|
24
|
-
|
41
|
+
return false unless can_safely_call_match?(expected, actual)
|
42
|
+
actual.match(expected)
|
43
|
+
end
|
44
|
+
|
45
|
+
def can_safely_call_match?(expected, actual)
|
46
|
+
return false unless actual.respond_to?(:match)
|
47
|
+
|
48
|
+
!(RSpec::Matchers.is_a_matcher?(expected) &&
|
49
|
+
(String === actual || Regexp === actual))
|
50
|
+
end
|
51
|
+
|
52
|
+
def match_captures(expected, actual)
|
53
|
+
match = actual.match(expected)
|
54
|
+
if match
|
55
|
+
match = ReliableMatchData.new(match)
|
56
|
+
if match.names.empty?
|
57
|
+
values_match?(@expected_captures, match.captures)
|
58
|
+
else
|
59
|
+
expected_matcher = @expected_captures.last
|
60
|
+
values_match?(expected_matcher, Hash[match.names.zip(match.captures)]) ||
|
61
|
+
values_match?(expected_matcher, Hash[match.names.map(&:to_sym).zip(match.captures)]) ||
|
62
|
+
values_match?(@expected_captures, match.captures)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# @api private
|
71
|
+
# Used to wrap match data and make it reliable for 1.8.7
|
72
|
+
class ReliableMatchData
|
73
|
+
def initialize(match_data)
|
74
|
+
@match_data = match_data
|
75
|
+
end
|
76
|
+
|
77
|
+
if RUBY_VERSION == "1.8.7"
|
78
|
+
# @api private
|
79
|
+
# Returns match data names for named captures
|
80
|
+
# @return Array
|
81
|
+
def names
|
82
|
+
[]
|
83
|
+
end
|
84
|
+
else
|
85
|
+
# @api private
|
86
|
+
# Returns match data names for named captures
|
87
|
+
# @return Array
|
88
|
+
def names
|
89
|
+
match_data.names
|
90
|
+
end
|
25
91
|
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
# returns an array of captures from the match data
|
95
|
+
# @return Array
|
96
|
+
def captures
|
97
|
+
match_data.captures
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
attr_reader :match_data
|
26
103
|
end
|
27
104
|
end
|
28
105
|
end
|