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.
Files changed (155) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +976 -25
  6. data/{License.txt → LICENSE.md} +5 -3
  7. data/README.md +162 -26
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +230 -0
  10. data/lib/rspec/expectations/expectation_target.rb +127 -51
  11. data/lib/rspec/expectations/fail_with.rb +17 -57
  12. data/lib/rspec/expectations/failure_aggregator.rb +229 -0
  13. data/lib/rspec/expectations/handler.rb +146 -32
  14. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  15. data/lib/rspec/expectations/syntax.rb +68 -100
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +58 -23
  18. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  19. data/lib/rspec/matchers/built_in/all.rb +86 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +191 -20
  21. data/lib/rspec/matchers/built_in/be.rb +114 -114
  22. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +15 -4
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +10 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +35 -18
  26. data/lib/rspec/matchers/built_in/change.rb +389 -80
  27. data/lib/rspec/matchers/built_in/compound.rb +290 -0
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +310 -0
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/cover.rb +3 -0
  31. data/lib/rspec/matchers/built_in/eq.rb +30 -8
  32. data/lib/rspec/matchers/built_in/eql.rb +23 -8
  33. data/lib/rspec/matchers/built_in/equal.rb +55 -22
  34. data/lib/rspec/matchers/built_in/exist.rb +74 -10
  35. data/lib/rspec/matchers/built_in/has.rb +141 -22
  36. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  37. data/lib/rspec/matchers/built_in/include.rb +184 -32
  38. data/lib/rspec/matchers/built_in/match.rb +95 -1
  39. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  40. data/lib/rspec/matchers/built_in/output.rb +207 -0
  41. data/lib/rspec/matchers/built_in/raise_error.rb +192 -44
  42. data/lib/rspec/matchers/built_in/respond_to.rb +154 -28
  43. data/lib/rspec/matchers/built_in/satisfy.rb +39 -9
  44. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  45. data/lib/rspec/matchers/built_in/throw_symbol.rb +58 -14
  46. data/lib/rspec/matchers/built_in/yield.rb +240 -161
  47. data/lib/rspec/matchers/built_in.rb +47 -33
  48. data/lib/rspec/matchers/composable.rb +171 -0
  49. data/lib/rspec/matchers/dsl.rb +531 -10
  50. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  51. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  52. data/lib/rspec/matchers/generated_descriptions.rb +14 -8
  53. data/lib/rspec/matchers/matcher_delegator.rb +61 -0
  54. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  55. data/lib/rspec/matchers/multi_matcher_diff.rb +82 -0
  56. data/lib/rspec/matchers.rb +520 -173
  57. data.tar.gz.sig +0 -0
  58. metadata +141 -242
  59. metadata.gz.sig +2 -0
  60. data/features/README.md +0 -48
  61. data/features/Upgrade.md +0 -53
  62. data/features/built_in_matchers/README.md +0 -90
  63. data/features/built_in_matchers/be.feature +0 -175
  64. data/features/built_in_matchers/be_within.feature +0 -48
  65. data/features/built_in_matchers/cover.feature +0 -47
  66. data/features/built_in_matchers/end_with.feature +0 -48
  67. data/features/built_in_matchers/equality.feature +0 -139
  68. data/features/built_in_matchers/exist.feature +0 -45
  69. data/features/built_in_matchers/expect_change.feature +0 -59
  70. data/features/built_in_matchers/expect_error.feature +0 -144
  71. data/features/built_in_matchers/have.feature +0 -109
  72. data/features/built_in_matchers/include.feature +0 -174
  73. data/features/built_in_matchers/match.feature +0 -52
  74. data/features/built_in_matchers/operators.feature +0 -227
  75. data/features/built_in_matchers/predicates.feature +0 -137
  76. data/features/built_in_matchers/respond_to.feature +0 -84
  77. data/features/built_in_matchers/satisfy.feature +0 -33
  78. data/features/built_in_matchers/start_with.feature +0 -48
  79. data/features/built_in_matchers/throw_symbol.feature +0 -91
  80. data/features/built_in_matchers/types.feature +0 -116
  81. data/features/built_in_matchers/yield.feature +0 -161
  82. data/features/custom_matchers/access_running_example.feature +0 -53
  83. data/features/custom_matchers/define_diffable_matcher.feature +0 -27
  84. data/features/custom_matchers/define_matcher.feature +0 -368
  85. data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
  86. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
  87. data/features/customized_message.feature +0 -22
  88. data/features/diffing.feature +0 -85
  89. data/features/implicit_docstrings.feature +0 -52
  90. data/features/step_definitions/additional_cli_steps.rb +0 -22
  91. data/features/support/env.rb +0 -14
  92. data/features/syntax_configuration.feature +0 -71
  93. data/features/test_frameworks/test_unit.feature +0 -44
  94. data/lib/rspec/expectations/deprecation.rb +0 -17
  95. data/lib/rspec/expectations/differ.rb +0 -133
  96. data/lib/rspec/expectations/errors.rb +0 -9
  97. data/lib/rspec/expectations/extensions/array.rb +0 -9
  98. data/lib/rspec/expectations/extensions/object.rb +0 -29
  99. data/lib/rspec/expectations/extensions.rb +0 -2
  100. data/lib/rspec/matchers/be_close.rb +0 -9
  101. data/lib/rspec/matchers/built_in/have.rb +0 -124
  102. data/lib/rspec/matchers/built_in/match_array.rb +0 -51
  103. data/lib/rspec/matchers/built_in/start_and_end_with.rb +0 -48
  104. data/lib/rspec/matchers/compatibility.rb +0 -14
  105. data/lib/rspec/matchers/configuration.rb +0 -108
  106. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  107. data/lib/rspec/matchers/matcher.rb +0 -300
  108. data/lib/rspec/matchers/method_missing.rb +0 -12
  109. data/lib/rspec/matchers/operator_matcher.rb +0 -109
  110. data/lib/rspec/matchers/pretty.rb +0 -70
  111. data/lib/rspec/matchers/test_unit_integration.rb +0 -11
  112. data/lib/rspec-expectations.rb +0 -1
  113. data/spec/rspec/expectations/differ_spec.rb +0 -192
  114. data/spec/rspec/expectations/expectation_target_spec.rb +0 -82
  115. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  116. data/spec/rspec/expectations/fail_with_spec.rb +0 -114
  117. data/spec/rspec/expectations/handler_spec.rb +0 -227
  118. data/spec/rspec/expectations/syntax_spec.rb +0 -139
  119. data/spec/rspec/matchers/base_matcher_spec.rb +0 -62
  120. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  121. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -63
  122. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -41
  123. data/spec/rspec/matchers/be_spec.rb +0 -516
  124. data/spec/rspec/matchers/be_within_spec.rb +0 -137
  125. data/spec/rspec/matchers/change_spec.rb +0 -553
  126. data/spec/rspec/matchers/configuration_spec.rb +0 -206
  127. data/spec/rspec/matchers/cover_spec.rb +0 -69
  128. data/spec/rspec/matchers/description_generation_spec.rb +0 -190
  129. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  130. data/spec/rspec/matchers/eq_spec.rb +0 -60
  131. data/spec/rspec/matchers/eql_spec.rb +0 -41
  132. data/spec/rspec/matchers/equal_spec.rb +0 -78
  133. data/spec/rspec/matchers/exist_spec.rb +0 -124
  134. data/spec/rspec/matchers/has_spec.rb +0 -122
  135. data/spec/rspec/matchers/have_spec.rb +0 -455
  136. data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
  137. data/spec/rspec/matchers/include_spec.rb +0 -531
  138. data/spec/rspec/matchers/match_array_spec.rb +0 -194
  139. data/spec/rspec/matchers/match_spec.rb +0 -61
  140. data/spec/rspec/matchers/matcher_spec.rb +0 -471
  141. data/spec/rspec/matchers/matchers_spec.rb +0 -37
  142. data/spec/rspec/matchers/method_missing_spec.rb +0 -28
  143. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -223
  144. data/spec/rspec/matchers/raise_error_spec.rb +0 -485
  145. data/spec/rspec/matchers/respond_to_spec.rb +0 -292
  146. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  147. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -186
  148. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  149. data/spec/rspec/matchers/yield_spec.rb +0 -514
  150. data/spec/spec_helper.rb +0 -54
  151. data/spec/support/classes.rb +0 -56
  152. data/spec/support/in_sub_process.rb +0 -38
  153. data/spec/support/matchers.rb +0 -22
  154. data/spec/support/ruby_version.rb +0 -10
  155. data/spec/support/shared_examples.rb +0 -13
@@ -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,59 +1,211 @@
1
+ require 'rspec/matchers/built_in/count_expectation'
2
+
1
3
  module RSpec
2
4
  module Matchers
3
5
  module BuiltIn
4
- class Include < BaseMatcher
5
- def initialize(*expected)
6
- @expected = expected
6
+ # @api private
7
+ # Provides the implementation for `include`.
8
+ # Not intended to be instantiated directly.
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
7
17
  end
8
18
 
19
+ # @api private
20
+ # @return [Boolean]
9
21
  def matches?(actual)
10
- @actual = actual
11
- perform_match(:all?, :all?, @actual, @expected)
22
+ check_actual?(actual) &&
23
+ if check_expected_count?
24
+ expected_count_matches?(count_inclusions)
25
+ else
26
+ perform_match { |v| v }
27
+ end
12
28
  end
13
29
 
30
+ # @api private
31
+ # @return [Boolean]
14
32
  def does_not_match?(actual)
15
- @actual = actual
16
- perform_match(:none?, :any?, @actual, @expected)
33
+ check_actual?(actual) &&
34
+ if check_expected_count?
35
+ !expected_count_matches?(count_inclusions)
36
+ else
37
+ perform_match { |v| !v }
38
+ end
17
39
  end
18
40
 
41
+ # @api private
42
+ # @return [String]
19
43
  def description
20
- "include#{expected_to_sentence}"
44
+ improve_hash_formatting("include#{readable_list_of(expecteds)}#{count_expectation_description}")
45
+ end
46
+
47
+ # @api private
48
+ # @return [String]
49
+ def failure_message
50
+ format_failure_message("to") { super }
21
51
  end
22
52
 
53
+ # @api private
54
+ # @return [String]
55
+ def failure_message_when_negated
56
+ format_failure_message("not to") { super }
57
+ end
58
+
59
+ # @api private
60
+ # @return [Boolean]
23
61
  def diffable?
24
- # Matchers do not diff well, since diff uses their inspect
25
- # output, which includes their instance variables and such.
26
- @expected.none? { |e| RSpec::Matchers.is_a_matcher?(e) }
27
- end
28
-
29
- private
30
-
31
- def perform_match(predicate, hash_predicate, actuals, expecteds)
32
- expecteds.__send__(predicate) do |expected|
33
- if comparing_hash_values?(actuals, expected)
34
- expected.__send__(hash_predicate) { |k,v|
35
- actuals.has_key?(k) && actuals[k] == v
36
- }
37
- elsif comparing_hash_keys?(actuals, expected)
38
- actuals.has_key?(expected)
39
- elsif comparing_with_matcher?(actual, expected)
40
- actual.any? { |value| expected.matches?(value) }
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
73
+ end
74
+
75
+ private
76
+
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'
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
105
+
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|
124
+ if comparing_hash_to_a_subset?(expected_item)
125
+ expected_item.each do |(key, value)|
126
+ memo << { key => value } unless yield actual_hash_includes?(key, value)
127
+ end
128
+ elsif comparing_hash_keys?(expected_item)
129
+ memo << expected_item unless yield actual_hash_has_key?(expected_item)
41
130
  else
42
- actuals.include?(expected)
131
+ memo << expected_item unless yield actual_collection_includes?(expected_item)
43
132
  end
133
+ memo
44
134
  end
45
135
  end
46
136
 
47
- def comparing_hash_keys?(actual, expected)
48
- actual.is_a?(Hash) && !expected.is_a?(Hash)
137
+ def comparing_hash_to_a_subset?(expected_item)
138
+ actual.is_a?(Hash) && expected_item.is_a?(Hash)
139
+ end
140
+
141
+ def actual_hash_includes?(expected_key, expected_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
146
+ values_match?(expected_value, actual_value)
147
+ end
148
+
149
+ def comparing_hash_keys?(expected_item)
150
+ actual.is_a?(Hash) && !expected_item.is_a?(Hash)
151
+ end
152
+
153
+ def actual_hash_has_key?(expected_key)
154
+ # We check `key?` first for perf:
155
+ # `key?` is O(1), but `any?` is O(N).
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) }
49
165
  end
50
166
 
51
- def comparing_hash_values?(actual, expected)
52
- actual.is_a?(Hash) && expected.is_a?(Hash)
167
+ def actual_collection_includes?(expected_item)
168
+ return true if actual.include?(expected_item)
169
+
170
+ # String lacks an `any?` method...
171
+ return false unless actual.respond_to?(:any?)
172
+
173
+ actual.any? { |value| values_match?(expected_item, value) }
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
53
205
  end
54
206
 
55
- def comparing_with_matcher?(actual, expected)
56
- actual.is_a?(Array) && RSpec::Matchers.is_a_matcher?(expected)
207
+ def convert_to_hash?(obj)
208
+ !obj.respond_to?(:include?) && obj.respond_to?(:to_hash)
57
209
  end
58
210
  end
59
211
  end
@@ -1,11 +1,105 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `match`.
6
+ # Not intended to be instantiated directly.
4
7
  class Match < BaseMatcher
8
+ def initialize(expected)
9
+ super(expected)
10
+
11
+ @expected_captures = nil
12
+ end
13
+ # @api private
14
+ # @return [String]
15
+ def description
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
21
+ end
22
+
23
+ # @api private
24
+ # @return [Boolean]
25
+ def diffable?
26
+ true
27
+ end
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
+
36
+ private
5
37
 
6
38
  def match(expected, actual)
7
- actual.match expected
39
+ return match_captures(expected, actual) if @expected_captures
40
+ return true if values_match?(expected, actual)
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))
8
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
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
9
103
  end
10
104
  end
11
105
  end
@@ -0,0 +1,128 @@
1
+ require 'rspec/support'
2
+
3
+ module RSpec
4
+ module Matchers
5
+ module BuiltIn
6
+ # @api private
7
+ # Provides the implementation for operator matchers.
8
+ # Not intended to be instantiated directly.
9
+ # Only available for use with `should`.
10
+ class OperatorMatcher
11
+ class << self
12
+ # @private
13
+ def registry
14
+ @registry ||= {}
15
+ end
16
+
17
+ # @private
18
+ def register(klass, operator, matcher)
19
+ registry[klass] ||= {}
20
+ registry[klass][operator] = matcher
21
+ end
22
+
23
+ # @private
24
+ def unregister(klass, operator)
25
+ registry[klass] && registry[klass].delete(operator)
26
+ end
27
+
28
+ # @private
29
+ def get(klass, operator)
30
+ klass.ancestors.each do |ancestor|
31
+ matcher = registry[ancestor] && registry[ancestor][operator]
32
+ return matcher if matcher
33
+ end
34
+
35
+ nil
36
+ end
37
+ end
38
+
39
+ register Enumerable, '=~', BuiltIn::ContainExactly
40
+
41
+ def initialize(actual)
42
+ @actual = actual
43
+ end
44
+
45
+ # @private
46
+ def self.use_custom_matcher_or_delegate(operator)
47
+ define_method(operator) do |expected|
48
+ if !has_non_generic_implementation_of?(operator) && (matcher = OperatorMatcher.get(@actual.class, operator))
49
+ @actual.__send__(::RSpec::Matchers.last_expectation_handler.should_method, matcher.new(expected))
50
+ else
51
+ eval_match(@actual, operator, expected)
52
+ end
53
+ end
54
+
55
+ negative_operator = operator.sub(/^=/, '!')
56
+ if negative_operator != operator && respond_to?(negative_operator)
57
+ define_method(negative_operator) do |_expected|
58
+ opposite_should = ::RSpec::Matchers.last_expectation_handler.opposite_should_method
59
+ raise "RSpec does not support `#{::RSpec::Matchers.last_expectation_handler.should_method} #{negative_operator} expected`. " \
60
+ "Use `#{opposite_should} #{operator} expected` instead."
61
+ end
62
+ end
63
+ end
64
+
65
+ ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
66
+ use_custom_matcher_or_delegate operator
67
+ end
68
+
69
+ # @private
70
+ def fail_with_message(message)
71
+ RSpec::Expectations.fail_with(message, @expected, @actual)
72
+ end
73
+
74
+ # @api private
75
+ # @return [String]
76
+ def description
77
+ "#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}"
78
+ end
79
+
80
+ private
81
+
82
+ def has_non_generic_implementation_of?(op)
83
+ Support.method_handle_for(@actual, op).owner != ::Kernel
84
+ rescue NameError
85
+ false
86
+ end
87
+
88
+ def eval_match(actual, operator, expected)
89
+ ::RSpec::Matchers.last_matcher = self
90
+ @operator, @expected = operator, expected
91
+ __delegate_operator(actual, operator, expected)
92
+ end
93
+ end
94
+
95
+ # @private
96
+ # Handles operator matcher for `should`.
97
+ class PositiveOperatorMatcher < OperatorMatcher
98
+ def __delegate_operator(actual, operator, expected)
99
+ if actual.__send__(operator, expected)
100
+ true
101
+ else
102
+ expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
103
+ actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
104
+
105
+ if ['==', '===', '=~'].include?(operator)
106
+ fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})")
107
+ else
108
+ fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ # @private
115
+ # Handles operator matcher for `should_not`.
116
+ class NegativeOperatorMatcher < OperatorMatcher
117
+ def __delegate_operator(actual, operator, expected)
118
+ return false unless actual.__send__(operator, expected)
119
+
120
+ expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
121
+ actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
122
+
123
+ fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end