rspec-expectations 3.0.0.beta1 → 3.0.0.beta2

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 (122) hide show
  1. data.tar.gz.sig +2 -2
  2. data/.yardopts +1 -0
  3. data/Changelog.md +138 -0
  4. data/README.md +75 -8
  5. data/features/README.md +2 -2
  6. data/features/built_in_matchers/README.md +12 -9
  7. data/features/built_in_matchers/comparisons.feature +2 -2
  8. data/features/built_in_matchers/contain_exactly.feature +46 -0
  9. data/features/built_in_matchers/expect_change.feature +2 -2
  10. data/features/built_in_matchers/include.feature +0 -48
  11. data/features/built_in_matchers/output.feature +70 -0
  12. data/features/composing_matchers.feature +250 -0
  13. data/features/compound_expectations.feature +45 -0
  14. data/features/custom_matchers/access_running_example.feature +1 -1
  15. data/features/custom_matchers/define_matcher.feature +6 -6
  16. data/features/custom_matchers/define_matcher_outside_rspec.feature +4 -8
  17. data/features/test_frameworks/{test_unit.feature → minitest.feature} +11 -11
  18. data/lib/rspec/expectations.rb +31 -42
  19. data/lib/rspec/expectations/diff_presenter.rb +141 -0
  20. data/lib/rspec/expectations/differ.rb +22 -132
  21. data/lib/rspec/expectations/encoded_string.rb +56 -0
  22. data/lib/rspec/expectations/expectation_target.rb +0 -30
  23. data/lib/rspec/expectations/fail_with.rb +2 -2
  24. data/lib/rspec/expectations/handler.rb +128 -31
  25. data/lib/rspec/expectations/minitest_integration.rb +16 -0
  26. data/lib/rspec/expectations/syntax.rb +4 -58
  27. data/lib/rspec/expectations/version.rb +1 -1
  28. data/lib/rspec/matchers.rb +298 -60
  29. data/lib/rspec/matchers/aliased_matcher.rb +35 -0
  30. data/lib/rspec/matchers/built_in.rb +37 -33
  31. data/lib/rspec/matchers/built_in/base_matcher.rb +25 -15
  32. data/lib/rspec/matchers/built_in/be.rb +23 -31
  33. data/lib/rspec/matchers/built_in/be_between.rb +55 -0
  34. data/lib/rspec/matchers/built_in/be_within.rb +15 -11
  35. data/lib/rspec/matchers/built_in/change.rb +198 -81
  36. data/lib/rspec/matchers/built_in/compound.rb +106 -0
  37. data/lib/rspec/matchers/built_in/contain_exactly.rb +245 -0
  38. data/lib/rspec/matchers/built_in/eq.rb +43 -4
  39. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  40. data/lib/rspec/matchers/built_in/equal.rb +35 -18
  41. data/lib/rspec/matchers/built_in/has.rb +16 -15
  42. data/lib/rspec/matchers/built_in/include.rb +45 -23
  43. data/lib/rspec/matchers/built_in/match.rb +6 -3
  44. data/lib/rspec/matchers/built_in/operators.rb +103 -0
  45. data/lib/rspec/matchers/built_in/output.rb +108 -0
  46. data/lib/rspec/matchers/built_in/raise_error.rb +9 -15
  47. data/lib/rspec/matchers/built_in/respond_to.rb +5 -4
  48. data/lib/rspec/matchers/built_in/satisfy.rb +4 -3
  49. data/lib/rspec/matchers/built_in/start_and_end_with.rb +37 -16
  50. data/lib/rspec/matchers/built_in/throw_symbol.rb +6 -5
  51. data/lib/rspec/matchers/built_in/yield.rb +31 -29
  52. data/lib/rspec/matchers/composable.rb +138 -0
  53. data/lib/rspec/matchers/dsl.rb +330 -0
  54. data/lib/rspec/matchers/generated_descriptions.rb +6 -6
  55. data/lib/rspec/matchers/matcher_delegator.rb +33 -0
  56. data/lib/rspec/matchers/pretty.rb +13 -2
  57. data/spec/rspec/expectations/{differ_spec.rb → diff_presenter_spec.rb} +56 -36
  58. data/spec/rspec/expectations/encoded_string_spec.rb +74 -0
  59. data/spec/rspec/expectations/extensions/kernel_spec.rb +11 -11
  60. data/spec/rspec/expectations/fail_with_spec.rb +8 -8
  61. data/spec/rspec/expectations/handler_spec.rb +27 -49
  62. data/spec/rspec/expectations/minitest_integration_spec.rb +27 -0
  63. data/spec/rspec/expectations/syntax_spec.rb +17 -67
  64. data/spec/rspec/expectations_spec.rb +7 -52
  65. data/spec/rspec/matchers/aliased_matcher_spec.rb +48 -0
  66. data/spec/rspec/matchers/aliases_spec.rb +449 -0
  67. data/spec/rspec/matchers/{base_matcher_spec.rb → built_in/base_matcher_spec.rb} +24 -3
  68. data/spec/rspec/matchers/built_in/be_between_spec.rb +159 -0
  69. data/spec/rspec/matchers/{be_instance_of_spec.rb → built_in/be_instance_of_spec.rb} +0 -0
  70. data/spec/rspec/matchers/{be_kind_of_spec.rb → built_in/be_kind_of_spec.rb} +0 -0
  71. data/spec/rspec/matchers/{be_spec.rb → built_in/be_spec.rb} +76 -32
  72. data/spec/rspec/matchers/{be_within_spec.rb → built_in/be_within_spec.rb} +6 -2
  73. data/spec/rspec/matchers/{change_spec.rb → built_in/change_spec.rb} +310 -69
  74. data/spec/rspec/matchers/built_in/compound_spec.rb +292 -0
  75. data/spec/rspec/matchers/built_in/contain_exactly_spec.rb +441 -0
  76. data/spec/rspec/matchers/{cover_spec.rb → built_in/cover_spec.rb} +0 -0
  77. data/spec/rspec/matchers/built_in/eq_spec.rb +156 -0
  78. data/spec/rspec/matchers/{eql_spec.rb → built_in/eql_spec.rb} +2 -2
  79. data/spec/rspec/matchers/built_in/equal_spec.rb +106 -0
  80. data/spec/rspec/matchers/{exist_spec.rb → built_in/exist_spec.rb} +1 -1
  81. data/spec/rspec/matchers/{has_spec.rb → built_in/has_spec.rb} +39 -0
  82. data/spec/rspec/matchers/{include_spec.rb → built_in/include_spec.rb} +118 -109
  83. data/spec/rspec/matchers/{match_spec.rb → built_in/match_spec.rb} +30 -2
  84. data/spec/rspec/matchers/{operator_matcher_spec.rb → built_in/operators_spec.rb} +26 -26
  85. data/spec/rspec/matchers/built_in/output_spec.rb +165 -0
  86. data/spec/rspec/matchers/{raise_error_spec.rb → built_in/raise_error_spec.rb} +81 -11
  87. data/spec/rspec/matchers/{respond_to_spec.rb → built_in/respond_to_spec.rb} +0 -0
  88. data/spec/rspec/matchers/{satisfy_spec.rb → built_in/satisfy_spec.rb} +0 -0
  89. data/spec/rspec/matchers/{start_with_end_with_spec.rb → built_in/start_and_end_with_spec.rb} +82 -15
  90. data/spec/rspec/matchers/{throw_symbol_spec.rb → built_in/throw_symbol_spec.rb} +29 -10
  91. data/spec/rspec/matchers/{yield_spec.rb → built_in/yield_spec.rb} +90 -0
  92. data/spec/rspec/matchers/configuration_spec.rb +7 -39
  93. data/spec/rspec/matchers/description_generation_spec.rb +22 -6
  94. data/spec/rspec/matchers/dsl_spec.rb +838 -0
  95. data/spec/rspec/matchers/legacy_spec.rb +101 -0
  96. data/spec/rspec/matchers_spec.rb +74 -0
  97. data/spec/spec_helper.rb +35 -21
  98. data/spec/support/shared_examples.rb +26 -4
  99. metadata +172 -116
  100. metadata.gz.sig +3 -4
  101. checksums.yaml +0 -15
  102. checksums.yaml.gz.sig +0 -0
  103. data/features/built_in_matchers/match_array.feature +0 -37
  104. data/lib/rspec/expectations/errors.rb +0 -9
  105. data/lib/rspec/expectations/extensions.rb +0 -1
  106. data/lib/rspec/expectations/extensions/object.rb +0 -29
  107. data/lib/rspec/matchers/built_in/match_array.rb +0 -51
  108. data/lib/rspec/matchers/compatibility.rb +0 -14
  109. data/lib/rspec/matchers/matcher.rb +0 -301
  110. data/lib/rspec/matchers/method_missing.rb +0 -12
  111. data/lib/rspec/matchers/operator_matcher.rb +0 -99
  112. data/lib/rspec/matchers/test_unit_integration.rb +0 -11
  113. data/spec/rspec/matchers/eq_spec.rb +0 -60
  114. data/spec/rspec/matchers/equal_spec.rb +0 -78
  115. data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
  116. data/spec/rspec/matchers/match_array_spec.rb +0 -194
  117. data/spec/rspec/matchers/matcher_spec.rb +0 -706
  118. data/spec/rspec/matchers/matchers_spec.rb +0 -36
  119. data/spec/rspec/matchers/method_missing_spec.rb +0 -28
  120. data/spec/support/classes.rb +0 -56
  121. data/spec/support/in_sub_process.rb +0 -37
  122. data/spec/support/ruby_version.rb +0 -10
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class RespondTo
5
+ include Composable
6
+
5
7
  def initialize(*names)
6
8
  @names = names
7
9
  @expected_arity = nil
@@ -10,18 +12,17 @@ module RSpec
10
12
  def matches?(actual)
11
13
  find_failing_method_names(actual, :reject).empty?
12
14
  end
13
- alias == matches?
14
15
 
15
16
  def does_not_match?(actual)
16
17
  find_failing_method_names(actual, :select).empty?
17
18
  end
18
19
 
19
- def failure_message_for_should
20
+ def failure_message
20
21
  "expected #{@actual.inspect} to respond to #{@failing_method_names.collect {|name| name.inspect }.join(', ')}#{with_arity}"
21
22
  end
22
23
 
23
- def failure_message_for_should_not
24
- failure_message_for_should.sub(/to respond to/, 'not to respond to')
24
+ def failure_message_when_negated
25
+ failure_message.sub(/to respond to/, 'not to respond to')
25
26
  end
26
27
 
27
28
  def description
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class Satisfy
5
+ include Composable
6
+
5
7
  def initialize(&block)
6
8
  @block = block
7
9
  end
@@ -11,13 +13,12 @@ module RSpec
11
13
  @actual = actual
12
14
  @block.call(actual)
13
15
  end
14
- alias == matches?
15
16
 
16
- def failure_message_for_should
17
+ def failure_message
17
18
  "expected #{@actual} to satisfy block"
18
19
  end
19
20
 
20
- def failure_message_for_should_not
21
+ def failure_message_when_negated
21
22
  "expected #{@actual} not to satisfy block"
22
23
  end
23
24
 
@@ -3,44 +3,65 @@ module RSpec
3
3
  module BuiltIn
4
4
  class StartAndEndWith < BaseMatcher
5
5
  def initialize(*expected)
6
+ @actual_does_not_have_ordered_elements = false
6
7
  @expected = expected.length == 1 ? expected.first : expected
7
8
  end
8
9
 
9
- def matches?(actual)
10
- @actual = actual.respond_to?(:[]) ? actual : (raise ArgumentError.new("#{actual.inspect} does not respond to :[]"))
10
+ def match(expected, actual)
11
+ return false unless actual.respond_to?(:[])
12
+
11
13
  begin
12
- @expected.respond_to?(:length) ? subset_matches?(@expected, @actual) : element_matches?(@expected, @actual)
14
+ return subset_matches? if expected.respond_to?(:length)
15
+ element_matches?
13
16
  rescue ArgumentError
14
- raise ArgumentError.new("#{actual.inspect} does not have ordered elements")
17
+ @actual_does_not_have_ordered_elements = true
18
+ return false
15
19
  end
16
20
  end
17
21
 
18
- def failure_message_for_should
19
- "expected #{@actual.inspect} to #{self.class.name.split('::').last.sub(/With/,'').downcase} with #{@expected.inspect}"
22
+ def failure_message
23
+ super.tap do |msg|
24
+ if @actual_does_not_have_ordered_elements
25
+ msg << ", but it does not have ordered elements"
26
+ elsif !actual.respond_to?(:[])
27
+ msg << ", but it cannot be indexed using #[]"
28
+ end
29
+ end
20
30
  end
21
31
 
22
- def failure_message_for_should_not
23
- "expected #{@actual.inspect} not to #{self.class.name.split('::').last.sub(/With/,'').downcase} with #{@expected.inspect}"
32
+ def description
33
+ return super unless Hash === expected
34
+ "#{name_to_sentence} #{surface_descriptions_in(expected).inspect}"
35
+ end
36
+
37
+ private
38
+
39
+ def actual_is_unordered
40
+ ArgumentError.new("#{actual.inspect} does not have ordered elements")
24
41
  end
25
42
  end
26
43
 
27
44
  class StartWith < StartAndEndWith
28
- def subset_matches?(expected, actual)
29
- actual[0, expected.length] == expected
45
+ private
46
+
47
+ def subset_matches?
48
+ values_match?(expected, actual[0, expected.length])
30
49
  end
31
50
 
32
- def element_matches?(expected, actual)
33
- @actual[0] == @expected
51
+ def element_matches?
52
+ values_match?(expected, actual[0])
34
53
  end
35
54
  end
36
55
 
37
56
  class EndWith < StartAndEndWith
38
- def subset_matches?(expected, actual)
39
- actual[-expected.length, expected.length] == expected
57
+ private
58
+
59
+ def subset_matches?
60
+ values_match?(expected, actual[-expected.length, expected.length])
40
61
  end
41
62
 
42
- def element_matches?(expected, actual)
43
- actual[-1] == expected
63
+ def element_matches?
64
+ values_match?(expected, actual[-1])
44
65
  end
45
66
  end
46
67
  end
@@ -2,6 +2,8 @@ module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
4
  class ThrowSymbol
5
+ include Composable
6
+
5
7
  def initialize(expected_symbol = nil, expected_arg=nil)
6
8
  @expected_symbol = expected_symbol
7
9
  @expected_arg = expected_arg
@@ -45,19 +47,18 @@ module RSpec
45
47
  if @expected_arg.nil?
46
48
  return @caught_symbol == @expected_symbol
47
49
  else
48
- return (@caught_symbol == @expected_symbol) & (@caught_arg == @expected_arg)
50
+ return (@caught_symbol == @expected_symbol) && values_match?(@expected_arg, @caught_arg)
49
51
  end
50
52
  end
51
53
  end
52
54
  end
53
55
  end
54
- alias == matches?
55
56
 
56
- def failure_message_for_should
57
+ def failure_message
57
58
  "expected #{expected} to be thrown, got #{caught}"
58
59
  end
59
60
 
60
- def failure_message_for_should_not
61
+ def failure_message_when_negated
61
62
  "expected #{expected('no Symbol')}#{' not' if @expected_symbol} to be thrown, got #{caught}"
62
63
  end
63
64
 
@@ -79,7 +80,7 @@ module RSpec
79
80
  symbol_description = symbol.is_a?(String) ? symbol : symbol.inspect
80
81
 
81
82
  arg_description = if arg
82
- " with #{arg.inspect}"
83
+ " with #{description_of arg}"
83
84
  elsif @expected_arg && @caught_symbol == @expected_symbol
84
85
  " with no argument"
85
86
  else
@@ -110,13 +110,13 @@ module RSpec
110
110
  self
111
111
  end
112
112
 
113
- def failure_message_for_should
113
+ def failure_message
114
114
  'expected given block to yield control'.tap do |failure_message|
115
115
  failure_message << relativity_failure_message
116
116
  end
117
117
  end
118
118
 
119
- def failure_message_for_should_not
119
+ def failure_message_when_negated
120
120
  'expected given block not to yield control'.tap do |failure_message|
121
121
  failure_message << relativity_failure_message
122
122
  end
@@ -162,11 +162,11 @@ module RSpec
162
162
  @probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.empty?
163
163
  end
164
164
 
165
- def failure_message_for_should
165
+ def failure_message
166
166
  "expected given block to yield with no arguments, but #{failure_reason}"
167
167
  end
168
168
 
169
- def failure_message_for_should_not
169
+ def failure_message_when_negated
170
170
  "expected given block not to yield with no arguments, but did"
171
171
  end
172
172
 
@@ -182,6 +182,8 @@ module RSpec
182
182
  end
183
183
 
184
184
  class YieldWithArgs
185
+ include Composable
186
+
185
187
  def initialize(*args)
186
188
  @expected = args
187
189
  end
@@ -191,19 +193,18 @@ module RSpec
191
193
  @actual = @probe.single_yield_args
192
194
  @probe.yielded_once?(:yield_with_args) && args_match?
193
195
  end
194
- alias == matches?
195
196
 
196
- def failure_message_for_should
197
+ def failure_message
197
198
  "expected given block to yield with arguments, but #{positive_failure_reason}"
198
199
  end
199
200
 
200
- def failure_message_for_should_not
201
+ def failure_message_when_negated
201
202
  "expected given block not to yield with arguments, but #{negative_failure_reason}"
202
203
  end
203
204
 
204
205
  def description
205
206
  desc = "yield with args"
206
- desc << "(" + @expected.map { |e| e.inspect }.join(", ") + ")" unless @expected.empty?
207
+ desc << "(#{expected_arg_description})" unless @expected.empty?
207
208
  desc
208
209
  end
209
210
 
@@ -217,11 +218,15 @@ module RSpec
217
218
  end
218
219
  end
219
220
 
221
+ def expected_arg_description
222
+ @expected.map { |e| description_of e }.join(", ")
223
+ end
224
+
220
225
  def negative_failure_reason
221
226
  if all_args_match?
222
227
  "yielded with expected arguments" +
223
- "\nexpected not: #{@expected.inspect}" +
224
- "\n got: #{@actual.inspect} (compared using === and ==)"
228
+ "\nexpected not: #{surface_descriptions_in(@expected).inspect}" +
229
+ "\n got: #{@actual.inspect}"
225
230
  else
226
231
  "did"
227
232
  end
@@ -235,23 +240,21 @@ module RSpec
235
240
 
236
241
  unless match = all_args_match?
237
242
  @positive_args_failure = "yielded with unexpected arguments" +
238
- "\nexpected: #{@expected.inspect}" +
239
- "\n got: #{@actual.inspect} (compared using === and ==)"
243
+ "\nexpected: #{surface_descriptions_in(@expected).inspect}" +
244
+ "\n got: #{@actual.inspect}"
240
245
  end
241
246
 
242
247
  match
243
248
  end
244
249
 
245
250
  def all_args_match?
246
- return false if @expected.size != @actual.size
247
-
248
- @expected.zip(@actual).all? do |expected, actual|
249
- expected === actual || actual == expected
250
- end
251
+ values_match?(@expected, @actual)
251
252
  end
252
253
  end
253
254
 
254
255
  class YieldSuccessiveArgs
256
+ include Composable
257
+
255
258
  def initialize(*args)
256
259
  @expected = args
257
260
  end
@@ -261,34 +264,33 @@ module RSpec
261
264
  @actual = @probe.successive_yield_args
262
265
  args_match?
263
266
  end
264
- alias == matches?
265
267
 
266
- def failure_message_for_should
268
+ def failure_message
267
269
  "expected given block to yield successively with arguments, but yielded with unexpected arguments" +
268
- "\nexpected: #{@expected.inspect}" +
269
- "\n got: #{@actual.inspect} (compared using === and ==)"
270
+ "\nexpected: #{surface_descriptions_in(@expected).inspect}" +
271
+ "\n got: #{@actual.inspect}"
270
272
  end
271
273
 
272
- def failure_message_for_should_not
274
+ def failure_message_when_negated
273
275
  "expected given block not to yield successively with arguments, but yielded with expected arguments" +
274
- "\nexpected not: #{@expected.inspect}" +
275
- "\n got: #{@actual.inspect} (compared using === and ==)"
276
+ "\nexpected not: #{surface_descriptions_in(@expected).inspect}" +
277
+ "\n got: #{@actual.inspect}"
276
278
  end
277
279
 
278
280
  def description
279
281
  desc = "yield successive args"
280
- desc << "(" + @expected.map { |e| e.inspect }.join(", ") + ")"
282
+ desc << "(#{expected_arg_description})"
281
283
  desc
282
284
  end
283
285
 
284
286
  private
285
287
 
286
288
  def args_match?
287
- return false if @expected.size != @actual.size
289
+ values_match?(@expected, @actual)
290
+ end
288
291
 
289
- @expected.zip(@actual).all? do |expected, actual|
290
- expected === actual || actual == expected
291
- end
292
+ def expected_arg_description
293
+ @expected.map { |e| description_of e }.join(", ")
292
294
  end
293
295
  end
294
296
  end
@@ -0,0 +1,138 @@
1
+ require 'rspec/support/fuzzy_matcher'
2
+
3
+ module RSpec
4
+ module Matchers
5
+ # Mixin designed to support the composable matcher features
6
+ # of RSpec 3+. Mix it into your custom matcher classes to
7
+ # allow them to be used in a composable fashion.
8
+ #
9
+ # @api public
10
+ module Composable
11
+ # Creates a compound `and` expectation. The matcher will
12
+ # only pass if both sub-matchers pass.
13
+ # This can be chained together to form an arbitrarily long
14
+ # chain of matchers.
15
+ #
16
+ # @example
17
+ # expect(alphabet).to start_with("a").and end_with("z")
18
+ #
19
+ # @note The negative form (`expect(...).not_to matcher.and other`)
20
+ # is not supported at this time.
21
+ def and(matcher)
22
+ BuiltIn::Compound::And.new self, matcher
23
+ end
24
+
25
+ # Creates a compound `or` expectation. The matcher will
26
+ # pass if either sub-matcher passes.
27
+ # This can be chained together to form an arbitrarily long
28
+ # chain of matchers.
29
+ #
30
+ # @example
31
+ # expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
32
+ #
33
+ # @note The negative form (`expect(...).not_to matcher.or other`)
34
+ # is not supported at this time.
35
+ def or(matcher)
36
+ BuiltIn::Compound::Or.new self, matcher
37
+ end
38
+
39
+ # Delegates to `#matches?`. Allows matchers to be used in composable
40
+ # fashion and also supports using matchers in case statements.
41
+ def ===(value)
42
+ matches?(value)
43
+ end
44
+
45
+ private unless defined?(::YARD)
46
+
47
+ # This provides a generic way to fuzzy-match an expected value against
48
+ # an actual value. It understands nested data structures (e.g. hashes
49
+ # and arrays) and is able to match against a matcher being used as
50
+ # the expected value or within the expected value at any level of
51
+ # nesting.
52
+ #
53
+ # Within a custom matcher you are encouraged to use this whenever your
54
+ # matcher needs to match two values, unless it needs more precise semantics.
55
+ # For example, the `eq` matcher _does not_ use this as it is meant to
56
+ # use `==` (and only `==`) for matching.
57
+ #
58
+ # @param expected [Object] what is expected
59
+ # @param actual [Object] the actual value
60
+ #
61
+ # @public
62
+ def values_match?(expected, actual)
63
+ Support::FuzzyMatcher.values_match?(expected, actual)
64
+ end
65
+
66
+ # Returns the description of the given object in a way that is
67
+ # aware of composed matchers. If the object is a matcher with
68
+ # a `description` method, returns the description; otherwise
69
+ # returns `object.inspect`.
70
+ #
71
+ # You are encouraged to use this in your custom matcher's
72
+ # `description`, `failure_message` or
73
+ # `failure_message_when_negated` implementation if you are
74
+ # supporting matcher arguments.
75
+ #
76
+ # @api public
77
+ def description_of(object)
78
+ return object.description if Matchers.is_a_describable_matcher?(object)
79
+ object.inspect
80
+ end
81
+
82
+ # Transforms the given data structue (typically a hash or array)
83
+ # into a new data structure that, when `#inspect` is called on it,
84
+ # will provide descriptions of any contained matchers rather than
85
+ # the normal `#inspect` output.
86
+ #
87
+ # You are encouraged to use this in your custom matcher's
88
+ # `description`, `failure_message` or
89
+ # `failure_message_when_negated` implementation if you are
90
+ # supporting any arguments which may be a data structure
91
+ # containing matchers.
92
+ #
93
+ # @api public
94
+ def surface_descriptions_in(item)
95
+ if Matchers.is_a_describable_matcher?(item)
96
+ DescribableItem.new(item)
97
+ elsif Hash === item
98
+ Hash[ surface_descriptions_in(item.to_a) ]
99
+ elsif enumerable?(item)
100
+ item.map { |subitem| surface_descriptions_in(subitem) }
101
+ else
102
+ item
103
+ end
104
+ end
105
+
106
+ if String.ancestors.include?(Enumerable) # 1.8.7
107
+ # Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
108
+ # nested enumerable: since ruby lacks a character class, it yields
109
+ # 1-character strings, which are themselves enumerable, composed of a
110
+ # a single 1-character string, which is an enumerable, etc.
111
+ #
112
+ # @api private
113
+ def enumerable?(item)
114
+ return false if String === item
115
+ Enumerable === item
116
+ end
117
+ else
118
+ # @api private
119
+ def enumerable?(item)
120
+ Enumerable === item
121
+ end
122
+ end
123
+ module_function :surface_descriptions_in, :enumerable? unless defined?(::YARD)
124
+
125
+ # Wraps an item in order to surface its `description` via `inspect`.
126
+ # @api private
127
+ DescribableItem = Struct.new(:item) do
128
+ def inspect
129
+ "(#{item.description})"
130
+ end
131
+
132
+ def pretty_print(pp)
133
+ pp.text "(#{item.description})"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end