lib/rspec/expectations/handler.rb
module RSpec
module Expectations
# @private
module ExpectationHelper
def self.check_message(msg)
unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call)
::Kernel.warn [
"WARNING: ignoring the provided expectation message argument (",
msg.inspect,
") since it is not a string or a proc."
].join
end
end
# Returns an RSpec-3+ compatible matcher, wrapping a legacy one
# in an adapter if necessary.
#
# @private
def self.modern_matcher_from(matcher)
LegacyMatcherAdapter::RSpec2.wrap(matcher) ||
LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher
end
def self.with_matcher(handler, matcher, message)
check_message(message)
matcher = modern_matcher_from(matcher)
yield matcher
ensure
::RSpec::Matchers.last_expectation_handler = handler
::RSpec::Matchers.last_matcher = matcher
end
def self.handle_failure(matcher, message, failure_message_method)
message = message.call if message.respond_to?(:call)
message ||= matcher.__send__(failure_message_method)
if matcher.respond_to?(:diffable?) && matcher.diffable?
::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
else
::RSpec::Expectations.fail_with message
end
end
end
# @private
class PositiveExpectationHandler
def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block)
ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher|
return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher
match_result = matcher.matches?(actual, &block)
if custom_message && match_result.respond_to?(:error_generator)
match_result.error_generator.opts[:message] = custom_message
end
match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message)
end
end
def self.verb
'is expected to'
end
def self.should_method
:should
end
def self.opposite_should_method
:should_not
end
end
# @private
class NegativeExpectationHandler
def self.handle_matcher(actual, initial_matcher, custom_message=nil, &block)
ExpectationHelper.with_matcher(self, initial_matcher, custom_message) do |matcher|
return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher
negated_match_result = does_not_match?(matcher, actual, &block)
if custom_message && negated_match_result.respond_to?(:error_generator)
negated_match_result.error_generator.opts[:message] = custom_message
end
negated_match_result || ExpectationHelper.handle_failure(matcher, custom_message, :failure_message_when_negated)
end
end
def self.does_not_match?(matcher, actual, &block)
if matcher.respond_to?(:does_not_match?)
matcher.does_not_match?(actual, &block)
else
!matcher.matches?(actual, &block)
end
end
def self.verb
'is expected not to'
end
def self.should_method
:should_not
end
def self.opposite_should_method
:should
end
end
# Wraps a matcher written against one of the legacy protocols in
# order to present the current protocol.
#
# @private
class LegacyMatcherAdapter < Matchers::MatcherDelegator
def initialize(matcher)
super
::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher")
|#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher
|protocol. For the current protocol you should expose the failure messages
|via the `failure_message` and `failure_message_when_negated` methods.
|(Used from #{CallerFilter.first_non_rspec_line})
EOS
end
def self.wrap(matcher)
new(matcher) if interface_matches?(matcher)
end
# Starting in RSpec 1.2 (and continuing through all 2.x releases),
# the failure message protocol was:
# * `failure_message_for_should`
# * `failure_message_for_should_not`
# @private
class RSpec2 < self
def failure_message
base_matcher.failure_message_for_should
end
def failure_message_when_negated
base_matcher.failure_message_for_should_not
end
def self.interface_matches?(matcher)
(
!matcher.respond_to?(:failure_message) &&
matcher.respond_to?(:failure_message_for_should)
) || (
!matcher.respond_to?(:failure_message_when_negated) &&
matcher.respond_to?(:failure_message_for_should_not)
)
end
end
# Before RSpec 1.2, the failure message protocol was:
# * `failure_message`
# * `negative_failure_message`
# @private
class RSpec1 < self
def failure_message
base_matcher.failure_message
end
def failure_message_when_negated
base_matcher.negative_failure_message
end
# Note: `failure_message` is part of the RSpec 3 protocol
# (paired with `failure_message_when_negated`), so we don't check
# for `failure_message` here.
def self.interface_matches?(matcher)
!matcher.respond_to?(:failure_message_when_negated) &&
matcher.respond_to?(:negative_failure_message)
end
end
end
# RSpec 3.0 was released with the class name misspelled. For SemVer compatibility,
# we will provide this misspelled alias until 4.0.
# @deprecated Use LegacyMatcherAdapter instead.
# @private
LegacyMacherAdapter = LegacyMatcherAdapter
end
end