rspec-expectations 2.14.0 → 3.0.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 (143) hide show
  1. checksums.yaml +15 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/Changelog.md +369 -25
  4. data/License.txt +1 -0
  5. data/README.md +88 -15
  6. data/lib/rspec/{matchers → expectations}/configuration.rb +44 -14
  7. data/lib/rspec/expectations/expectation_target.rb +67 -33
  8. data/lib/rspec/expectations/fail_with.rb +11 -53
  9. data/lib/rspec/expectations/handler.rb +126 -31
  10. data/lib/rspec/expectations/minitest_integration.rb +16 -0
  11. data/lib/rspec/expectations/syntax.rb +60 -94
  12. data/lib/rspec/expectations/version.rb +1 -1
  13. data/lib/rspec/expectations.rb +45 -23
  14. data/lib/rspec/matchers/aliased_matcher.rb +41 -0
  15. data/lib/rspec/matchers/built_in/all.rb +78 -0
  16. data/lib/rspec/matchers/built_in/base_matcher.rb +63 -15
  17. data/lib/rspec/matchers/built_in/be.rb +170 -81
  18. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  19. data/lib/rspec/matchers/built_in/be_instance_of.rb +11 -3
  20. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -0
  21. data/lib/rspec/matchers/built_in/be_within.rb +39 -15
  22. data/lib/rspec/matchers/built_in/change.rb +273 -80
  23. data/lib/rspec/matchers/built_in/compound.rb +122 -0
  24. data/lib/rspec/matchers/built_in/contain_exactly.rb +256 -0
  25. data/lib/rspec/matchers/built_in/cover.rb +3 -0
  26. data/lib/rspec/matchers/built_in/eq.rb +60 -6
  27. data/lib/rspec/matchers/built_in/eql.rb +17 -5
  28. data/lib/rspec/matchers/built_in/equal.rb +55 -21
  29. data/lib/rspec/matchers/built_in/exist.rb +74 -10
  30. data/lib/rspec/matchers/built_in/has.rb +71 -15
  31. data/lib/rspec/matchers/built_in/include.rb +64 -27
  32. data/lib/rspec/matchers/built_in/match.rb +19 -1
  33. data/lib/rspec/matchers/built_in/operators.rb +119 -0
  34. data/lib/rspec/matchers/built_in/output.rb +150 -0
  35. data/lib/rspec/matchers/built_in/raise_error.rb +84 -42
  36. data/lib/rspec/matchers/built_in/respond_to.rb +42 -20
  37. data/lib/rspec/matchers/built_in/satisfy.rb +19 -3
  38. data/lib/rspec/matchers/built_in/start_and_end_with.rb +54 -18
  39. data/lib/rspec/matchers/built_in/throw_symbol.rb +38 -8
  40. data/lib/rspec/matchers/built_in/yield.rb +175 -68
  41. data/lib/rspec/matchers/built_in.rb +45 -33
  42. data/lib/rspec/matchers/composable.rb +179 -0
  43. data/lib/rspec/matchers/dsl.rb +362 -5
  44. data/lib/rspec/matchers/generated_descriptions.rb +13 -6
  45. data/lib/rspec/matchers/matcher_delegator.rb +37 -0
  46. data/lib/rspec/matchers/pretty.rb +27 -20
  47. data/lib/rspec/matchers.rb +361 -136
  48. data.tar.gz.sig +1 -0
  49. metadata +99 -215
  50. metadata.gz.sig +0 -0
  51. data/features/README.md +0 -48
  52. data/features/Upgrade.md +0 -53
  53. data/features/built_in_matchers/README.md +0 -90
  54. data/features/built_in_matchers/be.feature +0 -175
  55. data/features/built_in_matchers/be_within.feature +0 -48
  56. data/features/built_in_matchers/cover.feature +0 -47
  57. data/features/built_in_matchers/end_with.feature +0 -48
  58. data/features/built_in_matchers/equality.feature +0 -139
  59. data/features/built_in_matchers/exist.feature +0 -45
  60. data/features/built_in_matchers/expect_change.feature +0 -59
  61. data/features/built_in_matchers/expect_error.feature +0 -144
  62. data/features/built_in_matchers/have.feature +0 -109
  63. data/features/built_in_matchers/include.feature +0 -174
  64. data/features/built_in_matchers/match.feature +0 -52
  65. data/features/built_in_matchers/operators.feature +0 -227
  66. data/features/built_in_matchers/predicates.feature +0 -137
  67. data/features/built_in_matchers/respond_to.feature +0 -84
  68. data/features/built_in_matchers/satisfy.feature +0 -33
  69. data/features/built_in_matchers/start_with.feature +0 -48
  70. data/features/built_in_matchers/throw_symbol.feature +0 -91
  71. data/features/built_in_matchers/types.feature +0 -116
  72. data/features/built_in_matchers/yield.feature +0 -161
  73. data/features/custom_matchers/access_running_example.feature +0 -53
  74. data/features/custom_matchers/define_diffable_matcher.feature +0 -27
  75. data/features/custom_matchers/define_matcher.feature +0 -368
  76. data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
  77. data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
  78. data/features/customized_message.feature +0 -22
  79. data/features/diffing.feature +0 -85
  80. data/features/implicit_docstrings.feature +0 -52
  81. data/features/step_definitions/additional_cli_steps.rb +0 -22
  82. data/features/support/env.rb +0 -14
  83. data/features/syntax_configuration.feature +0 -71
  84. data/features/test_frameworks/test_unit.feature +0 -44
  85. data/lib/rspec/expectations/deprecation.rb +0 -17
  86. data/lib/rspec/expectations/differ.rb +0 -133
  87. data/lib/rspec/expectations/errors.rb +0 -9
  88. data/lib/rspec/expectations/extensions/array.rb +0 -9
  89. data/lib/rspec/expectations/extensions/object.rb +0 -29
  90. data/lib/rspec/expectations/extensions.rb +0 -2
  91. data/lib/rspec/matchers/be_close.rb +0 -9
  92. data/lib/rspec/matchers/built_in/have.rb +0 -124
  93. data/lib/rspec/matchers/built_in/match_array.rb +0 -51
  94. data/lib/rspec/matchers/compatibility.rb +0 -14
  95. data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
  96. data/lib/rspec/matchers/matcher.rb +0 -300
  97. data/lib/rspec/matchers/method_missing.rb +0 -12
  98. data/lib/rspec/matchers/operator_matcher.rb +0 -109
  99. data/lib/rspec/matchers/test_unit_integration.rb +0 -11
  100. data/lib/rspec-expectations.rb +0 -1
  101. data/spec/rspec/expectations/differ_spec.rb +0 -192
  102. data/spec/rspec/expectations/expectation_target_spec.rb +0 -82
  103. data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
  104. data/spec/rspec/expectations/fail_with_spec.rb +0 -114
  105. data/spec/rspec/expectations/handler_spec.rb +0 -227
  106. data/spec/rspec/expectations/syntax_spec.rb +0 -139
  107. data/spec/rspec/matchers/base_matcher_spec.rb +0 -62
  108. data/spec/rspec/matchers/be_close_spec.rb +0 -22
  109. data/spec/rspec/matchers/be_instance_of_spec.rb +0 -63
  110. data/spec/rspec/matchers/be_kind_of_spec.rb +0 -41
  111. data/spec/rspec/matchers/be_spec.rb +0 -516
  112. data/spec/rspec/matchers/be_within_spec.rb +0 -137
  113. data/spec/rspec/matchers/change_spec.rb +0 -553
  114. data/spec/rspec/matchers/configuration_spec.rb +0 -206
  115. data/spec/rspec/matchers/cover_spec.rb +0 -69
  116. data/spec/rspec/matchers/description_generation_spec.rb +0 -190
  117. data/spec/rspec/matchers/dsl_spec.rb +0 -57
  118. data/spec/rspec/matchers/eq_spec.rb +0 -60
  119. data/spec/rspec/matchers/eql_spec.rb +0 -41
  120. data/spec/rspec/matchers/equal_spec.rb +0 -78
  121. data/spec/rspec/matchers/exist_spec.rb +0 -124
  122. data/spec/rspec/matchers/has_spec.rb +0 -122
  123. data/spec/rspec/matchers/have_spec.rb +0 -455
  124. data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
  125. data/spec/rspec/matchers/include_spec.rb +0 -531
  126. data/spec/rspec/matchers/match_array_spec.rb +0 -194
  127. data/spec/rspec/matchers/match_spec.rb +0 -61
  128. data/spec/rspec/matchers/matcher_spec.rb +0 -471
  129. data/spec/rspec/matchers/matchers_spec.rb +0 -37
  130. data/spec/rspec/matchers/method_missing_spec.rb +0 -28
  131. data/spec/rspec/matchers/operator_matcher_spec.rb +0 -223
  132. data/spec/rspec/matchers/raise_error_spec.rb +0 -485
  133. data/spec/rspec/matchers/respond_to_spec.rb +0 -292
  134. data/spec/rspec/matchers/satisfy_spec.rb +0 -44
  135. data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -186
  136. data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
  137. data/spec/rspec/matchers/yield_spec.rb +0 -514
  138. data/spec/spec_helper.rb +0 -54
  139. data/spec/support/classes.rb +0 -56
  140. data/spec/support/in_sub_process.rb +0 -38
  141. data/spec/support/matchers.rb +0 -22
  142. data/spec/support/ruby_version.rb +0 -10
  143. data/spec/support/shared_examples.rb +0 -13
@@ -0,0 +1,256 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `contain_exactly` and `match_array`.
6
+ # Not intended to be instantiated directly.
7
+ class ContainExactly < BaseMatcher
8
+
9
+ # @api private
10
+ # @return [String]
11
+ def failure_message
12
+ if Array === actual
13
+ message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n"
14
+ message += "actual collection contained: #{safe_sort(actual).inspect}\n"
15
+ message += "the missing elements were: #{safe_sort(surface_descriptions_in missing_items).inspect}\n" unless missing_items.empty?
16
+ message += "the extra elements were: #{safe_sort(extra_items).inspect}\n" unless extra_items.empty?
17
+ message
18
+ else
19
+ "expected a collection that can be converted to an array with " +
20
+ "`#to_ary` or `#to_a`, but got #{actual.inspect}"
21
+ end
22
+ end
23
+
24
+ # @api private
25
+ # @return [String]
26
+ def failure_message_when_negated
27
+ "`contain_exactly` does not support negation"
28
+ end
29
+
30
+ # @api private
31
+ # @return [String]
32
+ def description
33
+ "contain exactly#{to_sentence(surface_descriptions_in expected)}"
34
+ end
35
+
36
+ private
37
+
38
+ def match(expected, actual)
39
+ convert_actual_to_an_array or return false
40
+ match_when_sorted? || (extra_items.empty? && missing_items.empty?)
41
+ end
42
+
43
+ # This cannot always work (e.g. when dealing with unsortable items,
44
+ # or matchers as expected items), but it's practically free compared to
45
+ # the slowness of the full matching algorithm, and in common cases this
46
+ # works, so it's worth a try.
47
+ def match_when_sorted?
48
+ values_match?(safe_sort(expected), safe_sort(actual))
49
+ end
50
+
51
+ def convert_actual_to_an_array
52
+ if actual.respond_to?(:to_ary)
53
+ @actual = actual.to_ary
54
+ elsif enumerable?(actual) && actual.respond_to?(:to_a)
55
+ @actual = actual.to_a
56
+ else
57
+ return false
58
+ end
59
+ end
60
+
61
+ def safe_sort(array)
62
+ array.sort rescue array
63
+ end
64
+
65
+ def missing_items
66
+ @missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
67
+ expected[index]
68
+ end
69
+ end
70
+
71
+ def extra_items
72
+ @extra_items ||= best_solution.unmatched_actual_indexes.map do |index|
73
+ actual[index]
74
+ end
75
+ end
76
+
77
+ def best_solution
78
+ @best_solution ||= pairings_maximizer.find_best_solution
79
+ end
80
+
81
+ def pairings_maximizer
82
+ @pairings_maximizer ||= begin
83
+ expected_matches = {}
84
+ actual_matches = {}
85
+
86
+ expected.each_with_index do |e, ei|
87
+ expected_matches[ei] ||= []
88
+
89
+ actual.each_with_index do |a, ai|
90
+ actual_matches[ai] ||= []
91
+
92
+ # Normally we'd call `values_match?(e, a)` here but that contains
93
+ # some extra checks we don't need (e.g. to support nested data
94
+ # structures), and given that it's called N*M times here, it helps
95
+ # perf significantly to implement the matching bit ourselves.
96
+ if (e === a || a == e)
97
+ expected_matches[ei] << ai
98
+ actual_matches[ai] << ei
99
+ end
100
+ end
101
+ end
102
+
103
+ PairingsMaximizer.new(expected_matches, actual_matches)
104
+ end
105
+ end
106
+
107
+ # Once we started supporting composing matchers, the algorithm for this matcher got
108
+ # much more complicated. Consider this expression:
109
+ #
110
+ # expect(["fool", "food"]).to contain_exactly(/foo/, /fool/)
111
+ #
112
+ # This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but
113
+ # the original algorithm used by this matcher would pair the first elements it could
114
+ # (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have
115
+ # an expected element which is a matcher that matches a superset of actual items
116
+ # compared to another expected element matcher, we need to consider every possible pairing.
117
+ #
118
+ # This class is designed to maximize the number of actual/expected pairings -- or,
119
+ # conversely, to minimize the number of unpaired items. It's essentially a brute
120
+ # force solution, but with a few heuristics applied to reduce the size of the
121
+ # problem space:
122
+ #
123
+ # * Any items which match none of the items in the other list are immediately
124
+ # placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array.
125
+ # The extra items and missing items in the matcher failure message are derived
126
+ # from these arrays.
127
+ # * Any items which reciprocally match only each other are paired up and not
128
+ # considered further.
129
+ #
130
+ # What's left is only the items which match multiple items from the other list
131
+ # (or vice versa). From here, it performs a brute-force depth-first search,
132
+ # looking for a solution which pairs all elements in both lists, or, barring that,
133
+ # that produces the fewest unmatched items.
134
+ #
135
+ # @private
136
+ class PairingsMaximizer
137
+ Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
138
+ :indeterminate_expected_indexes, :indeterminate_actual_indexes) do
139
+ def worse_than?(other)
140
+ unmatched_item_count > other.unmatched_item_count
141
+ end
142
+
143
+ def candidate?
144
+ indeterminate_expected_indexes.empty? &&
145
+ indeterminate_actual_indexes.empty?
146
+ end
147
+
148
+ def ideal?
149
+ candidate? && (
150
+ unmatched_expected_indexes.empty? ||
151
+ unmatched_actual_indexes.empty?
152
+ )
153
+ end
154
+
155
+ def unmatched_item_count
156
+ unmatched_expected_indexes.count + unmatched_actual_indexes.count
157
+ end
158
+
159
+ def +(derived_candidate_solution)
160
+ self.class.new(
161
+ unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes,
162
+ unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes,
163
+ # Ignore the indeterminate indexes: by the time we get here,
164
+ # we've dealt with all indeterminates.
165
+ [], []
166
+ )
167
+ end
168
+ end
169
+
170
+ attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
171
+
172
+ def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
173
+ @expected_to_actual_matched_indexes = expected_to_actual_matched_indexes
174
+ @actual_to_expected_matched_indexes = actual_to_expected_matched_indexes
175
+
176
+ unmatched_expected_indexes, indeterminate_expected_indexes =
177
+ categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
178
+
179
+ unmatched_actual_indexes, indeterminate_actual_indexes =
180
+ categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes)
181
+
182
+ @solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes,
183
+ indeterminate_expected_indexes, indeterminate_actual_indexes)
184
+ end
185
+
186
+ def find_best_solution
187
+ return solution if solution.candidate?
188
+ best_solution_so_far = NullSolution
189
+
190
+ expected_index = solution.indeterminate_expected_indexes.first
191
+ actuals = expected_to_actual_matched_indexes[expected_index]
192
+
193
+ actuals.each do |actual_index|
194
+ solution = best_solution_for_pairing(expected_index, actual_index)
195
+ return solution if solution.ideal?
196
+ best_solution_so_far = solution if best_solution_so_far.worse_than?(solution)
197
+ end
198
+
199
+ best_solution_so_far
200
+ end
201
+
202
+ private
203
+
204
+ # @private
205
+ # Starting solution that is worse than any other real solution.
206
+ NullSolution = Class.new do
207
+ def self.worse_than?(other); true; end
208
+ end
209
+
210
+ def categorize_indexes(indexes_to_categorize, other_indexes)
211
+ unmatched = []
212
+ indeterminate = []
213
+
214
+ indexes_to_categorize.each_pair do |index, matches|
215
+ if matches.empty?
216
+ unmatched << index
217
+ elsif !reciprocal_single_match?(matches, index, other_indexes)
218
+ indeterminate << index
219
+ end
220
+ end
221
+
222
+ return unmatched, indeterminate
223
+ end
224
+
225
+ def reciprocal_single_match?(matches, index, other_list)
226
+ return false unless matches.one?
227
+ other_list[matches.first] == [index]
228
+ end
229
+
230
+ def best_solution_for_pairing(expected_index, actual_index)
231
+ modified_expecteds = apply_pairing_to(
232
+ solution.indeterminate_expected_indexes,
233
+ expected_to_actual_matched_indexes, actual_index)
234
+
235
+ modified_expecteds.delete(expected_index)
236
+
237
+ modified_actuals = apply_pairing_to(
238
+ solution.indeterminate_actual_indexes,
239
+ actual_to_expected_matched_indexes, expected_index)
240
+
241
+ modified_actuals.delete(actual_index)
242
+
243
+ solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution
244
+ end
245
+
246
+ def apply_pairing_to(indeterminates, original_matches, other_list_index)
247
+ indeterminates.inject({}) do |accum, index|
248
+ accum[index] = original_matches[index] - [other_list_index]
249
+ accum
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -1,6 +1,9 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `cover`.
6
+ # Not intended to be instantiated directly.
4
7
  class Cover < BaseMatcher
5
8
  def initialize(*expected)
6
9
  @expected = expected
@@ -1,22 +1,76 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `eq`.
6
+ # Not intended to be instantiated directly.
4
7
  class Eq < BaseMatcher
8
+
9
+ # @api private
10
+ # @return [String]
11
+ def failure_message
12
+ "\nexpected: #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
13
+ end
14
+
15
+ # @api private
16
+ # @return [String]
17
+ def failure_message_when_negated
18
+ "\nexpected: value != #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
19
+ end
20
+
21
+ # @api private
22
+ # @return [String]
23
+ def description
24
+ "#{name_to_sentence} #{@expected.inspect}"
25
+ end
26
+
27
+ # @api private
28
+ # @return [Boolean]
29
+ def diffable?
30
+ true
31
+ end
32
+
33
+ private
34
+
5
35
  def match(expected, actual)
6
36
  actual == expected
7
37
  end
8
38
 
9
- def failure_message_for_should
10
- "\nexpected: #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using ==)\n"
39
+ def format_object(object)
40
+ if Time === object
41
+ format_time(object)
42
+ elsif defined?(DateTime) && DateTime === object
43
+ format_date_time(object)
44
+ elsif defined?(BigDecimal) && BigDecimal === object
45
+ "#{object.to_s 'F'} (#{object.inspect})"
46
+ else
47
+ object.inspect
48
+ end
11
49
  end
12
50
 
13
- def failure_message_for_should_not
14
- "\nexpected: value != #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using ==)\n"
51
+ TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
52
+
53
+ if Time.method_defined?(:nsec)
54
+ def format_time(time)
55
+ time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
56
+ end
57
+ else # for 1.8.7
58
+ def format_time(time)
59
+ time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
60
+ end
15
61
  end
16
62
 
17
- def diffable?; true; end
63
+ DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
64
+ # ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
65
+ # defined use a custom format string that includes more time precision.
66
+ def format_date_time(date_time)
67
+ if defined?(ActiveSupport)
68
+ date_time.strftime(DATE_TIME_FORMAT)
69
+ else
70
+ date_time.inspect
71
+ end
72
+ end
18
73
  end
19
74
  end
20
75
  end
21
76
  end
22
-
@@ -1,22 +1,34 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `eql`.
6
+ # Not intended to be instantiated directly.
4
7
  class Eql < BaseMatcher
5
- def match(expected, actual)
6
- actual.eql? expected
7
- end
8
8
 
9
- def failure_message_for_should
9
+ # @api private
10
+ # @return [String]
11
+ def failure_message
10
12
  "\nexpected: #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
11
13
  end
12
14
 
13
- def failure_message_for_should_not
15
+ # @api private
16
+ # @return [String]
17
+ def failure_message_when_negated
14
18
  "\nexpected: value != #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
15
19
  end
16
20
 
21
+ # @api private
22
+ # @return [Boolean]
17
23
  def diffable?
18
24
  true
19
25
  end
26
+
27
+ private
28
+
29
+ def match(expected, actual)
30
+ actual.eql? expected
31
+ end
20
32
  end
21
33
  end
22
34
  end
@@ -1,46 +1,80 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `equal`.
6
+ # Not intended to be instantiated directly.
4
7
  class Equal < BaseMatcher
5
- def match(expected, actual)
6
- actual.equal? expected
8
+
9
+ # @api private
10
+ # @return [String]
11
+ def failure_message
12
+ if expected_is_a_literal_singleton?
13
+ simple_failure_message
14
+ else
15
+ detailed_failure_message
16
+ end
7
17
  end
8
18
 
9
- def failure_message_for_should
19
+ # @api private
20
+ # @return [String]
21
+ def failure_message_when_negated
10
22
  return <<-MESSAGE
11
23
 
12
- expected #{inspect_object(expected)}
13
- got #{inspect_object(actual)}
24
+ expected not #{inspect_object(actual)}
25
+ got #{inspect_object(expected)}
14
26
 
15
- Compared using equal?, which compares object identity,
16
- but expected and actual are not the same object. Use
17
- `#{eq_expression}` if you don't care about
18
- object identity in this example.
27
+ Compared using equal?, which compares object identity.
19
28
 
20
29
  MESSAGE
21
30
  end
22
31
 
23
- def failure_message_for_should_not
24
- return <<-MESSAGE
32
+ # @api private
33
+ # @return [Boolean]
34
+ def diffable?
35
+ !expected_is_a_literal_singleton?
36
+ end
25
37
 
26
- expected not #{inspect_object(actual)}
27
- got #{inspect_object(expected)}
38
+ private
28
39
 
29
- Compared using equal?, which compares object identity.
40
+ def match(expected, actual)
41
+ actual.equal? expected
42
+ end
30
43
 
31
- MESSAGE
44
+ LITERAL_SINGLETONS = [true, false, nil]
45
+
46
+ def expected_is_a_literal_singleton?
47
+ LITERAL_SINGLETONS.include?(expected)
32
48
  end
33
49
 
34
- def diffable?; true; end
50
+ def actual_inspected
51
+ if LITERAL_SINGLETONS.include?(actual)
52
+ actual.inspect
53
+ else
54
+ inspect_object(actual)
55
+ end
56
+ end
35
57
 
36
- private
58
+ def simple_failure_message
59
+ "\nexpected #{expected.inspect}\n got #{actual_inspected}\n"
60
+ end
37
61
 
38
- def inspect_object(o)
39
- "#<#{o.class}:#{o.object_id}> => #{o.inspect}"
62
+ def detailed_failure_message
63
+ return <<-MESSAGE
64
+
65
+ expected #{inspect_object(expected)}
66
+ got #{inspect_object(actual)}
67
+
68
+ Compared using equal?, which compares object identity,
69
+ but expected and actual are not the same object. Use
70
+ `expect(actual).to eq(expected)` if you don't care about
71
+ object identity in this example.
72
+
73
+ MESSAGE
40
74
  end
41
75
 
42
- def eq_expression
43
- Expectations::Syntax.positive_expression("actual", "eq(expected)")
76
+ def inspect_object(o)
77
+ "#<#{o.class}:#{o.object_id}> => #{o.inspect}"
44
78
  end
45
79
  end
46
80
  end
@@ -1,25 +1,89 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # @api private
5
+ # Provides the implementation for `exist`.
6
+ # Not intended to be instantiated directly.
4
7
  class Exist < BaseMatcher
5
8
  def initialize(*expected)
6
9
  @expected = expected
7
10
  end
8
11
 
12
+ # @api private
13
+ # @return [Boolean]
9
14
  def matches?(actual)
10
15
  @actual = actual
11
- predicates = [:exist?, :exists?].select { |p| @actual.respond_to?(p) }
12
- existence_values = predicates.map { |p| @actual.__send__(p, *@expected) }
13
- uniq_truthy_values = existence_values.map { |v| !!v }.uniq
14
-
15
- case uniq_truthy_values.size
16
- when 0; raise NoMethodError.new("#{@actual.inspect} does not respond to either #exist? or #exists?")
17
- when 1; existence_values.first
18
- else raise "#exist? and #exists? returned different values:\n\n" +
19
- " exist?: #{existence_values.first}\n" +
20
- "exists?: #{existence_values.last}"
16
+ @test = ExistenceTest.new @actual, @expected
17
+ @test.valid_test? && @test.actual_exists?
18
+ end
19
+
20
+ # @api private
21
+ # @return [Boolean]
22
+ def does_not_match?(actual)
23
+ @actual = actual
24
+ @test = ExistenceTest.new @actual, @expected
25
+ @test.valid_test? && !@test.actual_exists?
26
+ end
27
+
28
+ # @api private
29
+ # @return [String]
30
+ def failure_message
31
+ "expected #{@actual.inspect} to exist#{@test.validity_message}"
32
+ end
33
+
34
+ # @api private
35
+ # @return [String]
36
+ def failure_message_when_negated
37
+ "expected #{@actual.inspect} not to exist#{@test.validity_message}"
38
+ end
39
+
40
+ private
41
+
42
+ # @api private
43
+ # Simple class for memoizing actual/expected for this matcher
44
+ # and examining the match
45
+ class ExistenceTest < Struct.new(:actual, :expected)
46
+
47
+ # @api private
48
+ # @return [Boolean]
49
+ def valid_test?
50
+ uniq_truthy_values.size == 1
51
+ end
52
+
53
+ # @api private
54
+ # @return [Boolean]
55
+ def actual_exists?
56
+ existence_values.first
57
+ end
58
+
59
+ # @api private
60
+ # @return [String]
61
+ def validity_message
62
+ case uniq_truthy_values.size
63
+ when 0
64
+ " but it does not respond to either `exist?` or `exists?`"
65
+ when 2
66
+ " but `exist?` and `exists?` returned different values:\n\n"\
67
+ " exist?: #{existence_values.first}\n"\
68
+ "exists?: #{existence_values.last}"
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def uniq_truthy_values
75
+ @uniq_truthy_values ||= existence_values.map { |v| !!v }.uniq
76
+ end
77
+
78
+ def existence_values
79
+ @existence_values ||= predicates.map { |p| actual.__send__(p, *expected) }
80
+ end
81
+
82
+ def predicates
83
+ @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) }
21
84
  end
22
85
  end
86
+
23
87
  end
24
88
  end
25
89
  end