Add matcher docs

This commit is contained in:
Michael Miller 2019-08-10 10:50:48 -06:00
parent 37c8dd0865
commit d5fd21702a
30 changed files with 531 additions and 4 deletions

View File

@ -7,15 +7,21 @@ module Spectator::Matchers
# Matcher for checking that the contents of one array (or similar type)
# has the exact same contents as another and in the same order.
struct ArrayMatcher(ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"contains exactly #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
@ -31,6 +37,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
@ -45,14 +53,23 @@ module Spectator::Matchers
end
end
# Ensures the arrays elements are compared in order.
# This is the default behavior for the matcher.
def in_order
self
end
# Specifies that the arrays elements can be compared in any order.
# The elements can be in a different order, but both arrays must have the same elements.
def in_any_order
UnorderedArrayMatcher.new(expected)
end
# Compares two arrays to determine whether they contain the same elements, and in the same order.
# If the arrays are the same, then `true` is returned.
# If they are different, `false` or an integer is returned.
# `false` is returned when the sizes of the arrays don't match.
# An integer is returned, that is the index of the mismatched elements in the arrays.
private def compare_arrays(expected_elements, actual_elements)
if expected_elements.size == actual_elements.size
index = 0
@ -66,6 +83,7 @@ module Spectator::Matchers
end
end
# Produces match data for a failure when the array sizes differ.
private def failed_size_mismatch(expected_elements, actual_elements, actual_label)
FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (size mismatch)",
expected: expected_elements.inspect,
@ -75,6 +93,7 @@ module Spectator::Matchers
)
end
# Produces match data for a failure when the array content is mismatched.
private def failed_content_mismatch(expected_elements, actual_elements, index, actual_label)
FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (element mismatch)",
expected: expected_elements[index].inspect,
@ -83,6 +102,7 @@ module Spectator::Matchers
)
end
# Produces match data for a failure when the arrays are identical, but they shouldn't be (negation).
private def failed_content_identical(expected_elements, actual_elements, actual_label)
FailedMatchData.new("#{actual_label} contains exactly #{expected.label}",
expected: "Not #{expected_elements.inspect}",

View File

@ -10,15 +10,21 @@ module Spectator::Matchers
# Each key in the tuple is the attribute/method name,
# and the corresponding value is the expected value to match against.
struct AttributesMatcher(ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"has attributes #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -28,6 +34,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -49,6 +57,7 @@ module Spectator::Matchers
{% end %}
end
# Checks if all attributes from the snapshot of them are satisified.
private def match?(snapshot)
# Test that every attribute has the expected value.
{% for attribute in ExpectedType.keys %}
@ -59,6 +68,7 @@ module Spectator::Matchers
true
end
# Produces the tuple for the failed match data from a snapshot of the attributes.
private def values(snapshot)
{% begin %}
{

View File

@ -4,18 +4,35 @@ module Spectator::Matchers
# Common matcher that tests whether two values semantically equal each other.
# The values are compared with the === operator.
struct CaseMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value === actual.value
end
# 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
"matches #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not match #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} matched #{expected.label}"
end

View File

@ -5,18 +5,35 @@ require "./value_matcher"
module Spectator::Matchers
# Matcher for checking that a value is in a collection of other values.
struct CollectionMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value.includes?(actual.value)
end
# 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
"is in #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not in #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is in #{expected.label}"
end

View File

@ -4,24 +4,43 @@ module Spectator::Matchers
# Matcher that tests whether a value, such as a `String` or `Array`, contains one or more values.
# The values are checked with the `includes?` method.
struct ContainMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value.all? do |item|
actual.value.includes?(item)
end
end
# 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
"contains #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not match #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} contains #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
subset: expected.value.inspect,

View File

@ -4,18 +4,35 @@ module Spectator::Matchers
# Matcher that tests whether a collection is empty.
# The values are checked with the `empty?` method.
struct EmptyMatcher < StandardMatcher
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value.empty?
end
# 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
"is empty"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not empty"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is empty"
end

View File

@ -7,15 +7,21 @@ module Spectator::Matchers
# The `ends_with?` method is used if it's defined on the actual type.
# Otherwise, it is treated as an `Indexable` and the `last` value is compared against.
struct EndWithMatcher(ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"ends with #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
if (value = actual.value).responds_to?(:ends_with?)
match_ends_with(value, actual.label)
@ -24,6 +30,8 @@ module Spectator::Matchers
end
end
# Checks whether the actual value ends with the expected value.
# This method expects (and uses) the `#ends_with?` method on the value.
private def match_ends_with(actual_value, actual_label)
if actual_value.ends_with?(expected.value)
SuccessfulMatchData.new
@ -35,6 +43,8 @@ module Spectator::Matchers
end
end
# Checks whether the last element of the value is the expected value.
# This method expects that the actual value is a set (enumerable).
private def match_last(actual_value, actual_label)
list = actual_value.to_a
last = list.last
@ -50,6 +60,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
if actual.value.responds_to?(:ends_with?)
negated_match_ends_with(actual)
@ -58,6 +70,8 @@ module Spectator::Matchers
end
end
# Checks whether the actual value does not end with the expected value.
# This method expects (and uses) the `#ends_with?` method on the value.
private def negated_match_ends_with(actual)
if actual.value.ends_with?(expected.value)
FailedMatchData.new("#{actual.label} ends with #{expected.label} (using #ends_with?)",
@ -69,6 +83,8 @@ module Spectator::Matchers
end
end
# Checks whether the last element of the value is not the expected value.
# This method expects that the actual value is a set (enumerable).
private def negated_match_last(actual)
list = actual.value.to_a
last = list.last

View File

@ -4,18 +4,35 @@ module Spectator::Matchers
# Common matcher that tests whether two values equal each other.
# The values are compared with the == operator.
struct EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value == actual.value
end
# 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
"equals #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not equal #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} equals #{expected.label}"
end

View File

@ -6,15 +6,21 @@ require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an exception is raised.
struct ExceptionMatcher(ExceptionType, ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with no expectation of the message.
def initialize
@expected = TestValue.new(nil, ExceptionType.to_s)
end
# Creates the matcher with an expected message.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
if (message = @expected)
"raises #{ExceptionType} with message #{message}"
@ -23,6 +29,7 @@ module Spectator::Matchers
end
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
exception = capture_exception { actual.value }
if exception.nil?
@ -52,6 +59,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
exception = capture_exception { actual.value }
if exception.nil?

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether one value is greater than or equal to another.
# The values are compared with the >= operator.
struct GreaterThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value >= expected.value
end
# 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
"greater than or equal to #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is less than #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is greater than or equal to #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: ">= #{expected.value.inspect}",
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "< #{expected.value.inspect}",

View File

@ -4,29 +4,50 @@ module Spectator::Matchers
# Matcher that tests whether one value is greater than another.
# The values are compared with the > operator.
struct GreaterThanMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value > expected.value
end
# 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
"greater than #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is less than or equal to #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is greater than #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: "> #{expected.value.inspect}",
actual: actual.value.inspect,
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "<= #{expected.value.inspect}",

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether a `Hash` (or similar type) has a given key.
# The set is checked with the `has_key?` method.
struct HaveKeyMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value.has_key?(expected.value)
end
# 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
"has key #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not have key #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} has key #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
actual_value = actual.value
set = actual_value.responds_to?(:keys) ? actual_value.keys : actual_value

View File

@ -5,6 +5,7 @@ module Spectator::Matchers
# For a `String`, the `includes?` method is used.
# Otherwise, it expects an `Enumerable` and iterates over each item until === is true.
struct HaveMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
if (value = actual.value).is_a?(String)
match_string?(value)
@ -32,18 +33,36 @@ module Spectator::Matchers
end
end
# 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
"includes #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not include #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} includes #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
subset: expected.value.inspect,

View File

@ -7,15 +7,21 @@ module Spectator::Matchers
# 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)
# Expected value and label.
private getter expected
# Creates the matcher with a expected values.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"has #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -25,6 +31,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -34,10 +42,23 @@ module Spectator::Matchers
end
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not have #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} has #{expected.label}"
end
@ -54,6 +75,7 @@ module Spectator::Matchers
{% end %}
end
# Produces the tuple for the failed match data from a snapshot of the predicate methods.
private def values(snapshot)
{% begin %}
{
@ -64,6 +86,7 @@ module Spectator::Matchers
{% end %}
end
# Checks if all predicate methods from the snapshot of them are satisified.
private def match?(snapshot)
# Test each predicate and immediately return false if one is false.
{% for attribute in ExpectedType.keys %}

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether a `Hash` (or similar type) has a given value.
# The set is checked with the `has_value?` method.
struct HaveValueMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value.has_value?(expected.value)
end
# 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
"has value #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not have value #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} has value #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
actual_value = actual.value
set = actual_value.responds_to?(:values) ? actual_value.values : actual_value

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether two values do not equal each other.
# The values are compared with the != operator.
struct InequalityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value != actual.value
end
# 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
"is not equal to #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is equal to #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is not equal to #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: "Not #{expected.value.inspect}",
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: expected.value.inspect,

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether one value is less than or equal to another.
# The values are compared with the <= operator.
struct LessThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value <= expected.value
end
# 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
"less than or equal to #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is greater than #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is less than or equal to #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: "<= #{expected.value.inspect}",
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "> #{expected.value.inspect}",

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether one value is less than another.
# The values are compared with the < operator.
struct LessThanMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value < expected.value
end
# 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
"less than #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is greater than or equal to #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is less than #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: "< #{expected.value.inspect}",
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: ">= #{expected.value.inspect}",

View File

@ -4,18 +4,35 @@ module Spectator::Matchers
# Common matcher that tests whether a value is nil.
# The `Object#nil?` method is used for this.
struct NilMatcher < StandardMatcher
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value.nil?
end
# 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
"is nil"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not nil"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is nil"
end

View File

@ -6,15 +6,21 @@ module Spectator::Matchers
# Each key in the tuple is a predicate (without the '?') to test.
# Each value is a a `Tuple` of arguments to pass to the predicate method.
struct PredicateMatcher(ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with a expected values.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"is #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -24,6 +30,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -33,10 +41,23 @@ module Spectator::Matchers
end
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is #{expected.label}"
end
@ -53,6 +74,7 @@ module Spectator::Matchers
{% end %}
end
# Produces the tuple for the failed match data from a snapshot of the predicate methods.
private def values(snapshot)
{% begin %}
{
@ -63,6 +85,7 @@ module Spectator::Matchers
{% end %}
end
# Checks if all predicate methods from the snapshot of them are satisified.
private def match?(snapshot)
# Test each predicate and immediately return false if one is false.
{% for attribute in ExpectedType.keys %}

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether a value is in a given range.
# The `Range#includes?` method is used for this check.
struct RangeMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value.includes?(actual.value)
end
# 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
"is in #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not in #{expected.label} (#{exclusivity})"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is in #{expected.label} (#{exclusivity})"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
lower: ">= #{range.begin.inspect}",
@ -28,6 +47,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
lower: "< #{range.begin.inspect}",

View File

@ -4,18 +4,35 @@ module Spectator::Matchers
# Matcher that tests whether two references are the same.
# The values are compared with the `Reference#same?` method.
struct ReferenceMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value.same?(actual.value)
end
# 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
"is #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is #{expected.label}"
end

View File

@ -6,15 +6,20 @@ module Spectator::Matchers
# The `ExpectedType` type param should be a `NamedTuple`,
# with each key being the method to check and the value is ignored.
struct RespondMatcher(ExpectedType) < Matcher
# 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
"responds to #{label}"
end
# Generated, user-friendly, string for the expected value.
private def label
# Prefix every method name with # and join them with commas.
{{ExpectedType.keys.map { |e| "##{e}".id }.splat.stringify}}
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -24,6 +29,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
@ -45,6 +52,7 @@ module Spectator::Matchers
{% end %}
end
# Produces the tuple for the failed match data from a snapshot of the results.
private def values(snapshot)
{% begin %}
{
@ -55,6 +63,7 @@ module Spectator::Matchers
{% end %}
end
# Checks if all results from the snapshot are satisified.
private def match?(snapshot)
# The snapshot did the hard work.
# Here just check if all values are true.

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether a set has a specified number of elements.
# The set's `#size` method is used for this check.
struct SizeMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value == actual.value.size
end
# 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
"has size #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} does not have #{expected.label} elements"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} has #{expected.label} elements"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: expected.value.inspect,
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "Not #{expected.value.inspect}",

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests whether a set has the same number of elements as another set.
# The set's `#size` method is used for this check.
struct SizeOfMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
expected.value.size == actual.value.size
end
# 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
"is the same size as #{expected.label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not the same size as #{expected.label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is the same size as #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: expected.value.size.inspect,
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "Not #{expected.value.size.inspect}",

View File

@ -26,6 +26,7 @@ module Spectator::Matchers
private abstract def failure_message(actual : TestExpression(T)) : String forall T
# Message displayed when the matcher isn't satisifed 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.
#
@ -39,7 +40,7 @@ module Spectator::Matchers
{% raise "Negation with #{@type.name} is not supported." %}
end
# Checks whether the matcher is satisifed.
# Checks whether the matcher is satisifed with the expression given to it.
private abstract def match?(actual : TestExpression(T)) : Bool forall T
# If the expectation is negated, then this method is called instead of `#match?`.

View File

@ -1,19 +1,27 @@
require "./value_matcher"
# Checks whether the last element of the value is the expected value.
# This method expects that the actual value is a set (enumerable).require "./value_matcher"
module Spectator::Matchers
# Matcher that tests whether a value, such as a `String` or `Array`, starts with a value.
# The `starts_with?` method is used if it's defined on the actual type.
# Otherwise, it is treated as an `Enumerable` and the `first` value is compared against.
struct StartWithMatcher(ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"starts with #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
if (value = actual.value).responds_to?(:starts_with?)
match_starts_with(value, actual.label)
@ -22,6 +30,8 @@ module Spectator::Matchers
end
end
# Checks whether the actual value starts with the expected value.
# This method expects (and uses) the `#starts_with?` method on the value.
private def match_starts_with(actual_value, actual_label)
if actual_value.starts_with?(expected.value)
SuccessfulMatchData.new
@ -33,6 +43,8 @@ module Spectator::Matchers
end
end
# Checks whether the first element of the value is the expected value.
# This method expects that the actual value is a set (enumerable).
private def match_first(actual_value, actual_label)
list = actual_value.to_a
first = list.first
@ -48,6 +60,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
if (value = actual.value).responds_to?(:starts_with?)
negated_match_starts_with(value, actual.label)
@ -56,6 +70,8 @@ module Spectator::Matchers
end
end
# Checks whether the actual value does not start with the expected value.
# This method expects (and uses) the `#starts_with?` method on the value.
private def negated_match_starts_with(actual_value, actual_label)
if actual_value.starts_with?(expected.value)
FailedMatchData.new("#{actual_label} starts with #{expected.label} (using #starts_with?)",
@ -67,6 +83,8 @@ module Spectator::Matchers
end
end
# Checks whether the first element of the value is not the expected value.
# This method expects that the actual value is a set (enumerable).
private def negated_match_first(actual_value, actual_label)
list = actual_value.to_a
first = list.first

View File

@ -15,30 +15,51 @@ module Spectator::Matchers
def initialize(@truthy : Bool = true)
end
# Generated, user-friendly, string for the expected value.
private def label
@truthy ? "truthy" : "falsey"
end
# Generated, user-friendly, string for the unexpected value.
private def negated_label
@truthy ? "falsey" : "truthy"
end
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
@truthy == !!actual.value
end
# 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
"is #{label}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is #{negated_label}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is #{label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: @truthy ? "Not false or nil" : "false or nil",
@ -47,6 +68,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: @truthy ? "false or nil" : "Not false or nil",

View File

@ -4,22 +4,41 @@ module Spectator::Matchers
# Matcher that tests a value is of a specified type.
# The values are compared with the `Object#is_a?` method.
struct TypeMatcher(Expected) < StandardMatcher
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual)
actual.value.is_a?(Expected)
end
# 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
"is as #{Expected}"
end
# Message displayed when the matcher isn't satisifed.
#
# 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)
"#{actual.label} is not a #{Expected}"
end
# Message displayed when the matcher isn't satisifed 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)
"#{actual.label} is a #{Expected}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: Expected.to_s,
@ -27,6 +46,8 @@ module Spectator::Matchers
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "Not #{Expected}",

View File

@ -4,15 +4,21 @@ module Spectator::Matchers
# Matcher for checking that the contents of one array (or similar type)
# has the exact same contents as another, but in any order.
struct UnorderedArrayMatcher(ExpectedType) < Matcher
# Expected value and label.
private getter expected
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end
# 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
"contains #{expected.label} in any order"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
@ -30,6 +36,8 @@ module Spectator::Matchers
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 : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a