Implement predicate matcher

Took a trick I learned from `have_attributes` and applied it here.
This commit is contained in:
Michael Miller 2019-02-04 22:52:09 -07:00
parent 5da222d7a0
commit 89208b8ed1
2 changed files with 54 additions and 0 deletions

View file

@ -408,5 +408,25 @@ module Spectator::DSL
macro have_attributes(**expected)
::Spectator::Matchers::AttributesMatcher.new({{expected.double_splat.stringify}}, {{expected}})
end
# Used to create predicate matchers.
# Any missing method that starts with `be_` will be handled.
# All other method names will be ignored and raise a compile-time error.
#
# This can be used to simply check a predicate method that ends in `?`.
# For instance:
# ```
# expect("foobar").to be_ascii_only
# # Is equivalent to:
# expect("foobar".ascii_only?).to be_true
# ```
macro method_missing(call)
{% if call.name.starts_with?("be_") %}
{% method_name = call.name[3..-1] %} # Remove `be_` prefix.
::Spectator::Matchers::PredicateMatcher.new({{method_name.stringify}}, { {{method_name}}: nil })
{% else %}
{% raise "Undefined local variable or method '#{call}'" %}
{% end %}
end
end
end

View file

@ -0,0 +1,34 @@
require "./value_matcher"
module Spectator::Matchers
# Matcher that tests one or more predicates (methods ending in `?`).
# The `ExpectedType` type param should be a `NamedTuple`.
# Each key in the tuple is a predicate (without the `?`) to test.
struct PredicateMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it.
# True is returned if the match was successful, false otherwise.
def match?(partial)
actual = partial.actual
# Test each predicate and immediately return false if one is false.
{% for attribute in ExpectedType.keys %}
return false unless actual.{{attribute}}?
{% end %}
# All checks passed if this point is reached.
true
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message(partial)
"Expected #{partial.label} to be #{label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message(partial)
"Expected #{partial.label} to not be #{label}"
end
end
end