mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
99 lines
3.6 KiB
Crystal
99 lines
3.6 KiB
Crystal
require "./value_matcher"
|
|
|
|
module Spectator::Matchers
|
|
# Matcher that tests one or more "has" predicates
|
|
# (methods ending in '?' and starting with 'has_').
|
|
# The `ExpectedType` type param should be a `NamedTuple`.
|
|
# Each key in the tuple is a predicate (without the '?' and 'has_' prefix) to test.
|
|
# Each value is a a `Tuple` of arguments to pass to the predicate method.
|
|
struct HavePredicateMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
|
# Short text about the matcher's purpose.
|
|
# This explains what condition satisfies the matcher.
|
|
# The description is used when the one-liner syntax is used.
|
|
def description : String
|
|
"has #{expected.label}"
|
|
end
|
|
|
|
# Actually performs the test against the expression.
|
|
def match(actual : Expression(T)) : MatchData forall T
|
|
snapshot = snapshot_values(actual.value)
|
|
if values_match?(snapshot)
|
|
SuccessfulMatchData.new(description)
|
|
else
|
|
FailedMatchData.new(description, "#{actual.label} does not have #{expected.label}", values(snapshot).to_a)
|
|
end
|
|
end
|
|
|
|
# Performs the test against the expression, but inverted.
|
|
# A successful match with `#match` should normally fail for this method, and vice-versa.
|
|
def negated_match(actual : Expression(T)) : MatchData forall T
|
|
snapshot = snapshot_values(actual.value)
|
|
if values_match?(snapshot)
|
|
FailedMatchData.new(description, "#{actual.label} has #{expected.label}", values(snapshot).to_a)
|
|
else
|
|
SuccessfulMatchData.new(description)
|
|
end
|
|
end
|
|
|
|
def match?(actual : Expression(T)) : Bool forall T
|
|
snapshot = snapshot_values(actual.value)
|
|
values_match?(snapshot)
|
|
end
|
|
|
|
# Message displayed when the matcher isn't satisfied.
|
|
#
|
|
# This is only called when `#match?` returns false.
|
|
#
|
|
# The message should typically only contain the test expression labels.
|
|
# Actual values should be returned by `#values`.
|
|
private def failure_message(actual) : String
|
|
"#{actual.label} does not have #{expected.label}"
|
|
end
|
|
|
|
# Message displayed when the matcher isn't satisfied and is negated.
|
|
# This is essentially what would satisfy the matcher if it wasn't negated.
|
|
#
|
|
# This is only called when `#does_not_match?` returns false.
|
|
#
|
|
# The message should typically only contain the test expression labels.
|
|
# Actual values should be returned by `#values`.
|
|
private def failure_message_when_negated(actual) : String
|
|
"#{actual.label} has #{expected.label}"
|
|
end
|
|
|
|
# Captures all of the actual values.
|
|
# A `NamedTuple` is returned, with each key being the attribute.
|
|
private def snapshot_values(object)
|
|
{% begin %}
|
|
{
|
|
{% for attribute in ExpectedType.keys %}
|
|
{{attribute}}: object.has_{{attribute}}?(*@expected.value[{{attribute.symbolize}}]),
|
|
{% end %}
|
|
}
|
|
{% end %}
|
|
end
|
|
|
|
# Checks if all predicate methods from the snapshot of them are satisfied.
|
|
private def values_match?(snapshot) : Bool
|
|
# Test each predicate and immediately return false if one is false.
|
|
{% for attribute in ExpectedType.keys %}
|
|
return false unless snapshot[{{attribute.symbolize}}]
|
|
{% end %}
|
|
|
|
# All checks passed if this point is reached.
|
|
true
|
|
end
|
|
|
|
# Produces the tuple for the failed match data from a snapshot of the predicate methods.
|
|
private def values(snapshot)
|
|
{% begin %}
|
|
{
|
|
{% for attribute in ExpectedType.keys %}
|
|
{{attribute}}: snapshot[{{attribute.symbolize}}].inspect,
|
|
{% end %}
|
|
}
|
|
{% end %}
|
|
end
|
|
end
|
|
end
|