diff --git a/spec/matchers/size_matcher_spec.cr b/spec/matchers/size_matcher_spec.cr new file mode 100644 index 0000000..e50432d --- /dev/null +++ b/spec/matchers/size_matcher_spec.cr @@ -0,0 +1,121 @@ +require "../spec_helper" + +describe Spectator::Matchers::SizeMatcher do + describe "#match" do + context "returned MatchData" do + describe "#matched?" do + context "with a matching number of items" do + it "is true" do + array = %i[a b c] + size = array.size + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data.matched?.should be_true + end + end + + context "with a different number of items" do + it "is false" do + array = %i[a b c] + size = array.size + 1 + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data.matched?.should be_false + end + end + end + + describe "#values" do + context "expected" do + it "is the size given" do + array = %i[a b c] + size = array.size + 1 + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(size) + end + end + + context "actual" do + it "is the size of the set" do + array = %i[a b c] + size = array.size + 1 + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :actual).value.should eq(array.size) + end + end + end + + describe "#message" do + it "contains the actual label" do + array = %i[a b c] + size = array.size + 1 + label = "foobar" + partial = new_partial(array, label) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data.message.should contain(label) + end + + it "contains the expected label" do + array = %i[a b c] + size = array.size + 1 + label = "foobar" + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size, label) + match_data = matcher.match(partial) + match_data.message.should contain(label) + end + + context "when expected label is omitted" do + it "contains stringified form of expected value" do + array = %i[a b c] + size = array.size + 1 + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data.message.should contain(size.to_s) + end + end + end + + describe "#negated_message" do + it "contains the actual label" do + array = %i[a b c] + size = array.size + 1 + label = "foobar" + partial = new_partial(array, label) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data.negated_message.should contain(label) + end + + it "contains the expected label" do + array = %i[a b c] + size = array.size + 1 + label = "foobar" + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size, label) + match_data = matcher.match(partial) + match_data.negated_message.should contain(label) + end + + context "when expected label is omitted" do + it "contains stringified form of expected value" do + array = %i[a b c] + size = array.size + 1 + partial = new_partial(array) + matcher = Spectator::Matchers::SizeMatcher.new(size) + match_data = matcher.match(partial) + match_data.negated_message.should contain(size.to_s) + end + end + end + end + end +end diff --git a/src/spectator/dsl/matcher_dsl.cr b/src/spectator/dsl/matcher_dsl.cr index 665e44f..544de7c 100644 --- a/src/spectator/dsl/matcher_dsl.cr +++ b/src/spectator/dsl/matcher_dsl.cr @@ -470,6 +470,16 @@ module Spectator::DSL ::Spectator::Matchers::ArrayMatcher.new({{expected}}, {{expected.stringify}}) end + # Indicates that some set should have a specified size. + # + # Example: + # ``` + # expect([1, 2, 3]).to have_size(3) + # ``` + macro have_size(expected) + ::Spectator::Matchers::SizeMatcher.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. diff --git a/src/spectator/matchers/size_matcher.cr b/src/spectator/matchers/size_matcher.cr new file mode 100644 index 0000000..7295acf --- /dev/null +++ b/src/spectator/matchers/size_matcher.cr @@ -0,0 +1,43 @@ +require "./value_matcher" + +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) + # 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.size + values = ExpectedActual.new(expected, label, actual, partial.label) + MatchData.new(actual == expected, values) + end + + # Match data specific to this matcher. + private struct MatchData(ExpectedType, ActualType) < MatchData + # Creates the match data. + def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) + super(matched) + end + + # 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} has #{@values.expected_label} elements" + 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 have #{@values.expected_label} elements" + end + end + end +end