lib/rspec/matchers/built_in/yield.rb in rspec-expectations-3.0.0.beta2 vs lib/rspec/matchers/built_in/yield.rb in rspec-expectations-3.0.0.rc1
- old
+ new
@@ -1,24 +1,34 @@
module RSpec
module Matchers
module BuiltIn
+ # @private
+ # Object that is yielded to `expect` when one of the
+ # yield matchers is used. Provides information about
+ # the yield behavior of the object-under-test.
class YieldProbe
def self.probe(block)
- probe = new
- assert_valid_expect_block!(block)
+ probe = new(block)
+ return probe unless probe.has_block?
+ probe.assert_valid_expect_block!
block.call(probe)
probe.assert_used!
probe
end
attr_accessor :num_yields, :yielded_args
- def initialize
+ def initialize(block)
+ @block = block
@used = false
self.num_yields, self.yielded_args = 0, []
end
+ def has_block?
+ Proc === @block
+ end
+
def to_proc
@used = true
probe = self
Proc.new do |*args|
@@ -56,86 +66,114 @@
"allows the matcher to detect whether or not the method-under-test " +
"yields, and, if so, how many times, and what the yielded arguments " +
"are."
end
- def self.assert_valid_expect_block!(block)
- return if block.arity == 1
+ def assert_valid_expect_block!
+ return if @block.arity == 1
raise "Your expect block must accept an argument to be used with this " +
"matcher. Pass the argument as a block on to the method you are testing."
end
end
+ # @api private
+ # Provides the implementation for `yield_control`.
+ # Not intended to be instantiated directly.
class YieldControl < BaseMatcher
def initialize
@expectation_type = nil
@expected_yields_count = nil
end
- def matches?(block)
- probe = YieldProbe.probe(block)
-
- if @expectation_type
- probe.num_yields.send(@expectation_type, @expected_yields_count)
- else
- probe.yielded_once?(:yield_control)
- end
- end
-
+ # @api public
+ # Specifies that the method is expected to yield once.
def once
exactly(1)
self
end
+ # @api public
+ # Specifies that the method is expected to yield once.
def twice
exactly(2)
self
end
+ # @api public
+ # Specifies that the method is expected to yield the given number of times.
def exactly(number)
set_expected_yields_count(:==, number)
self
end
+ # @api public
+ # Specifies the maximum number of times the method is expected to yield
def at_most(number)
set_expected_yields_count(:<=, number)
self
end
+ # @api public
+ # Specifies the minimum number of times the method is expected to yield
def at_least(number)
set_expected_yields_count(:>=, number)
self
end
+ # @api public
+ # No-op. Provides syntactic sugar.
def times
self
end
- def failure_message
- 'expected given block to yield control'.tap do |failure_message|
- failure_message << relativity_failure_message
+ # @private
+ def matches?(block)
+ @probe = YieldProbe.probe(block)
+ return false unless @probe.has_block?
+
+ if @expectation_type
+ @probe.num_yields.__send__(@expectation_type, @expected_yields_count)
+ else
+ @probe.yielded_once?(:yield_control)
end
end
+ # @private
+ def does_not_match?(block)
+ !matches?(block) && @probe.has_block?
+ end
+
+ # @api private
+ # @return [String]
+ def failure_message
+ 'expected given block to yield control' + failure_reason
+ end
+
+ # @api private
+ # @return [String]
def failure_message_when_negated
- 'expected given block not to yield control'.tap do |failure_message|
- failure_message << relativity_failure_message
- end
+ 'expected given block not to yield control' + failure_reason
end
- private
+ # @private
+ def supports_block_expectations?
+ true
+ end
+ private
+
def set_expected_yields_count(relativity, n)
@expectation_type = relativity
@expected_yields_count = case n
when Numeric then n
when :once then 1
when :twice then 2
end
end
- def relativity_failure_message
+ def failure_reason
+ return " but was not a block" unless @probe.has_block?
return '' unless @expected_yields_count
" #{human_readable_expecation_type}#{human_readable_count}"
end
def human_readable_expecation_type
@@ -153,79 +191,116 @@
else "#{@expected_yields_count} times"
end
end
end
+ # @api private
+ # Provides the implementation for `yield_with_no_args`.
+ # Not intended to be instantiated directly.
class YieldWithNoArgs < BaseMatcher
-
+ # @private
def matches?(block)
@probe = YieldProbe.probe(block)
+ return false unless @probe.has_block?
@probe.yielded_once?(:yield_with_no_args) && @probe.single_yield_args.empty?
end
+ # @private
+ def does_not_match?(block)
+ !matches?(block) && @probe.has_block?
+ end
+
+ # @private
def failure_message
- "expected given block to yield with no arguments, but #{failure_reason}"
+ "expected given block to yield with no arguments, but #{positive_failure_reason}"
end
+ # @private
def failure_message_when_negated
- "expected given block not to yield with no arguments, but did"
+ "expected given block not to yield with no arguments, but #{negative_failure_reason}"
end
+ # @private
+ def supports_block_expectations?
+ true
+ end
+
private
- def failure_reason
- if @probe.num_yields.zero?
- "did not yield"
- else
- "yielded with arguments: #{@probe.single_yield_args.inspect}"
- end
+ def positive_failure_reason
+ return "was not a block" unless @probe.has_block?
+ return "did not yield" if @probe.num_yields.zero?
+ "yielded with arguments: #{@probe.single_yield_args.inspect}"
end
+
+ def negative_failure_reason
+ return "was not a block" unless @probe.has_block?
+ "did"
+ end
end
+ # @api private
+ # Provides the implementation for `yield_with_args`.
+ # Not intended to be instantiated directly.
class YieldWithArgs
include Composable
def initialize(*args)
@expected = args
end
+ # @private
def matches?(block)
@probe = YieldProbe.probe(block)
+ return false unless @probe.has_block?
@actual = @probe.single_yield_args
@probe.yielded_once?(:yield_with_args) && args_match?
end
+ # @private
+ def does_not_match?(block)
+ !matches?(block) && @probe.has_block?
+ end
+
+ # @private
def failure_message
"expected given block to yield with arguments, but #{positive_failure_reason}"
end
+ # @private
def failure_message_when_negated
"expected given block not to yield with arguments, but #{negative_failure_reason}"
end
+ # @private
def description
desc = "yield with args"
desc << "(#{expected_arg_description})" unless @expected.empty?
desc
end
+ # @private
+ def supports_block_expectations?
+ true
+ end
+
private
def positive_failure_reason
- if @probe.num_yields.zero?
- "did not yield"
- else
- @positive_args_failure
- end
+ return "was not a block" unless @probe.has_block?
+ return "did not yield" if @probe.num_yields.zero?
+ @positive_args_failure
end
def expected_arg_description
@expected.map { |e| description_of e }.join(", ")
end
def negative_failure_reason
- if all_args_match?
+ if !@probe.has_block?
+ "was not a block"
+ elsif all_args_match?
"yielded with expected arguments" +
"\nexpected not: #{surface_descriptions_in(@expected).inspect}" +
"\n got: #{@actual.inspect}"
else
"did"
@@ -250,48 +325,77 @@
def all_args_match?
values_match?(@expected, @actual)
end
end
+ # @api private
+ # Provides the implementation for `yield_successive_args`.
+ # Not intended to be instantiated directly.
class YieldSuccessiveArgs
include Composable
def initialize(*args)
@expected = args
end
+ # @private
def matches?(block)
@probe = YieldProbe.probe(block)
+ return false unless @probe.has_block?
@actual = @probe.successive_yield_args
args_match?
end
+ def does_not_match?(block)
+ !matches?(block) && @probe.has_block?
+ end
+
+ # @private
def failure_message
- "expected given block to yield successively with arguments, but yielded with unexpected arguments" +
- "\nexpected: #{surface_descriptions_in(@expected).inspect}" +
- "\n got: #{@actual.inspect}"
+ "expected given block to yield successively with arguments, but #{positive_failure_reason}"
end
+ # @private
def failure_message_when_negated
- "expected given block not to yield successively with arguments, but yielded with expected arguments" +
- "\nexpected not: #{surface_descriptions_in(@expected).inspect}" +
- "\n got: #{@actual.inspect}"
+ "expected given block not to yield successively with arguments, but #{negative_failure_reason}"
end
+ # @private
def description
desc = "yield successive args"
desc << "(#{expected_arg_description})"
desc
end
+ # @private
+ def supports_block_expectations?
+ true
+ end
+
private
def args_match?
values_match?(@expected, @actual)
end
def expected_arg_description
@expected.map { |e| description_of e }.join(", ")
+ end
+
+ def positive_failure_reason
+ return "was not a block" unless @probe.has_block?
+
+ "yielded with unexpected arguments" +
+ "\nexpected: #{surface_descriptions_in(@expected).inspect}" +
+ "\n got: #{@actual.inspect}"
+ end
+
+ def negative_failure_reason
+ return "was not a block" unless @probe.has_block?
+
+ "yielded with expected arguments" +
+ "\nexpected not: #{surface_descriptions_in(@expected).inspect}" +
+ "\n got: #{@actual.inspect}"
end
end
end
end
end