Add array matcher

This commit is contained in:
Michael Miller 2019-03-30 15:07:04 -06:00
parent cd60fae157
commit 27ec27a3f3
2 changed files with 126 additions and 0 deletions

View file

@ -447,6 +447,26 @@ module Spectator::DSL
have_value({{expected}})
end
# Indicates that some set should contain some values in exact order.
#
# Example:
# ```
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
# ```
macro contain_exactly(*expected)
::Spectator::Matchers::ArrayMatcher.new({{expected}}, {{expected.stringify}})
end
# Indicates that some set should contain the same values in exact order as another set.
#
# Example:
# ```
# expect([1, 2, 3]).to match_array([1, 2, 3])
# ```
macro match_array(expected)
::Spectator::Matchers::ArrayMatcher.new({{expected}}, {{expected.stringify}})
end
# Indicates that some value should have a set of attributes matching some conditions.
# A list of named arguments are expected.
# The names correspond to the attributes in the instance to check.

View file

@ -0,0 +1,106 @@
require "./value_matcher"
module Spectator::Matchers
struct ArrayMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the partial given to it.
# `MatchData` is returned that contains information about the match.
def match(partial)
actual = partial.actual.to_a
values = ExpectedActual.new(expected, label, actual, partial.label)
if values.expected.size == values.actual.size
index = 0
values.expected.zip(values.actual) do |expected, actual|
return ContentMatchData.new(index, values) unless expected == actual
index += 1
end
IdenticalMatchData.new(values)
else
SizeMatchData.new(values)
end
end
# Common functionality for all match data for this matcher.
private abstract struct CommonMatchData(ExpectedType, ActualType) < MatchData
# Creates the match data.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType))
super(matched)
end
# Basic information about the match.
def named_tuple
{
expected: NegatableMatchDataValue.new(@values.expected),
actual: @values.actual,
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} contains exactly #{@values.expected_label}"
end
end
# Match data specific to this matcher.
# This type is used when the actual value matches the expected value.
private struct IdenticalMatchData(ExpectedType, ActualType) < CommonMatchData(ExpectedType, ActualType)
# Creates the match data.
def initialize(values : ExpectedActual(ExpectedType, ActualType))
super(true, values)
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain exactly #{@values.expected_label}"
end
end
# Match data specific to this matcher.
# This type is used when the actual size differs from the expected size.
private struct SizeMatchData(ExpectedType, ActualType) < CommonMatchData(ExpectedType, ActualType)
# Creates the match data.
def initialize(values : ExpectedActual(ExpectedType, ActualType))
super(false, values)
end
# Information about the match.
def named_tuple
super.merge({
"expected size": NegatableMatchDataValue.new(@values.expected.size),
"actual size": @values.actual.size,
})
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain exactly #{@values.expected_label} (size differs)"
end
end
# Match data specific to this matcher.
# This type is used when the actual contents differs from the expected contents.
private struct ContentMatchData(ExpectedType, ActualType) < CommonMatchData(ExpectedType, ActualType)
# Creates the match data.
def initialize(@index : Int32, values : ExpectedActual(ExpectedType, ActualType))
super(false, values)
end
# Information about the match.
def named_tuple
super.merge({
index: @index,
"expected element": NegatableMatchDataValue.new(@values.expected[@index]),
"actual element": @values.actual[@index],
})
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain exactly #{@values.expected_label} (content differs)"
end
end
end
end