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.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +530 -5
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +73 -31
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +96 -1
  10. data/lib/rspec/expectations/expectation_target.rb +82 -38
  11. data/lib/rspec/expectations/fail_with.rb +11 -6
  12. data/lib/rspec/expectations/failure_aggregator.rb +229 -0
  13. data/lib/rspec/expectations/handler.rb +36 -15
  14. data/lib/rspec/expectations/minitest_integration.rb +43 -2
  15. data/lib/rspec/expectations/syntax.rb +5 -5
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +15 -1
  18. data/lib/rspec/matchers/aliased_matcher.rb +79 -4
  19. data/lib/rspec/matchers/built_in/all.rb +11 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +111 -28
  21. data/lib/rspec/matchers/built_in/be.rb +28 -114
  22. data/lib/rspec/matchers/built_in/be_between.rb +1 -1
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +5 -12
  26. data/lib/rspec/matchers/built_in/change.rb +171 -63
  27. data/lib/rspec/matchers/built_in/compound.rb +201 -30
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +73 -12
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/eq.rb +3 -38
  31. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  32. data/lib/rspec/matchers/built_in/equal.rb +3 -3
  33. data/lib/rspec/matchers/built_in/exist.rb +7 -3
  34. data/lib/rspec/matchers/built_in/has.rb +93 -30
  35. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  36. data/lib/rspec/matchers/built_in/include.rb +133 -25
  37. data/lib/rspec/matchers/built_in/match.rb +79 -2
  38. data/lib/rspec/matchers/built_in/operators.rb +14 -5
  39. data/lib/rspec/matchers/built_in/output.rb +59 -2
  40. data/lib/rspec/matchers/built_in/raise_error.rb +130 -27
  41. data/lib/rspec/matchers/built_in/respond_to.rb +117 -15
  42. data/lib/rspec/matchers/built_in/satisfy.rb +28 -14
  43. data/lib/rspec/matchers/built_in/{start_and_end_with.rb → start_or_end_with.rb} +20 -8
  44. data/lib/rspec/matchers/built_in/throw_symbol.rb +15 -5
  45. data/lib/rspec/matchers/built_in/yield.rb +129 -156
  46. data/lib/rspec/matchers/built_in.rb +5 -3
  47. data/lib/rspec/matchers/composable.rb +24 -36
  48. data/lib/rspec/matchers/dsl.rb +203 -37
  49. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  50. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  51. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  52. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  53. data/lib/rspec/matchers/matcher_delegator.rb +3 -4
  54. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  55. data/lib/rspec/matchers.rb +267 -144
  56. data.tar.gz.sig +0 -0
  57. metadata +71 -49
  58. metadata.gz.sig +0 -0
  59. data/lib/rspec/matchers/pretty.rb +0 -77
@@ -5,7 +5,7 @@ module RSpec
5
5
  # Base class for `and` and `or` compound matchers.
6
6
  class Compound < BaseMatcher
7
7
  # @private
8
- attr_reader :matcher_1, :matcher_2
8
+ attr_reader :matcher_1, :matcher_2, :evaluator
9
9
 
10
10
  def initialize(matcher_1, matcher_2)
11
11
  @matcher_1 = matcher_1
@@ -14,14 +14,56 @@ module RSpec
14
14
 
15
15
  # @private
16
16
  def does_not_match?(_actual)
17
- raise NotImplementedError, "`expect(...).not_to " \
18
- "matcher.#{conjunction} matcher` is not supported"
17
+ raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \
18
+ "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \
19
+ "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \
20
+ "use `expect(...).to matcher.#{conjunction} matcher`."
19
21
  end
20
22
 
21
23
  # @api private
22
24
  # @return [String]
23
25
  def description
24
- singleline_message(matcher_1.description, matcher_2.description)
26
+ "#{matcher_1.description} #{conjunction} #{matcher_2.description}"
27
+ end
28
+
29
+ # @api private
30
+ def supports_block_expectations?
31
+ matcher_supports_block_expectations?(matcher_1) &&
32
+ matcher_supports_block_expectations?(matcher_2)
33
+ end
34
+
35
+ # @api private
36
+ def supports_value_expectations?
37
+ matcher_supports_value_expectations?(matcher_1) &&
38
+ matcher_supports_value_expectations?(matcher_2)
39
+ end
40
+
41
+ # @api private
42
+ def expects_call_stack_jump?
43
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
44
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
45
+ end
46
+
47
+ # @api private
48
+ # @return [Boolean]
49
+ def diffable?
50
+ matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2)
51
+ end
52
+
53
+ # @api private
54
+ # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
55
+ def expected
56
+ return nil unless evaluator
57
+ ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
58
+ end
59
+
60
+ protected
61
+
62
+ def diffable_matcher_list
63
+ list = []
64
+ list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches?
65
+ list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches?
66
+ list
25
67
  end
26
68
 
27
69
  private
@@ -32,6 +74,16 @@ module RSpec
32
74
  super
33
75
  end
34
76
 
77
+ def match(_expected, actual)
78
+ evaluator_klass = if supports_block_expectations? && Proc === actual
79
+ NestedEvaluator
80
+ else
81
+ SequentialEvaluator
82
+ end
83
+
84
+ @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2)
85
+ end
86
+
35
87
  def indent_multiline_message(message)
36
88
  message.lines.map do |line|
37
89
  line =~ /\S/ ? ' ' + line : line
@@ -39,30 +91,150 @@ module RSpec
39
91
  end
40
92
 
41
93
  def compound_failure_message
42
- message_1 = matcher_1.failure_message
43
- message_2 = matcher_2.failure_message
94
+ "#{indent_multiline_message(matcher_1.failure_message.sub(/\n+\z/, ''))}" \
95
+ "\n\n...#{conjunction}:" \
96
+ "\n\n#{indent_multiline_message(matcher_2.failure_message.sub(/\A\n+/, ''))}"
97
+ end
44
98
 
45
- if multiline?(message_1) || multiline?(message_2)
46
- multiline_message(message_1, message_2)
47
- else
48
- singleline_message(message_1, message_2)
49
- end
99
+ def matcher_1_matches?
100
+ evaluator.matcher_matches?(matcher_1)
101
+ end
102
+
103
+ def matcher_2_matches?
104
+ evaluator.matcher_matches?(matcher_2)
105
+ end
106
+
107
+ def matcher_supports_block_expectations?(matcher)
108
+ matcher.supports_block_expectations?
109
+ rescue NoMethodError
110
+ false
50
111
  end
51
112
 
52
- def multiline_message(message_1, message_2)
53
- [
54
- indent_multiline_message(message_1.sub(/\n+\z/, '')),
55
- "...#{conjunction}:",
56
- indent_multiline_message(message_2.sub(/\A\n+/, ''))
57
- ].join("\n\n")
113
+ def matcher_supports_value_expectations?(matcher)
114
+ matcher.supports_value_expectations?
115
+ rescue NoMethodError
116
+ true
58
117
  end
59
118
 
60
- def multiline?(message)
61
- message.lines.count > 1
119
+ def matcher_is_diffable?(matcher)
120
+ matcher.diffable?
121
+ rescue NoMethodError
122
+ false
62
123
  end
63
124
 
64
- def singleline_message(message_1, message_2)
65
- [message_1, conjunction, message_2].join(' ')
125
+ def diffable_matcher_list_for(matcher)
126
+ return [] unless matcher_is_diffable?(matcher)
127
+ return matcher.diffable_matcher_list if Compound === matcher
128
+ [matcher]
129
+ end
130
+
131
+ # For value expectations, we can evaluate the matchers sequentially.
132
+ class SequentialEvaluator
133
+ def initialize(actual, *)
134
+ @actual = actual
135
+ end
136
+
137
+ def matcher_matches?(matcher)
138
+ matcher.matches?(@actual)
139
+ end
140
+ end
141
+
142
+ # Normally, we evaluate the matching sequentially. For an expression like
143
+ # `expect(x).to foo.and bar`, this becomes:
144
+ #
145
+ # expect(x).to foo
146
+ # expect(x).to bar
147
+ #
148
+ # For block expectations, we need to nest them instead, so that
149
+ # `expect { x }.to foo.and bar` becomes:
150
+ #
151
+ # expect {
152
+ # expect { x }.to foo
153
+ # }.to bar
154
+ #
155
+ # This is necessary so that the `expect` block is only executed once.
156
+ class NestedEvaluator
157
+ def initialize(actual, matcher_1, matcher_2)
158
+ @actual = actual
159
+ @matcher_1 = matcher_1
160
+ @matcher_2 = matcher_2
161
+ @match_results = {}
162
+
163
+ inner, outer = order_block_matchers
164
+
165
+ @match_results[outer] = outer.matches?(Proc.new do |*args|
166
+ @match_results[inner] = inner.matches?(inner_matcher_block(args))
167
+ end)
168
+ end
169
+
170
+ def matcher_matches?(matcher)
171
+ @match_results.fetch(matcher) do
172
+ raise ArgumentError, "Your #{matcher.description} has no match " \
173
+ "results, this can occur when an unexpected call stack or " \
174
+ "local jump occurs. Perhaps one of your matchers needs to " \
175
+ "declare `expects_call_stack_jump?` as `true`?"
176
+ end
177
+ end
178
+
179
+ private
180
+
181
+ # Some block matchers (such as `yield_xyz`) pass args to the `expect` block.
182
+ # When such a matcher is used as the outer matcher, we need to forward the
183
+ # the args on to the `expect` block.
184
+ def inner_matcher_block(outer_args)
185
+ return @actual if outer_args.empty?
186
+
187
+ Proc.new do |*inner_args|
188
+ unless inner_args.empty?
189
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
190
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
191
+ "since both matchers pass arguments to the block."
192
+ end
193
+
194
+ @actual.call(*outer_args)
195
+ end
196
+ end
197
+
198
+ # For a matcher like `raise_error` or `throw_symbol`, where the block will jump
199
+ # up the call stack, we need to order things so that it is the inner matcher.
200
+ # For example, we need it to be this:
201
+ #
202
+ # expect {
203
+ # expect {
204
+ # x += 1
205
+ # raise "boom"
206
+ # }.to raise_error("boom")
207
+ # }.to change { x }.by(1)
208
+ #
209
+ # ...rather than:
210
+ #
211
+ # expect {
212
+ # expect {
213
+ # x += 1
214
+ # raise "boom"
215
+ # }.to change { x }.by(1)
216
+ # }.to raise_error("boom")
217
+ #
218
+ # In the latter case, the after-block logic in the `change` matcher would never
219
+ # get executed because the `raise "boom"` line would jump to the `rescue` in the
220
+ # `raise_error` logic, so only the former case will work properly.
221
+ #
222
+ # This method figures out which matcher should be the inner matcher and which
223
+ # should be the outer matcher.
224
+ def order_block_matchers
225
+ return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2)
226
+ return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1)
227
+
228
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
229
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
230
+ "because they both expect a call stack jump."
231
+ end
232
+
233
+ def self.matcher_expects_call_stack_jump?(matcher)
234
+ matcher.expects_call_stack_jump?
235
+ rescue NoMethodError
236
+ false
237
+ end
66
238
  end
67
239
 
68
240
  # @api public
@@ -71,9 +243,9 @@ module RSpec
71
243
  # @api private
72
244
  # @return [String]
73
245
  def failure_message
74
- if @matcher_1_matches
246
+ if matcher_1_matches?
75
247
  matcher_2.failure_message
76
- elsif @matcher_2_matches
248
+ elsif matcher_2_matches?
77
249
  matcher_1.failure_message
78
250
  else
79
251
  compound_failure_message
@@ -82,11 +254,9 @@ module RSpec
82
254
 
83
255
  private
84
256
 
85
- def match(_expected, actual)
86
- @matcher_1_matches = matcher_1.matches?(actual)
87
- @matcher_2_matches = matcher_2.matches?(actual)
88
-
89
- @matcher_1_matches && @matcher_2_matches
257
+ def match(*)
258
+ super
259
+ matcher_1_matches? && matcher_2_matches?
90
260
  end
91
261
 
92
262
  def conjunction
@@ -105,8 +275,9 @@ module RSpec
105
275
 
106
276
  private
107
277
 
108
- def match(_expected, actual)
109
- matcher_1.matches?(actual) || matcher_2.matches?(actual)
278
+ def match(*)
279
+ super
280
+ matcher_1_matches? || matcher_2_matches?
110
281
  end
111
282
 
112
283
  def conjunction
@@ -1,6 +1,7 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # rubocop:disable Metrics/ClassLength
4
5
  # @api private
5
6
  # Provides the implementation for `contain_exactly` and `match_array`.
6
7
  # Not intended to be instantiated directly.
@@ -9,31 +10,74 @@ module RSpec
9
10
  # @return [String]
10
11
  def failure_message
11
12
  if Array === actual
12
- message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n"
13
- message += "actual collection contained: #{safe_sort(actual).inspect}\n"
14
- message += "the missing elements were: #{safe_sort(surface_descriptions_in missing_items).inspect}\n" unless missing_items.empty?
15
- message += "the extra elements were: #{safe_sort(extra_items).inspect}\n" unless extra_items.empty?
16
- message
13
+ generate_failure_message
17
14
  else
18
15
  "expected a collection that can be converted to an array with " \
19
- "`#to_ary` or `#to_a`, but got #{actual.inspect}"
16
+ "`#to_ary` or `#to_a`, but got #{actual_formatted}"
20
17
  end
21
18
  end
22
19
 
23
20
  # @api private
24
21
  # @return [String]
25
22
  def failure_message_when_negated
26
- "`contain_exactly` does not support negation"
23
+ list = EnglishPhrasing.list(surface_descriptions_in(expected))
24
+ "expected #{actual_formatted} not to contain exactly#{list}"
27
25
  end
28
26
 
29
27
  # @api private
30
28
  # @return [String]
31
29
  def description
32
- "contain exactly#{to_sentence(surface_descriptions_in expected)}"
30
+ list = EnglishPhrasing.list(surface_descriptions_in(expected))
31
+ "contain exactly#{list}"
32
+ end
33
+
34
+ def matches?(actual)
35
+ @pairings_maximizer = nil
36
+ @best_solution = nil
37
+ @extra_items = nil
38
+ @missing_items = nil
39
+ super(actual)
33
40
  end
34
41
 
35
42
  private
36
43
 
44
+ def generate_failure_message
45
+ message = expected_collection_line
46
+ message += actual_collection_line
47
+ message += missing_elements_line unless missing_items.empty?
48
+ message += extra_elements_line unless extra_items.empty?
49
+ message
50
+ end
51
+
52
+ def expected_collection_line
53
+ message_line('expected collection contained', expected, true)
54
+ end
55
+
56
+ def actual_collection_line
57
+ message_line('actual collection contained', actual)
58
+ end
59
+
60
+ def missing_elements_line
61
+ message_line('the missing elements were', missing_items, true)
62
+ end
63
+
64
+ def extra_elements_line
65
+ message_line('the extra elements were', extra_items)
66
+ end
67
+
68
+ def describe_collection(collection, surface_descriptions=false)
69
+ if surface_descriptions
70
+ "#{description_of(safe_sort(surface_descriptions_in collection))}\n"
71
+ else
72
+ "#{description_of(safe_sort(collection))}\n"
73
+ end
74
+ end
75
+
76
+ def message_line(prefix, collection, surface_descriptions=false)
77
+ "%-32s%s" % [prefix + ':',
78
+ describe_collection(collection, surface_descriptions)]
79
+ end
80
+
37
81
  def match(_expected, _actual)
38
82
  return false unless convert_actual_to_an_array
39
83
  match_when_sorted? || (extra_items.empty? && missing_items.empty?)
@@ -50,15 +94,30 @@ module RSpec
50
94
  def convert_actual_to_an_array
51
95
  if actual.respond_to?(:to_ary)
52
96
  @actual = actual.to_ary
53
- elsif enumerable?(actual) && actual.respond_to?(:to_a)
97
+ elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
54
98
  @actual = actual.to_a
55
99
  else
56
- return false
100
+ false
57
101
  end
58
102
  end
59
103
 
60
104
  def safe_sort(array)
61
- array.sort rescue array
105
+ array.sort
106
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue
107
+ array
108
+ end
109
+
110
+ if RUBY_VERSION == "1.8.7"
111
+ def to_a_disallowed?(object)
112
+ case object
113
+ when NilClass, String then true
114
+ else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
115
+ end
116
+ end
117
+ else
118
+ def to_a_disallowed?(object)
119
+ NilClass === object
120
+ end
62
121
  end
63
122
 
64
123
  def missing_items
@@ -125,6 +184,7 @@ module RSpec
125
184
  #
126
185
  # @private
127
186
  class PairingsMaximizer
187
+ # @private
128
188
  Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
129
189
  :indeterminate_expected_indexes, :indeterminate_actual_indexes) do
130
190
  def worse_than?(other)
@@ -227,7 +287,7 @@ module RSpec
227
287
 
228
288
  modified_expecteds.delete(expected_index)
229
289
 
230
- modified_actuals = apply_pairing_to(
290
+ modified_actuals = apply_pairing_to(
231
291
  solution.indeterminate_actual_indexes,
232
292
  actual_to_expected_matched_indexes, expected_index)
233
293
 
@@ -244,6 +304,7 @@ module RSpec
244
304
  end
245
305
  end
246
306
  end
307
+ # rubocop:enable Metrics/ClassLength
247
308
  end
248
309
  end
249
310
  end
@@ -0,0 +1,169 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Abstract class to implement `once`, `at_least` and other
6
+ # count constraints.
7
+ module CountExpectation
8
+ # @api public
9
+ # Specifies that the method is expected to match once.
10
+ def once
11
+ exactly(1)
12
+ end
13
+
14
+ # @api public
15
+ # Specifies that the method is expected to match twice.
16
+ def twice
17
+ exactly(2)
18
+ end
19
+
20
+ # @api public
21
+ # Specifies that the method is expected to match thrice.
22
+ def thrice
23
+ exactly(3)
24
+ end
25
+
26
+ # @api public
27
+ # Specifies that the method is expected to match the given number of times.
28
+ def exactly(number)
29
+ set_expected_count(:==, number)
30
+ self
31
+ end
32
+
33
+ # @api public
34
+ # Specifies the maximum number of times the method is expected to match
35
+ def at_most(number)
36
+ set_expected_count(:<=, number)
37
+ self
38
+ end
39
+
40
+ # @api public
41
+ # Specifies the minimum number of times the method is expected to match
42
+ def at_least(number)
43
+ set_expected_count(:>=, number)
44
+ self
45
+ end
46
+
47
+ # @api public
48
+ # No-op. Provides syntactic sugar.
49
+ def times
50
+ self
51
+ end
52
+
53
+ protected
54
+ # @api private
55
+ attr_reader :count_expectation_type, :expected_count
56
+
57
+ private
58
+
59
+ if RUBY_VERSION.to_f > 1.8
60
+ def cover?(count, number)
61
+ count.cover?(number)
62
+ end
63
+ else
64
+ def cover?(count, number)
65
+ number >= count.first && number <= count.last
66
+ end
67
+ end
68
+
69
+ def expected_count_matches?(actual_count)
70
+ @actual_count = actual_count
71
+ return @actual_count > 0 unless count_expectation_type
72
+ return cover?(expected_count, actual_count) if count_expectation_type == :<=>
73
+
74
+ @actual_count.__send__(count_expectation_type, expected_count)
75
+ end
76
+
77
+ def has_expected_count?
78
+ !!count_expectation_type
79
+ end
80
+
81
+ def set_expected_count(relativity, n)
82
+ raise_unsupported_count_expectation if unsupported_count_expectation?(relativity)
83
+
84
+ count = count_constraint_to_number(n)
85
+
86
+ if count_expectation_type == :<= && relativity == :>=
87
+ raise_impossible_count_expectation(count) if count > expected_count
88
+ @count_expectation_type = :<=>
89
+ @expected_count = count..expected_count
90
+ elsif count_expectation_type == :>= && relativity == :<=
91
+ raise_impossible_count_expectation(count) if count < expected_count
92
+ @count_expectation_type = :<=>
93
+ @expected_count = expected_count..count
94
+ else
95
+ @count_expectation_type = relativity
96
+ @expected_count = count
97
+ end
98
+ end
99
+
100
+ def raise_impossible_count_expectation(count)
101
+ text =
102
+ case count_expectation_type
103
+ when :<= then "at_least(#{count}).at_most(#{expected_count})"
104
+ when :>= then "at_least(#{expected_count}).at_most(#{count})"
105
+ end
106
+ raise ArgumentError, "The constraint #{text} is not possible"
107
+ end
108
+
109
+ def raise_unsupported_count_expectation
110
+ text =
111
+ case count_expectation_type
112
+ when :<= then "at_least"
113
+ when :>= then "at_most"
114
+ when :<=> then "at_least/at_most combination"
115
+ else "count"
116
+ end
117
+ raise ArgumentError, "Multiple #{text} constraints are not supported"
118
+ end
119
+
120
+ def count_constraint_to_number(n)
121
+ case n
122
+ when Numeric then n
123
+ when :once then 1
124
+ when :twice then 2
125
+ when :thrice then 3
126
+ else
127
+ raise ArgumentError, "Expected a number, :once, :twice or :thrice," \
128
+ " but got #{n}"
129
+ end
130
+ end
131
+
132
+ def unsupported_count_expectation?(relativity)
133
+ return true if count_expectation_type == :==
134
+ return true if count_expectation_type == :<=>
135
+ (count_expectation_type == :<= && relativity == :<=) ||
136
+ (count_expectation_type == :>= && relativity == :>=)
137
+ end
138
+
139
+ def count_expectation_description
140
+ "#{human_readable_expectation_type}#{human_readable_count(expected_count)}"
141
+ end
142
+
143
+ def count_failure_reason(action)
144
+ "#{count_expectation_description}" \
145
+ " but #{action}#{human_readable_count(@actual_count)}"
146
+ end
147
+
148
+ def human_readable_expectation_type
149
+ case count_expectation_type
150
+ when :<= then ' at most'
151
+ when :>= then ' at least'
152
+ when :<=> then ' between'
153
+ else ''
154
+ end
155
+ end
156
+
157
+ def human_readable_count(count)
158
+ case count
159
+ when Range then " #{count.first} and #{count.last} times"
160
+ when nil then ''
161
+ when 1 then ' once'
162
+ when 2 then ' twice'
163
+ else " #{count} times"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -8,19 +8,19 @@ module RSpec
8
8
  # @api private
9
9
  # @return [String]
10
10
  def failure_message
11
- "\nexpected: #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
11
+ "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
12
12
  end
13
13
 
14
14
  # @api private
15
15
  # @return [String]
16
16
  def failure_message_when_negated
17
- "\nexpected: value != #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
17
+ "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
18
18
  end
19
19
 
20
20
  # @api private
21
21
  # @return [String]
22
22
  def description
23
- "#{name_to_sentence} #{@expected.inspect}"
23
+ "eq #{expected_formatted}"
24
24
  end
25
25
 
26
26
  # @api private
@@ -34,41 +34,6 @@ module RSpec
34
34
  def match(expected, actual)
35
35
  actual == expected
36
36
  end
37
-
38
- def format_object(object)
39
- if Time === object
40
- format_time(object)
41
- elsif defined?(DateTime) && DateTime === object
42
- format_date_time(object)
43
- elsif defined?(BigDecimal) && BigDecimal === object
44
- "#{object.to_s 'F'} (#{object.inspect})"
45
- else
46
- object.inspect
47
- end
48
- end
49
-
50
- TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
51
-
52
- if Time.method_defined?(:nsec)
53
- def format_time(time)
54
- time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
55
- end
56
- else # for 1.8.7
57
- def format_time(time)
58
- time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
59
- end
60
- end
61
-
62
- DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
63
- # ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
64
- # defined use a custom format string that includes more time precision.
65
- def format_date_time(date_time)
66
- if defined?(ActiveSupport)
67
- date_time.strftime(DATE_TIME_FORMAT)
68
- else
69
- date_time.inspect
70
- end
71
- end
72
37
  end
73
38
  end
74
39
  end
@@ -8,13 +8,13 @@ module RSpec
8
8
  # @api private
9
9
  # @return [String]
10
10
  def failure_message
11
- "\nexpected: #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
11
+ "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
12
12
  end
13
13
 
14
14
  # @api private
15
15
  # @return [String]
16
16
  def failure_message_when_negated
17
- "\nexpected: value != #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
17
+ "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
18
18
  end
19
19
 
20
20
  # @api private