Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/rspec/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ def self.configuration

private

BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
BE_PREDICATE_REGEX = /^(?:be_(?:an?_)?)(.*)/
HAS_REGEX = /^(?:have_)(.*)/
DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX)

Expand Down
3 changes: 2 additions & 1 deletion lib/rspec/matchers/built_in.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ module BuiltIn
autoload :Be, 'rspec/matchers/built_in/be'
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
autoload :BeFalsey, 'rspec/matchers/built_in/be'
autoload :BeHelpers, 'rspec/matchers/built_in/be'
autoload :BeNil, 'rspec/matchers/built_in/be'
autoload :BePredicate, 'rspec/matchers/built_in/be'
autoload :BePredicate, 'rspec/matchers/built_in/has'
autoload :BeTruthy, 'rspec/matchers/built_in/be'
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
autoload :Change, 'rspec/matchers/built_in/change'
Expand Down
133 changes: 0 additions & 133 deletions lib/rspec/matchers/built_in/be.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,139 +186,6 @@ def perform_match(actual)
@actual.__send__ @operator, @expected
end
end

# @api private
# Provides the implementation of `be_<predicate>`.
# Not intended to be instantiated directly.
class BePredicate < BaseMatcher
include BeHelpers

if RSpec::Support::RubyFeatures.kw_args_supported?
binding.eval(<<-CODE, __FILE__, __LINE__)
def initialize(*args, **kwargs, &block)
@expected = parse_expected(args.shift)
@args = args
@kwargs = kwargs
@block = block
end
CODE
else
def initialize(*args, &block)
@expected = parse_expected(args.shift)
@args = args
@block = block
end
end

def matches?(actual, &block)
@actual = actual
@block ||= block
predicate_accessible? && predicate_matches?
end

def does_not_match?(actual, &block)
@actual = actual
@block ||= block
predicate_accessible? && !predicate_matches?
end

# @api private
# @return [String]
def failure_message
failure_message_expecting(true)
end

# @api private
# @return [String]
def failure_message_when_negated
failure_message_expecting(false)
end

# @api private
# @return [String]
def description
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
end

private

def predicate_accessible?
actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
end

# support 1.8.7, evaluate once at load time for performance
if String === methods.first
# :nocov:
def private_predicate?
@actual.private_methods.include? predicate.to_s
end
# :nocov:
else
def private_predicate?
@actual.private_methods.include? predicate
end
end

if RSpec::Support::RubyFeatures.kw_args_supported?
binding.eval(<<-CODE, __FILE__, __LINE__)
def predicate_matches?
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
if @kwargs.empty?
@predicate_matches = actual.__send__(method_name, *@args, &@block)
else
@predicate_matches = actual.__send__(method_name, *@args, **@kwargs, &@block)
end
end
CODE
else
def predicate_matches?
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
@predicate_matches = actual.__send__(method_name, *@args, &@block)
end
end

def predicate
:"#{@expected}?"
end

def present_tense_predicate
:"#{@expected}s?"
end

def parse_expected(expected)
@prefix, expected = prefix_and_expected(expected)
expected
end

def prefix_and_expected(symbol)
Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
end

def prefix_to_sentence
EnglishPhrasing.split_words(@prefix)
end

def failure_message_expecting(value)
validity_message ||
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
end

def validity_message
return nil if predicate_accessible?

msg = "expected #{actual_formatted} to respond to `#{predicate}`".dup

if private_predicate?
msg << " but `#{predicate}` is a private method"
elsif predicate == :true?
msg << " or perhaps you meant `be true` or `be_truthy`"
elsif predicate == :false?
msg << " or perhaps you meant `be false` or `be_falsey`"
end

msg
end
end
end
end
end
129 changes: 104 additions & 25 deletions lib/rspec/matchers/built_in/has.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ module RSpec
module Matchers
module BuiltIn
# @api private
# Provides the implementation for `has_<predicate>`.
# Not intended to be instantiated directly.
class Has < BaseMatcher
# Provides the implementation for dynamic predicate matchers.
# Not intended to be inherited directly.
class DynamicPredicate < BaseMatcher
include BeHelpers

if RSpec::Support::RubyFeatures.kw_args_supported?
binding.eval(<<-CODE, __FILE__, __LINE__)
def initialize(method_name, *args, **kwargs, &block)
Expand Down Expand Up @@ -34,25 +36,25 @@ def does_not_match?(actual, &block)
# @api private
# @return [String]
def failure_message
validity_message || "expected ##{predicate}#{failure_message_args_description} to return true, got false"
failure_message_expecting(true)
end

# @api private
# @return [String]
def failure_message_when_negated
validity_message || "expected ##{predicate}#{failure_message_args_description} to return false, got true"
failure_message_expecting(false)
end

# @api private
# @return [String]
def description
[method_description, args_description].compact.join(' ')
"#{method_description}#{args_to_sentence}"
end

private

def predicate_accessible?
!private_predicate? && predicate_exists?
@actual.respond_to? predicate
end

# support 1.8.7, evaluate once at load time for performance
Expand All @@ -68,38 +70,87 @@ def private_predicate?
end
end

def predicate_exists?
@actual.respond_to? predicate
end

if RSpec::Support::RubyFeatures.kw_args_supported?
binding.eval(<<-CODE, __FILE__, __LINE__)
def predicate_matches?
def predicate_result
if @kwargs.empty?
@actual.__send__(predicate, *@args, &@block)
@predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
else
@actual.__send__(predicate, *@args, **@kwargs, &@block)
@predicate_result = actual.__send__(predicate_method_name, *@args, **@kwargs, &@block)
end
end
CODE
else
def predicate_matches?
@actual.__send__(predicate, *@args, &@block)
def predicate_result
@predicate_result = actual.__send__(predicate_method_name, *@args, &@block)
end
end

def predicate
def predicate_method_name
predicate
end
Comment on lines +89 to +91
Copy link
Member

@JonRowe JonRowe Aug 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this should be in the Has class as this implementation is only used there? (I've checked the other PR so I think thats the case?)

Copy link
Member

@JonRowe JonRowe Aug 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah actually, I see, its not used elsewhere in has, ignore this comment. Commented on the wrong comment.


def predicate_matches?
!!predicate_result
end

def root
# On 1.9, there appears to be a bug where String#match can return `false`
# rather than the match data object. Changing to Regex#match appears to
# work around this bug. For an example of this bug, see:
# https://travis-ci.org/rspec/rspec-expectations/jobs/27549635
@predicate ||= :"has_#{Matchers::HAS_REGEX.match(@method_name.to_s).captures.first}?"
self.class::REGEX.match(@method_name.to_s).captures.first
end

def method_description
@method_name.to_s.tr('_', ' ')
EnglishPhrasing.split_words(@method_name)
end

def failure_message_expecting(value)
validity_message ||
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_result}"
end

def validity_message
return nil if predicate_accessible?

"expected #{actual_formatted} to respond to `#{predicate}`#{failure_to_respond_explanation}"
end

def failure_to_respond_explanation
if private_predicate?
" but `#{predicate}` is a private method"
end
end
end

# @api private
# Provides the implementation for `has_<predicate>`.
# Not intended to be instantiated directly.
class Has < DynamicPredicate
# :nodoc:
REGEX = Matchers::HAS_REGEX

# @api private
# @return [String]
def failure_message
validity_message || "expected ##{predicate}#{failure_message_args_description} to return true, got false"
end

# @api private
# @return [String]
def failure_message_when_negated
validity_message || "expected ##{predicate}#{failure_message_args_description} to return false, got true"
end

# @api private
# @return [String]
def description
[method_description, args_description].compact.join(' ')
end

private

def args_description
return nil if @args.empty?
@args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(', ')
Expand All @@ -110,12 +161,40 @@ def failure_message_args_description
"(#{desc})" if desc
end

def validity_message
if private_predicate?
"expected #{@actual} to respond to `#{predicate}` but `#{predicate}` is a private method"
elsif !predicate_exists?
"expected #{@actual} to respond to `#{predicate}`"
end
def predicate
@predicate ||= :"has_#{root}?"
end
end

# @api private
# Provides the implementation of `be_<predicate>`.
# Not intended to be instantiated directly.
class BePredicate < DynamicPredicate
# :nodoc:
REGEX = Matchers::BE_PREDICATE_REGEX
private
def predicate
@predicate ||= :"#{root}?"
end

def predicate_method_name
actual.respond_to?(predicate) ? predicate : present_tense_predicate
end

def failure_to_respond_explanation
super || if predicate == :true?
" or perhaps you meant `be true` or `be_truthy`"
elsif predicate == :false?
" or perhaps you meant `be false` or `be_falsey`"
end
end

def predicate_accessible?
super || actual.respond_to?(present_tense_predicate)
end

def present_tense_predicate
:"#{root}s?"
end
end
end
Expand Down