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
@@ -80,11 +80,10 @@ module RSpec
80
80
  #
81
81
  # @!visibility public
82
82
  def description_of(object)
83
- return object.description if Matchers.is_a_describable_matcher?(object)
84
- object.inspect
83
+ RSpec::Support::ObjectFormatter.format(object)
85
84
  end
86
85
 
87
- # Transforms the given data structue (typically a hash or array)
86
+ # Transforms the given data structure (typically a hash or array)
88
87
  # into a new data structure that, when `#inspect` is called on it,
89
88
  # will provide descriptions of any contained matchers rather than
90
89
  # the normal `#inspect` output.
@@ -101,14 +100,10 @@ module RSpec
101
100
  DescribableItem.new(item)
102
101
  elsif Hash === item
103
102
  Hash[surface_descriptions_in(item.to_a)]
104
- elsif Struct === item
105
- item.inspect
106
- elsif enumerable?(item)
107
- begin
108
- item.map { |subitem| surface_descriptions_in(subitem) }
109
- rescue IOError # STDOUT is enumerable but `map` raises an error
110
- item.inspect
111
- end
103
+ elsif Struct === item || unreadable_io?(item)
104
+ RSpec::Support::ObjectFormatter.format(item)
105
+ elsif should_enumerate?(item)
106
+ item.map { |subitem| surface_descriptions_in(subitem) }
112
107
  else
113
108
  item
114
109
  end
@@ -135,45 +130,38 @@ module RSpec
135
130
  object.clone
136
131
  elsif Hash === object
137
132
  Hash[with_matchers_cloned(object.to_a)]
138
- elsif Struct === object
139
- object
140
- elsif enumerable?(object)
141
- begin
142
- object.map { |subobject| with_matchers_cloned(subobject) }
143
- rescue IOError # STDOUT is enumerable but `map` raises an error
144
- object
145
- end
133
+ elsif should_enumerate?(object)
134
+ object.map { |subobject| with_matchers_cloned(subobject) }
146
135
  else
147
136
  object
148
137
  end
149
138
  end
150
139
 
151
- if String.ancestors.include?(Enumerable) # 1.8.7
152
- # Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
153
- # nested enumerable: since ruby lacks a character class, it yields
154
- # 1-character strings, which are themselves enumerable, composed of a
155
- # a single 1-character string, which is an enumerable, etc.
156
- #
157
- # @api private
158
- def enumerable?(item)
159
- return false if String === item
160
- Enumerable === item
161
- end
162
- else
163
- # @api private
164
- def enumerable?(item)
165
- Enumerable === item
166
- end
140
+ # @api private
141
+ # We should enumerate arrays as long as they are not recursive.
142
+ def should_enumerate?(item)
143
+ Array === item && item.none? { |subitem| subitem.equal?(item) }
144
+ end
145
+
146
+ # @api private
147
+ def unreadable_io?(object)
148
+ return false unless IO === object
149
+ object.each {} # STDOUT is enumerable but raises an error
150
+ false
151
+ rescue IOError
152
+ true
167
153
  end
168
- module_function :surface_descriptions_in, :enumerable?
154
+ module_function :surface_descriptions_in, :should_enumerate?, :unreadable_io?
169
155
 
170
156
  # Wraps an item in order to surface its `description` via `inspect`.
171
157
  # @api private
172
158
  DescribableItem = Struct.new(:item) do
159
+ # Inspectable version of the item description
173
160
  def inspect
174
161
  "(#{item.description})"
175
162
  end
176
163
 
164
+ # A pretty printed version of the item description.
177
165
  def pretty_print(pp)
178
166
  pp.text "(#{item.description})"
179
167
  end
@@ -1,16 +1,102 @@
1
+ RSpec::Support.require_rspec_support "with_keywords_when_needed"
2
+
1
3
  module RSpec
2
4
  module Matchers
3
5
  # Defines the custom matcher DSL.
4
6
  module DSL
7
+ # Defines a matcher alias. The returned matcher's `description` will be overridden
8
+ # to reflect the phrasing of the new name, which will be used in failure messages
9
+ # when passed as an argument to another matcher in a composed matcher expression.
10
+ #
11
+ # @example
12
+ # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
13
+ # sum_to(3).description # => "sum to 3"
14
+ # a_list_that_sums_to(3).description # => "a list that sums to 3"
15
+ #
16
+ # @example
17
+ # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
18
+ # description.sub("be sorted by", "a list sorted by")
19
+ # end
20
+ #
21
+ # be_sorted_by(:age).description # => "be sorted by age"
22
+ # a_list_sorted_by(:age).description # => "a list sorted by age"
23
+ #
24
+ # @param new_name [Symbol] the new name for the matcher
25
+ # @param old_name [Symbol] the original name for the matcher
26
+ # @param options [Hash] options for the aliased matcher
27
+ # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
28
+ # @yield [String] optional block that, when given, is used to define the overridden
29
+ # logic. The yielded arg is the original description or failure message. If no
30
+ # block is provided, a default override is used based on the old and new names.
31
+ # @see RSpec::Matchers
32
+ def alias_matcher(new_name, old_name, options={}, &description_override)
33
+ description_override ||= lambda do |old_desc|
34
+ old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
35
+ end
36
+ klass = options.fetch(:klass) { AliasedMatcher }
37
+
38
+ define_method(new_name) do |*args, &block|
39
+ matcher = __send__(old_name, *args, &block)
40
+ matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=)
41
+ klass.new(matcher, description_override)
42
+ end
43
+ ruby2_keywords new_name if respond_to?(:ruby2_keywords, true)
44
+ end
45
+
46
+ # Defines a negated matcher. The returned matcher's `description` and `failure_message`
47
+ # will be overridden to reflect the phrasing of the new name, and the match logic will
48
+ # be based on the original matcher but negated.
49
+ #
50
+ # @example
51
+ # RSpec::Matchers.define_negated_matcher :exclude, :include
52
+ # include(1, 2).description # => "include 1 and 2"
53
+ # exclude(1, 2).description # => "exclude 1 and 2"
54
+ #
55
+ # @param negated_name [Symbol] the name for the negated matcher
56
+ # @param base_name [Symbol] the name of the original matcher that will be negated
57
+ # @yield [String] optional block that, when given, is used to define the overridden
58
+ # logic. The yielded arg is the original description or failure message. If no
59
+ # block is provided, a default override is used based on the old and new names.
60
+ # @see RSpec::Matchers
61
+ def define_negated_matcher(negated_name, base_name, &description_override)
62
+ alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
63
+ end
64
+
5
65
  # Defines a custom matcher.
66
+ #
67
+ # @param name [Symbol] the name for the matcher
68
+ # @yield [Object] block that is used to define the matcher.
69
+ # The block is evaluated in the context of your custom matcher class.
70
+ # When args are passed to your matcher, they will be yielded here,
71
+ # usually representing the expected value(s).
6
72
  # @see RSpec::Matchers
7
73
  def define(name, &declarations)
8
- define_method name do |*expected|
9
- RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected)
74
+ warn_about_block_args(name, declarations)
75
+ define_method name do |*expected, &block_arg|
76
+ RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg)
10
77
  end
11
78
  end
12
79
  alias_method :matcher, :define
13
80
 
81
+ private
82
+
83
+ if Proc.method_defined?(:parameters)
84
+ def warn_about_block_args(name, declarations)
85
+ declarations.parameters.each do |type, arg_name|
86
+ next unless type == :block
87
+ RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \
88
+ "but due to limitations in ruby, RSpec cannot provide the block. Instead, " \
89
+ "use the `block_arg` method to access the block")
90
+ end
91
+ end
92
+ else
93
+ # :nocov:
94
+ def warn_about_block_args(*)
95
+ # There's no way to detect block params on 1.8 since the method reflection APIs don't expose it
96
+ end
97
+ # :nocov:
98
+ end
99
+
14
100
  RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure)
15
101
 
16
102
  # Contains the methods that are available from within the
@@ -35,28 +121,53 @@ module RSpec
35
121
  # expect(3).to be_even # fails
36
122
  # expect(4).not_to be_even # fails
37
123
  #
124
+ # By default the match block will swallow expectation errors (e.g.
125
+ # caused by using an expectation such as `expect(1).to eq 2`), if you
126
+ # wish to allow these to bubble up, pass in the option
127
+ # `:notify_expectation_failures => true`.
128
+ #
129
+ # @param [Hash] options for defining the behavior of the match block.
38
130
  # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
39
- def match(&match_block)
131
+ def match(options={}, &match_block)
40
132
  define_user_override(:matches?, match_block) do |actual|
41
- begin
42
- @actual = actual
43
- super(*actual_arg_for(match_block))
44
- rescue RSpec::Expectations::ExpectationNotMetError
45
- false
133
+ @actual = actual
134
+ RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
135
+ begin
136
+ super(*actual_arg_for(match_block))
137
+ rescue RSpec::Expectations::ExpectationNotMetError
138
+ raise if options[:notify_expectation_failures]
139
+ false
140
+ end
46
141
  end
47
142
  end
48
143
  end
49
144
 
145
+ # @private
146
+ RAISE_NOTIFIER = Proc.new { |err, _opts| raise err }
147
+
50
148
  # Use this to define the block for a negative expectation (`expect(...).not_to`)
51
149
  # when the positive and negative forms require different handling. This
52
150
  # is rarely necessary, but can be helpful, for example, when specifying
53
151
  # asynchronous processes that require different timeouts.
54
152
  #
153
+ # By default the match block will swallow expectation errors (e.g.
154
+ # caused by using an expectation such as `expect(1).to eq 2`), if you
155
+ # wish to allow these to bubble up, pass in the option
156
+ # `:notify_expectation_failures => true`.
157
+ #
158
+ # @param [Hash] options for defining the behavior of the match block.
55
159
  # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
56
- def match_when_negated(&match_block)
160
+ def match_when_negated(options={}, &match_block)
57
161
  define_user_override(:does_not_match?, match_block) do |actual|
58
- @actual = actual
59
- super(*actual_arg_for(match_block))
162
+ begin
163
+ @actual = actual
164
+ RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
165
+ super(*actual_arg_for(match_block))
166
+ end
167
+ rescue RSpec::Expectations::ExpectationNotMetError
168
+ raise if options[:notify_expectation_failures]
169
+ false
170
+ end
60
171
  end
61
172
  end
62
173
 
@@ -87,7 +198,7 @@ module RSpec
87
198
  end
88
199
  end
89
200
 
90
- # Customizes the failure messsage to use when this matcher is
201
+ # Customizes the failure message to use when this matcher is
91
202
  # asked to positively match. Only use this when the message
92
203
  # generated by default doesn't suit your needs.
93
204
  #
@@ -106,7 +217,7 @@ module RSpec
106
217
  define_user_override(__method__, definition)
107
218
  end
108
219
 
109
- # Customize the failure messsage to use when this matcher is asked
220
+ # Customize the failure message to use when this matcher is asked
110
221
  # to negatively match. Only use this when the message generated by
111
222
  # default doesn't suit your needs.
112
223
  #
@@ -160,7 +271,16 @@ module RSpec
160
271
  # Convenience for defining methods on this matcher to create a fluent
161
272
  # interface. The trick about fluent interfaces is that each method must
162
273
  # return self in order to chain methods together. `chain` handles that
163
- # for you.
274
+ # for you. If the method is invoked and the
275
+ # `include_chain_clauses_in_custom_matcher_descriptions` config option
276
+ # hash been enabled, the chained method name and args will be added to the
277
+ # default description and failure message.
278
+ #
279
+ # In the common case where you just want the chained method to store some
280
+ # value(s) for later use (e.g. in `match`), you can provide one or more
281
+ # attribute names instead of a block; the chained method will store its
282
+ # arguments in instance variables with those names, and the values will
283
+ # be exposed via getters.
164
284
  #
165
285
  # @example
166
286
  #
@@ -175,21 +295,43 @@ module RSpec
175
295
  # end
176
296
  #
177
297
  # expect(minor).to have_errors_on(:age).with("Not old enough to participate")
178
- def chain(name, &definition)
179
- define_user_override(name, definition) do |*args, &block|
298
+ def chain(method_name, *attr_names, &definition)
299
+ unless block_given? ^ attr_names.any?
300
+ raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`."
301
+ end
302
+
303
+ definition = assign_attributes(attr_names) if attr_names.any?
304
+
305
+ define_user_override(method_name, definition) do |*args, &block|
180
306
  super(*args, &block)
307
+ @chained_method_clauses.push([method_name, args])
181
308
  self
182
309
  end
183
310
  end
184
311
 
312
+ def assign_attributes(attr_names)
313
+ attr_reader(*attr_names)
314
+ private(*attr_names)
315
+
316
+ lambda do |*attr_values|
317
+ attr_names.zip(attr_values) do |attr_name, attr_value|
318
+ instance_variable_set(:"@#{attr_name}", attr_value)
319
+ end
320
+ end
321
+ end
322
+
323
+ # assign_attributes isn't defined in the private section below because
324
+ # that makes MRI 1.9.2 emit a warning about private attributes.
325
+ private :assign_attributes
326
+
185
327
  private
186
328
 
187
329
  # Does the following:
188
330
  #
189
- # - Defines the named method usign a user-provided block
331
+ # - Defines the named method using a user-provided block
190
332
  # in @user_method_defs, which is included as an ancestor
191
333
  # in the singleton class in which we eval the `define` block.
192
- # - Defines an overriden definition for the same method
334
+ # - Defines an overridden definition for the same method
193
335
  # usign the provided `our_def` block.
194
336
  # - Provides a default `our_def` block for the common case
195
337
  # of needing to call the user's definition with `@actual`
@@ -197,7 +339,7 @@ module RSpec
197
339
  #
198
340
  # This compiles the user block into an actual method, allowing
199
341
  # them to use normal method constructs like `return`
200
- # (e.g. for a early guard statement), while allowing us to define
342
+ # (e.g. for an early guard statement), while allowing us to define
201
343
  # an override that can provide the wrapped handling
202
344
  # (e.g. assigning `@actual`, rescueing errors, etc) and
203
345
  # can `super` to the user's definition.
@@ -241,6 +383,8 @@ module RSpec
241
383
  # override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods
242
384
  # from within an `RSpec::Matchers.define` block.
243
385
  module DefaultImplementations
386
+ include BuiltIn::BaseMatcher::DefaultFailureMessages
387
+
244
388
  # @api private
245
389
  # Used internally by objects returns by `should` and `should_not`.
246
390
  def diffable?
@@ -249,24 +393,37 @@ module RSpec
249
393
 
250
394
  # The default description.
251
395
  def description
252
- "#{name_to_sentence}#{to_sentence expected}"
396
+ english_name = EnglishPhrasing.split_words(name)
397
+ expected_list = EnglishPhrasing.list(expected)
398
+ "#{english_name}#{expected_list}#{chained_method_clause_sentences}"
253
399
  end
254
400
 
255
- # The default failure message for positive expectations.
256
- def failure_message
257
- "expected #{actual.inspect} to #{name_to_sentence}#{to_sentence expected}"
401
+ # Matchers do not support block expectations by default. You
402
+ # must opt-in.
403
+ def supports_block_expectations?
404
+ false
258
405
  end
259
406
 
260
- # The default failure message for negative expectations.
261
- def failure_message_when_negated
262
- "expected #{actual.inspect} not to #{name_to_sentence}#{to_sentence expected}"
407
+ def supports_value_expectations?
408
+ true
263
409
  end
264
410
 
265
- # Matchers do not support block expectations by default. You
266
- # must opt-in.
267
- def supports_block_expectations?
411
+ # Most matchers do not expect call stack jumps.
412
+ def expects_call_stack_jump?
268
413
  false
269
414
  end
415
+
416
+ private
417
+
418
+ def chained_method_clause_sentences
419
+ return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions?
420
+
421
+ @chained_method_clauses.map do |(method_name, method_args)|
422
+ english_name = EnglishPhrasing.split_words(method_name)
423
+ arg_list = EnglishPhrasing.list(method_args)
424
+ " #{english_name}#{arg_list}"
425
+ end.join
426
+ end
270
427
  end
271
428
 
272
429
  # The class used for custom matchers. The block passed to
@@ -280,9 +437,6 @@ module RSpec
280
437
  # Allows expectation expressions to be used in the match block.
281
438
  include RSpec::Matchers
282
439
 
283
- # Converts matcher name and expected args to an English expresion.
284
- include RSpec::Matchers::Pretty
285
-
286
440
  # Supports the matcher composability features of RSpec 3+.
287
441
  include Composable
288
442
 
@@ -298,18 +452,27 @@ module RSpec
298
452
  # Could be useful to extract details for a failure message.
299
453
  attr_reader :rescued_exception
300
454
 
455
+ # The block parameter used in the expectation
456
+ attr_reader :block_arg
457
+
458
+ # The name of the matcher.
459
+ attr_reader :name
460
+
301
461
  # @api private
302
- def initialize(name, declarations, matcher_execution_context, *expected)
462
+ def initialize(name, declarations, matcher_execution_context, *expected, &block_arg)
303
463
  @name = name
304
464
  @actual = nil
305
465
  @expected_as_array = expected
306
466
  @matcher_execution_context = matcher_execution_context
467
+ @chained_method_clauses = []
468
+ @block_arg = block_arg
307
469
 
308
- class << self
470
+ klass = class << self
309
471
  # See `Macros#define_user_override` above, for an explanation.
310
472
  include(@user_method_defs = Module.new)
311
473
  self
312
- end.class_exec(*expected, &declarations)
474
+ end
475
+ RSpec::Support::WithKeywordsWhenNeeded.class_exec(klass, *expected, &declarations)
313
476
  end
314
477
 
315
478
  # Provides the expected value. This will return an array if
@@ -345,11 +508,13 @@ module RSpec
345
508
  super || @matcher_execution_context.respond_to?(method, include_private)
346
509
  end
347
510
  else # for 1.8.7
511
+ # :nocov:
348
512
  # Indicates that this matcher responds to messages
349
513
  # from the `@matcher_execution_context` as well.
350
514
  def respond_to?(method, include_private=false)
351
515
  super || @matcher_execution_context.respond_to?(method, include_private)
352
516
  end
517
+ # :nocov:
353
518
  end
354
519
 
355
520
  private
@@ -371,9 +536,10 @@ module RSpec
371
536
  super(method, *args, &block)
372
537
  end
373
538
  end
539
+ # The method_missing method should be refactored to pass kw args in RSpec 4
540
+ # then this can be removed
541
+ ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true)
374
542
  end
375
543
  end
376
544
  end
377
545
  end
378
-
379
- RSpec::Matchers.extend RSpec::Matchers::DSL
@@ -0,0 +1,58 @@
1
+ module RSpec
2
+ module Matchers
3
+ # Facilitates converting ruby objects to English phrases.
4
+ module EnglishPhrasing
5
+ # Converts a symbol into an English expression.
6
+ #
7
+ # split_words(:banana_creme_pie) #=> "banana creme pie"
8
+ #
9
+ def self.split_words(sym)
10
+ sym.to_s.tr('_', ' ')
11
+ end
12
+
13
+ # @note The returned string has a leading space except
14
+ # when given an empty list.
15
+ #
16
+ # Converts an object (often a collection of objects)
17
+ # into an English list.
18
+ #
19
+ # list(['banana', 'kiwi', 'mango'])
20
+ # #=> " \"banana\", \"kiwi\", and \"mango\""
21
+ #
22
+ # Given an empty collection, returns the empty string.
23
+ #
24
+ # list([]) #=> ""
25
+ #
26
+ def self.list(obj)
27
+ return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj || Hash === obj
28
+ items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) }
29
+ case items.length
30
+ when 0
31
+ ""
32
+ when 1
33
+ " #{items[0]}"
34
+ when 2
35
+ " #{items[0]} and #{items[1]}"
36
+ else
37
+ " #{items[0...-1].join(', ')}, and #{items[-1]}"
38
+ end
39
+ end
40
+
41
+ if RUBY_VERSION == '1.8.7'
42
+ # Not sure why, but on travis on 1.8.7 we have gotten these warnings:
43
+ # lib/rspec/matchers/english_phrasing.rb:28: warning: default `to_a' will be obsolete
44
+ # So it appears that `Array` can trigger that (e.g. by calling `to_a` on the passed object?)
45
+ # So here we replace `Kernel#Array` with our own warning-free implementation for 1.8.7.
46
+ # @private
47
+ # rubocop:disable Naming/MethodName
48
+ def self.Array(obj)
49
+ case obj
50
+ when Array then obj
51
+ else [obj]
52
+ end
53
+ end
54
+ # rubocop:enable Naming/MethodName
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,82 @@
1
+ module RSpec
2
+ module Matchers
3
+ # @api private
4
+ # Handles list of expected values when there is a need to render
5
+ # multiple diffs. Also can handle one value.
6
+ class ExpectedsForMultipleDiffs
7
+ # @private
8
+ # Default diff label when there is only one matcher in diff
9
+ # output
10
+ DEFAULT_DIFF_LABEL = "Diff:".freeze
11
+
12
+ # @private
13
+ # Maximum readable matcher description length
14
+ DESCRIPTION_MAX_LENGTH = 65
15
+
16
+ def initialize(expected_list)
17
+ @expected_list = expected_list
18
+ end
19
+
20
+ # @api private
21
+ # Wraps provided expected value in instance of
22
+ # ExpectedForMultipleDiffs. If provided value is already an
23
+ # ExpectedForMultipleDiffs then it just returns it.
24
+ # @param [Any] expected value to be wrapped
25
+ # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
26
+ def self.from(expected)
27
+ return expected if self === expected
28
+ new([[expected, DEFAULT_DIFF_LABEL]])
29
+ end
30
+
31
+ # @api private
32
+ # Wraps provided matcher list in instance of
33
+ # ExpectedForMultipleDiffs.
34
+ # @param [Array<Any>] matchers list of matchers to wrap
35
+ # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
36
+ def self.for_many_matchers(matchers)
37
+ new(matchers.map { |m| [m.expected, diff_label_for(m)] })
38
+ end
39
+
40
+ # @api private
41
+ # Returns message with diff(s) appended for provided differ
42
+ # factory and actual value if there are any
43
+ # @param [String] message original failure message
44
+ # @param [Proc] differ
45
+ # @param [Any] actual value
46
+ # @return [String]
47
+ def message_with_diff(message, differ, actual)
48
+ diff = diffs(differ, actual)
49
+ message = "#{message}\n#{diff}" unless diff.empty?
50
+ message
51
+ end
52
+
53
+ private
54
+
55
+ class << self
56
+ private
57
+
58
+ def diff_label_for(matcher)
59
+ "Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):"
60
+ end
61
+
62
+ def truncated(description)
63
+ return description if description.length <= DESCRIPTION_MAX_LENGTH
64
+ description[0...DESCRIPTION_MAX_LENGTH - 3] << "..."
65
+ end
66
+ end
67
+
68
+ def diffs(differ, actual)
69
+ @expected_list.map do |(expected, diff_label)|
70
+ diff = differ.diff(actual, expected)
71
+ next if diff.strip.empty?
72
+ if diff == "\e[0m\n\e[0m"
73
+ "#{diff_label}\n" \
74
+ " <The diff is empty, are your objects producing identical `#inspect` output?>"
75
+ else
76
+ "#{diff_label}#{diff}"
77
+ end
78
+ end.compact.join("\n")
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ require 'rspec/expectations'
2
+
3
+ module RSpec
4
+ module Matchers
5
+ # Matchers for testing RSpec matchers. Include them with:
6
+ #
7
+ # require 'rspec/matchers/fail_matchers'
8
+ # RSpec.configure do |config|
9
+ # config.include RSpec::Matchers::FailMatchers
10
+ # end
11
+ #
12
+ module FailMatchers
13
+ # Matches if an expectation fails
14
+ #
15
+ # @example
16
+ # expect { some_expectation }.to fail
17
+ def fail(&block)
18
+ raise_error(RSpec::Expectations::ExpectationNotMetError, &block)
19
+ end
20
+
21
+ # Matches if an expectation fails with the provided message
22
+ #
23
+ # @example
24
+ # expect { some_expectation }.to fail_with("some failure message")
25
+ # expect { some_expectation }.to fail_with(/some failure message/)
26
+ def fail_with(message)
27
+ raise_error(RSpec::Expectations::ExpectationNotMetError, message)
28
+ end
29
+
30
+ # Matches if an expectation fails including the provided message
31
+ #
32
+ # @example
33
+ # expect { some_expectation }.to fail_including("portion of some failure message")
34
+ def fail_including(*snippets)
35
+ raise_error(
36
+ RSpec::Expectations::ExpectationNotMetError,
37
+ a_string_including(*snippets)
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -21,8 +21,7 @@ module RSpec
21
21
  "#{last_expectation_handler.verb} #{last_description}"
22
22
  end
23
23
 
24
- private
25
-
24
+ # @private
26
25
  def self.last_description
27
26
  last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE
28
27
  When you call a matcher in an example without a String, like this: