From 9bffb300419cef28e33b7a3380675fabb65cc85e Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Thu, 8 Aug 2019 16:59:29 -0600 Subject: [PATCH] Refactor UnorderedArrayMatcher --- .../matchers/unordered_array_matcher.cr | 121 ++++++------------ 1 file changed, 37 insertions(+), 84 deletions(-) diff --git a/src/spectator/matchers/unordered_array_matcher.cr b/src/spectator/matchers/unordered_array_matcher.cr index cb13a34..8c5ed49 100644 --- a/src/spectator/matchers/unordered_array_matcher.cr +++ b/src/spectator/matchers/unordered_array_matcher.cr @@ -3,18 +3,45 @@ require "./value_matcher" 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) < ValueMatcher(Enumerable(ExpectedType)) - # Determines whether the matcher is satisfied with the partial given to it. - def match(partial, negated = false) - expected_elements = expected.to_a - actual = partial.actual.to_a - missing, extra = array_diff(expected, actual) + struct UnorderedArrayMatcher(ExpectedType) < Matcher + private getter expected + + def initialize(@expected : TestValue(Enumerable(ExpectedType))) + end + + def description + "contains #{expected.label} in any order" + end + + def match(actual) + actual_elements = actual.value.to_a + expected_elements = expected.value.to_a + missing, extra = array_diff(expected_elements, actual_elements) - values = ExpectedActual.new(expected_elements, label, actual, partial.label) if missing.empty? && extra.empty? - IdenticalMatchData.new(values) + SuccessfulMatchData.new else - ContentMatchData.new(values, missing, extra) + FailedMatchData.new("#{actual_label} does not contain #{expected.label} (unordered)", + expected: expected_elements.inspect, + actual: actual_elements.inspect, + missing: missing.inspect, + extra: extra.inspect, + ) + end + end + + def negated_match(actual) + actual_elements = actual.value.to_a + expected_elements = expected.value.to_a + missing, extra = array_diff(expected_elements, actual_elements) + + if missing.empty? && extra.empty? + FailedMatchData.new("#{actual_label} contains #{expected.label} (unordered)", + expected: "Not #{expected_elements.inspect}", + actual: actual_elements.inspect, + ) + else + SuccessfulMatchData.new end end @@ -24,7 +51,7 @@ module Spectator::Matchers extra = actual.dup missing = [] of ExpectedType - # TODO: OPTIMIZE + # OPTIMIZE: Not very efficient at finding the difference. expected.each do |item| index = extra.index(item) if index @@ -36,79 +63,5 @@ module Spectator::Matchers {missing, extra} end - - # Creates the value matcher. - # The label should be a string representation of the expectation. - # The expected value is stored for later use. - def initialize(expected : Enumerable(ExpectedType), label : String) - super - end - - # Creates the value matcher. - # The label is generated by calling `#to_s` on the expected value. - # The expected value is stored for later use. - def initialize(expected : Enumerable(ExpectedType)) - super - 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 #{@values.expected_label} (unordered)" - 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 #{@values.expected_label} (unordered)" - 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(values : ExpectedActual(Array(ExpectedType), Array(ActualType)), @missing : Array(ExpectedType), @extra : Array(ActualType)) - super(false, values) - end - - # Information about the match. - def named_tuple - super.merge({ - missing: @missing, - extra: @extra, - }) - 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 #{@values.expected_label} (content differs)" - end - end end end