rspec-expectations 3.5.0 → 3.9.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 (37) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +138 -2
  5. data/README.md +37 -20
  6. data/lib/rspec/expectations.rb +2 -1
  7. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  8. data/lib/rspec/expectations/configuration.rb +14 -0
  9. data/lib/rspec/expectations/expectation_target.rb +2 -2
  10. data/lib/rspec/expectations/fail_with.rb +9 -1
  11. data/lib/rspec/expectations/handler.rb +2 -2
  12. data/lib/rspec/expectations/minitest_integration.rb +1 -1
  13. data/lib/rspec/expectations/syntax.rb +2 -2
  14. data/lib/rspec/expectations/version.rb +1 -1
  15. data/lib/rspec/matchers.rb +97 -97
  16. data/lib/rspec/matchers/built_in/all.rb +1 -0
  17. data/lib/rspec/matchers/built_in/base_matcher.rb +14 -2
  18. data/lib/rspec/matchers/built_in/be.rb +2 -2
  19. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  20. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  21. data/lib/rspec/matchers/built_in/change.rb +127 -53
  22. data/lib/rspec/matchers/built_in/compound.rb +6 -2
  23. data/lib/rspec/matchers/built_in/contain_exactly.rb +18 -2
  24. data/lib/rspec/matchers/built_in/exist.rb +5 -1
  25. data/lib/rspec/matchers/built_in/has.rb +1 -1
  26. data/lib/rspec/matchers/built_in/include.rb +6 -0
  27. data/lib/rspec/matchers/built_in/raise_error.rb +1 -1
  28. data/lib/rspec/matchers/built_in/respond_to.rb +13 -4
  29. data/lib/rspec/matchers/built_in/satisfy.rb +27 -4
  30. data/lib/rspec/matchers/built_in/yield.rb +43 -30
  31. data/lib/rspec/matchers/composable.rb +6 -20
  32. data/lib/rspec/matchers/dsl.rb +72 -4
  33. data/lib/rspec/matchers/english_phrasing.rb +3 -3
  34. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +16 -7
  35. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  36. metadata +22 -18
  37. metadata.gz.sig +0 -0
@@ -77,7 +77,11 @@ module RSpec
77
77
  end
78
78
 
79
79
  def predicates
80
- @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) }
80
+ @predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) && !deprecated(p, actual) }
81
+ end
82
+
83
+ def deprecated(predicate, actual)
84
+ predicate == :exists? && File == actual
81
85
  end
82
86
  end
83
87
  end
@@ -77,7 +77,7 @@ module RSpec
77
77
  end
78
78
 
79
79
  def method_description
80
- @method_name.to_s.gsub('_', ' ')
80
+ @method_name.to_s.tr('_', ' ')
81
81
  end
82
82
 
83
83
  def args_description
@@ -15,12 +15,14 @@ module RSpec
15
15
  # @api private
16
16
  # @return [Boolean]
17
17
  def matches?(actual)
18
+ actual = actual.to_hash if convert_to_hash?(actual)
18
19
  perform_match(actual) { |v| v }
19
20
  end
20
21
 
21
22
  # @api private
22
23
  # @return [Boolean]
23
24
  def does_not_match?(actual)
25
+ actual = actual.to_hash if convert_to_hash?(actual)
24
26
  perform_match(actual) { |v| !v }
25
27
  end
26
28
 
@@ -137,6 +139,10 @@ module RSpec
137
139
  actual.include?(str) && lines.none? { |line| line == str }
138
140
  end
139
141
  end
142
+
143
+ def convert_to_hash?(obj)
144
+ !obj.respond_to?(:include?) && obj.respond_to?(:to_hash)
145
+ end
140
146
  end
141
147
  end
142
148
  end
@@ -172,7 +172,7 @@ module RSpec
172
172
  "including those raised by Ruby (e.g. NoMethodError, NameError " \
173
173
  "and ArgumentError), meaning the code you are intending to test " \
174
174
  "may not even get reached. Instead consider using " \
175
- "`expect {}.not_to raise_error` or `expect { }.to raise_error" \
175
+ "`expect { }.not_to raise_error` or `expect { }.to raise_error" \
176
176
  "(DifferentSpecificErrorClass)`. This message can be suppressed by " \
177
177
  "setting: `RSpec::Expectations.configuration.on_potential_false" \
178
178
  "_positives = :nothing`")
@@ -125,13 +125,22 @@ module RSpec
125
125
 
126
126
  return true if expectation.empty?
127
127
 
128
- signature = Support::MethodSignature.new(Support.method_handle_for(actual, name))
128
+ Support::StrictSignatureVerifier.new(method_signature_for(actual, name)).
129
+ with_expectation(expectation).valid?
130
+ end
131
+
132
+ def method_signature_for(actual, name)
133
+ method_handle = Support.method_handle_for(actual, name)
129
134
 
130
- Support::StrictSignatureVerifier.new(signature).with_expectation(expectation).valid?
135
+ if name == :new && method_handle.owner === ::Class && ::Class === actual
136
+ Support::MethodSignature.new(actual.instance_method(:initialize))
137
+ else
138
+ Support::MethodSignature.new(method_handle)
139
+ end
131
140
  end
132
141
 
133
142
  def with_arity
134
- str = ''
143
+ str = ''.dup
135
144
  str << " with #{with_arity_string}" if @expected_arity
136
145
  str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0
137
146
  str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments
@@ -151,7 +160,7 @@ module RSpec
151
160
  @expected_keywords.map(&:inspect).join(' and ')
152
161
  else
153
162
  "#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}"
154
- end
163
+ end
155
164
 
156
165
  "keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}"
157
166
  end
@@ -5,10 +5,7 @@ module RSpec
5
5
  # Provides the implementation for `satisfy`.
6
6
  # Not intended to be instantiated directly.
7
7
  class Satisfy < BaseMatcher
8
- # @private
9
- attr_reader :description
10
-
11
- def initialize(description="satisfy block", &block)
8
+ def initialize(description=nil, &block)
12
9
  @description = description
13
10
  @block = block
14
11
  end
@@ -20,6 +17,11 @@ module RSpec
20
17
  @block.call(actual)
21
18
  end
22
19
 
20
+ # @private
21
+ def description
22
+ @description ||= "satisfy #{block_representation}"
23
+ end
24
+
23
25
  # @api private
24
26
  # @return [String]
25
27
  def failure_message
@@ -31,6 +33,27 @@ module RSpec
31
33
  def failure_message_when_negated
32
34
  "expected #{actual_formatted} not to #{description}"
33
35
  end
36
+
37
+ private
38
+
39
+ if RSpec::Support::RubyFeatures.ripper_supported?
40
+ def block_representation
41
+ if (block_snippet = extract_block_snippet)
42
+ "expression `#{block_snippet}`"
43
+ else
44
+ 'block'
45
+ end
46
+ end
47
+
48
+ def extract_block_snippet
49
+ return nil unless @block
50
+ Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
51
+ end
52
+ else
53
+ def block_representation
54
+ 'block'
55
+ end
56
+ end
34
57
  end
35
58
  end
36
59
  end
@@ -8,19 +8,17 @@ module RSpec
8
8
  # yield matchers is used. Provides information about
9
9
  # the yield behavior of the object-under-test.
10
10
  class YieldProbe
11
- def self.probe(block)
12
- probe = new(block)
11
+ def self.probe(block, &callback)
12
+ probe = new(block, &callback)
13
13
  return probe unless probe.has_block?
14
- probe.assert_valid_expect_block!
15
- block.call(probe)
16
- probe.assert_used!
17
- probe
14
+ probe.probe
18
15
  end
19
16
 
20
17
  attr_accessor :num_yields, :yielded_args
21
18
 
22
- def initialize(block)
19
+ def initialize(block, &callback)
23
20
  @block = block
21
+ @callback = callback || Proc.new {}
24
22
  @used = false
25
23
  self.num_yields = 0
26
24
  self.yielded_args = []
@@ -30,13 +28,22 @@ module RSpec
30
28
  Proc === @block
31
29
  end
32
30
 
31
+ def probe
32
+ assert_valid_expect_block!
33
+ @block.call(self)
34
+ assert_used!
35
+ self
36
+ end
37
+
33
38
  def to_proc
34
39
  @used = true
35
40
 
36
41
  probe = self
42
+ callback = @callback
37
43
  Proc.new do |*args|
38
44
  probe.num_yields += 1
39
45
  probe.yielded_args << args
46
+ callback.call(*args)
40
47
  nil # to indicate the block does not return a meaningful value
41
48
  end
42
49
  end
@@ -56,12 +63,6 @@ module RSpec
56
63
  end
57
64
  end
58
65
 
59
- def successive_yield_args
60
- yielded_args.map do |arg_array|
61
- arg_array.size == 1 ? arg_array.first : arg_array
62
- end
63
- end
64
-
65
66
  def assert_used!
66
67
  return if @used
67
68
  raise 'You must pass the argument yielded to your expect block on ' \
@@ -269,10 +270,15 @@ module RSpec
269
270
 
270
271
  # @private
271
272
  def matches?(block)
272
- @probe = YieldProbe.probe(block)
273
+ @args_matched_when_yielded = true
274
+ @probe = YieldProbe.new(block) do
275
+ @actual = @probe.single_yield_args
276
+ @actual_formatted = actual_formatted
277
+ @args_matched_when_yielded &&= args_currently_match?
278
+ end
273
279
  return false unless @probe.has_block?
274
- @actual = @probe.single_yield_args
275
- @probe.yielded_once?(:yield_with_args) && args_match?
280
+ @probe.probe
281
+ @probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded
276
282
  end
277
283
 
278
284
  # @private
@@ -293,7 +299,7 @@ module RSpec
293
299
  # @private
294
300
  def description
295
301
  desc = 'yield with args'
296
- desc << "(#{expected_arg_description})" unless @expected.empty?
302
+ desc = "#{desc}(#{expected_arg_description})" unless @expected.empty?
297
303
  desc
298
304
  end
299
305
 
@@ -317,16 +323,16 @@ module RSpec
317
323
  def negative_failure_reason
318
324
  if !@probe.has_block?
319
325
  'was not a block'
320
- elsif all_args_match?
326
+ elsif @args_matched_when_yielded && !@expected.empty?
321
327
  'yielded with expected arguments' \
322
328
  "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
323
- "\n got: #{actual_formatted}"
329
+ "\n got: #{@actual_formatted}"
324
330
  else
325
331
  'did'
326
332
  end
327
333
  end
328
334
 
329
- def args_match?
335
+ def args_currently_match?
330
336
  if @expected.empty? # expect {...}.to yield_with_args
331
337
  @positive_args_failure = 'yielded with no arguments' if @actual.empty?
332
338
  return !@actual.empty?
@@ -335,7 +341,7 @@ module RSpec
335
341
  unless (match = all_args_match?)
336
342
  @positive_args_failure = 'yielded with unexpected arguments' \
337
343
  "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
338
- "\n got: #{actual_formatted}"
344
+ "\n got: #{@actual_formatted}"
339
345
  end
340
346
 
341
347
  match
@@ -356,10 +362,21 @@ module RSpec
356
362
 
357
363
  # @private
358
364
  def matches?(block)
359
- @probe = YieldProbe.probe(block)
365
+ @actual_formatted = []
366
+ @actual = []
367
+ args_matched_when_yielded = true
368
+ yield_count = 0
369
+
370
+ @probe = YieldProbe.probe(block) do |*arg_array|
371
+ arg_or_args = arg_array.size == 1 ? arg_array.first : arg_array
372
+ @actual_formatted << RSpec::Support::ObjectFormatter.format(arg_or_args)
373
+ @actual << arg_or_args
374
+ args_matched_when_yielded &&= values_match?(@expected[yield_count], arg_or_args)
375
+ yield_count += 1
376
+ end
377
+
360
378
  return false unless @probe.has_block?
361
- @actual = @probe.successive_yield_args
362
- args_match?
379
+ args_matched_when_yielded && yield_count == @expected.length
363
380
  end
364
381
 
365
382
  def does_not_match?(block)
@@ -390,10 +407,6 @@ module RSpec
390
407
 
391
408
  private
392
409
 
393
- def args_match?
394
- values_match?(@expected, @actual)
395
- end
396
-
397
410
  def expected_arg_description
398
411
  @expected.map { |e| description_of e }.join(', ')
399
412
  end
@@ -403,7 +416,7 @@ module RSpec
403
416
 
404
417
  'yielded with unexpected arguments' \
405
418
  "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
406
- "\n got: #{actual_formatted}"
419
+ "\n got: [#{@actual_formatted.join(", ")}]"
407
420
  end
408
421
 
409
422
  def negative_failure_reason
@@ -411,7 +424,7 @@ module RSpec
411
424
 
412
425
  'yielded with expected arguments' \
413
426
  "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
414
- "\n got: #{actual_formatted}"
427
+ "\n got: [#{@actual_formatted.join(", ")}]"
415
428
  end
416
429
  end
417
430
  end
@@ -130,8 +130,6 @@ module RSpec
130
130
  object.clone
131
131
  elsif Hash === object
132
132
  Hash[with_matchers_cloned(object.to_a)]
133
- elsif Struct === object || unreadable_io?(object)
134
- object
135
133
  elsif should_enumerate?(object)
136
134
  object.map { |subobject| with_matchers_cloned(subobject) }
137
135
  else
@@ -139,24 +137,10 @@ module RSpec
139
137
  end
140
138
  end
141
139
 
142
- if String.ancestors.include?(Enumerable) # 1.8.7
143
- # :nocov:
144
- # Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
145
- # nested enumerable: since ruby lacks a character class, it yields
146
- # 1-character strings, which are themselves enumerable, composed of a
147
- # a single 1-character string, which is an enumerable, etc.
148
- #
149
- # @api private
150
- def should_enumerate?(item)
151
- return false if String === item
152
- Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
153
- end
154
- # :nocov:
155
- else
156
- # @api private
157
- def should_enumerate?(item)
158
- Enumerable === item && !(Range === item) && item.none? { |subitem| subitem.equal?(item) }
159
- 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) }
160
144
  end
161
145
 
162
146
  # @api private
@@ -172,10 +156,12 @@ module RSpec
172
156
  # Wraps an item in order to surface its `description` via `inspect`.
173
157
  # @api private
174
158
  DescribableItem = Struct.new(:item) do
159
+ # Inspectable version of the item description
175
160
  def inspect
176
161
  "(#{item.description})"
177
162
  end
178
163
 
164
+ # A pretty printed version of the item description.
179
165
  def pretty_print(pp)
180
166
  pp.text "(#{item.description})"
181
167
  end
@@ -2,7 +2,70 @@ module RSpec
2
2
  module Matchers
3
3
  # Defines the custom matcher DSL.
4
4
  module DSL
5
+ # Defines a matcher alias. The returned matcher's `description` will be overriden
6
+ # to reflect the phrasing of the new name, which will be used in failure messages
7
+ # when passed as an argument to another matcher in a composed matcher expression.
8
+ #
9
+ # @example
10
+ # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
11
+ # sum_to(3).description # => "sum to 3"
12
+ # a_list_that_sums_to(3).description # => "a list that sums to 3"
13
+ #
14
+ # @example
15
+ # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
16
+ # description.sub("be sorted by", "a list sorted by")
17
+ # end
18
+ #
19
+ # be_sorted_by(:age).description # => "be sorted by age"
20
+ # a_list_sorted_by(:age).description # => "a list sorted by age"
21
+ #
22
+ # @param new_name [Symbol] the new name for the matcher
23
+ # @param old_name [Symbol] the original name for the matcher
24
+ # @param options [Hash] options for the aliased matcher
25
+ # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
26
+ # @yield [String] optional block that, when given, is used to define the overriden
27
+ # logic. The yielded arg is the original description or failure message. If no
28
+ # block is provided, a default override is used based on the old and new names.
29
+ # @see RSpec::Matchers
30
+ def alias_matcher(new_name, old_name, options={}, &description_override)
31
+ description_override ||= lambda do |old_desc|
32
+ old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
33
+ end
34
+ klass = options.fetch(:klass) { AliasedMatcher }
35
+
36
+ define_method(new_name) do |*args, &block|
37
+ matcher = __send__(old_name, *args, &block)
38
+ matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=)
39
+ klass.new(matcher, description_override)
40
+ end
41
+ end
42
+
43
+ # Defines a negated matcher. The returned matcher's `description` and `failure_message`
44
+ # will be overriden to reflect the phrasing of the new name, and the match logic will
45
+ # be based on the original matcher but negated.
46
+ #
47
+ # @example
48
+ # RSpec::Matchers.define_negated_matcher :exclude, :include
49
+ # include(1, 2).description # => "include 1 and 2"
50
+ # exclude(1, 2).description # => "exclude 1 and 2"
51
+ #
52
+ # @param negated_name [Symbol] the name for the negated matcher
53
+ # @param base_name [Symbol] the name of the original matcher that will be negated
54
+ # @yield [String] optional block that, when given, is used to define the overriden
55
+ # logic. The yielded arg is the original description or failure message. If no
56
+ # block is provided, a default override is used based on the old and new names.
57
+ # @see RSpec::Matchers
58
+ def define_negated_matcher(negated_name, base_name, &description_override)
59
+ alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
60
+ end
61
+
5
62
  # Defines a custom matcher.
63
+ #
64
+ # @param name [Symbol] the name for the matcher
65
+ # @yield [Object] block that is used to define the matcher.
66
+ # The block is evaluated in the context of your custom matcher class.
67
+ # When args are passed to your matcher, they will be yielded here,
68
+ # usually representing the expected value(s).
6
69
  # @see RSpec::Matchers
7
70
  def define(name, &declarations)
8
71
  warn_about_block_args(name, declarations)
@@ -84,8 +147,14 @@ module RSpec
84
147
  # is rarely necessary, but can be helpful, for example, when specifying
85
148
  # asynchronous processes that require different timeouts.
86
149
  #
150
+ # By default the match block will swallow expectation errors (e.g.
151
+ # caused by using an expectation such as `expect(1).to eq 2`), if you
152
+ # with to allow these to bubble up, pass in the option
153
+ # `:notify_expectation_failures => true`.
154
+ #
155
+ # @param [Hash] options for defining the behavior of the match block.
87
156
  # @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
88
- def match_when_negated(&match_block)
157
+ def match_when_negated(options={}, &match_block)
89
158
  define_user_override(:does_not_match?, match_block) do |actual|
90
159
  begin
91
160
  @actual = actual
@@ -93,6 +162,7 @@ module RSpec
93
162
  super(*actual_arg_for(match_block))
94
163
  end
95
164
  rescue RSpec::Expectations::ExpectationNotMetError
165
+ raise if options[:notify_expectation_failures]
96
166
  false
97
167
  end
98
168
  end
@@ -266,7 +336,7 @@ module RSpec
266
336
  #
267
337
  # This compiles the user block into an actual method, allowing
268
338
  # them to use normal method constructs like `return`
269
- # (e.g. for a early guard statement), while allowing us to define
339
+ # (e.g. for an early guard statement), while allowing us to define
270
340
  # an override that can provide the wrapped handling
271
341
  # (e.g. assigning `@actual`, rescueing errors, etc) and
272
342
  # can `super` to the user's definition.
@@ -462,5 +532,3 @@ module RSpec
462
532
  end
463
533
  end
464
534
  end
465
-
466
- RSpec::Matchers.extend RSpec::Matchers::DSL