Initial refactor of ArrayMatcher

This commit is contained in:
Michael Miller 2019-08-06 21:56:33 -06:00
parent 044202e606
commit 13fad5081b

View file

@ -5,129 +5,72 @@ module Spectator::Matchers
# Matcher for checking that the contents of one array (or similar type) # Matcher for checking that the contents of one array (or similar type)
# has the exact same contents as another and in the same order. # has the exact same contents as another and in the same order.
struct ArrayMatcher(ExpectedType) < ValueMatcher(Enumerable(ExpectedType)) struct ArrayMatcher(ExpectedType) < ValueMatcher(Enumerable(ExpectedType))
# Determines whether the matcher is satisfied with the partial given to it. def description
def match(partial, negated = false) "contains exactly #{expected.label}"
actual = partial.actual.to_a end
expected_elements = expected.to_a
values = ExpectedActual.new(expected_elements, label, actual, partial.label) private def failure_message(actual)
if values.expected.size == values.actual.size {% raise "This method should never be called" %}
end
private def failure_message_when_negated(actual)
{% raise "This method should never be called" %}
end
private def match?(actual)
{% raise "This method should never be called" %}
end
private def does_not_match?(actual)
{% raise "This method should never be called" %}
end
def match(actual)
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
if expected_elements.size == actual_elements.size
index = 0 index = 0
values.expected.zip(values.actual) do |expected, element| expected_elements.zip(actual_elements) do |expected_element, actual_element|
return ContentMatchData.new(index, values) unless expected == element unless expected_element == actual_element
return FailedMatchData.new("#{actual.label} does not contain exactly #{expected.label} (element mismatch)",
[
LabeledValue.new(expected_element.inspect, "expected"),
LabeledValue.new(actual_element.inspect, "actual"),
LabeledValue.new(index.to_s, "index"),
])
end
index += 1 index += 1
end end
IdenticalMatchData.new(values) # Success.
SuccessfulMatchData.new
else else
SizeMatchData.new(values) # Size mismatch.
FailedMatchData.new("#{actual.label} does not contain exactly #{expected.label} (size mismatch)",
[
LabeledValue.new(expected_elements.inspect, "expected"),
LabeledValue.new(actual_elements.inspect, "actual"),
LabeledValue.new(expected_elements.size, "expected size"),
LabeledValue.new(actual_elements.size, "actual size"),
])
end end
end end
# Creates the value matcher. def negated_match(actual)
# The label should be a string representation of the expectation. actual_elements = actual.value.to_a
# The expected value is stored for later use. expected_elements = expected.value.to_a
def initialize(expected : Enumerable(ExpectedType), label : String) if expected_elements.size == actual_elements.size
super index = 0
end expected_elements.zip(actual_elements) do |expected_element, actual_element|
return SuccessfulMatchData.new unless expected_element == actual_element
# Creates the value matcher. index += 1
# The label is generated by calling `#to_s` on the expected value. end
# The expected value is stored for later use. FailedMatchData.new("#{actual.label} contains exactly #{expected.label}",
def initialize(expected : Enumerable(ExpectedType)) [
super LabeledValue.new("Not #{expected_elements.inspect}", "expected"),
end LabeledValue.new(actual_elements.inspect, "actual"),
])
# Returns a matcher that uses the same expected array, but allows unordered items. else
def in_any_order SuccessfulMatchData.new
UnorderedArrayMatcher.new(@expected, @label)
end
# Returns self.
# Exists for syntax to ensure in-order matching is performed.
def in_order
self
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(Array(ExpectedType), Array(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(Array(ExpectedType), Array(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(Array(ExpectedType), Array(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(Array(ExpectedType), Array(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
end end