From 535dc6e9233430ad862386357c21df68f4fce66e Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Fri, 19 Jul 2019 13:09:17 -0600 Subject: [PATCH] Add change.to variant --- spec/matchers/change_matcher_spec.cr | 41 +++++ spec/matchers/change_to_matcher_spec.cr | 191 ++++++++++++++++++++ src/spectator/matchers/change_matcher.cr | 8 +- src/spectator/matchers/change_to_matcher.cr | 102 +++++++++++ 4 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 spec/matchers/change_to_matcher_spec.cr create mode 100644 src/spectator/matchers/change_to_matcher.cr diff --git a/spec/matchers/change_matcher_spec.cr b/spec/matchers/change_matcher_spec.cr index af7fea5..d153b61 100644 --- a/spec/matchers/change_matcher_spec.cr +++ b/spec/matchers/change_matcher_spec.cr @@ -98,6 +98,14 @@ describe Spectator::Matchers::ChangeMatcher do matcher.from(0).should be_a(Spectator::Matchers::ChangeFromMatcher(Int32, Int32)) end + it "passes along the expected from value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeMatcher.new { i } + match_data = matcher.from(0).match(partial) + match_data_value_with_key(match_data.values, :"expected before").value.should eq(0) + end + it "passes along the expression" do i = 0 partial = new_block_partial { i += 5 } @@ -115,4 +123,37 @@ describe Spectator::Matchers::ChangeMatcher do match_data.message.should contain(label) end end + + describe "#to" do + it "returns a ChangeToMatcher" do + i = 0 + matcher = Spectator::Matchers::ChangeMatcher.new { i } + matcher.to(0).should be_a(Spectator::Matchers::ChangeToMatcher(Int32, Int32)) + end + + it "passes along the expected to value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeMatcher.new { i } + match_data = matcher.to(5).match(partial) + match_data_value_with_key(match_data.values, :"expected after").value.should eq(5) + end + + it "passes along the expression" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeMatcher.new { i } + matcher.to(5).match(partial) + i.should eq(5) # Local scope `i` will be updated if the expression (closure) was passed on. + end + + it "passes along the label" do + i = 0 + label = "EXPRESSION" + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeMatcher.new(label) { i } + match_data = matcher.to(5).match(partial) + match_data.message.should contain(label) + end + end end diff --git a/spec/matchers/change_to_matcher_spec.cr b/spec/matchers/change_to_matcher_spec.cr new file mode 100644 index 0000000..34ab71f --- /dev/null +++ b/spec/matchers/change_to_matcher_spec.cr @@ -0,0 +1,191 @@ +require "../spec_helper" + +describe Spectator::Matchers::ChangeToMatcher do + describe "#match" do + context "returned MatchData" do + context "with a static expression" do + describe "#matched?" do + it "is false" do + i = 0 + partial = new_block_partial { i += 0 } + matcher = Spectator::Matchers::ChangeToMatcher.new(i) { i } + match_data = matcher.match(partial) + match_data.matched?.should be_false + end + end + end + + context "with changing expression" do + describe "#matched?" do + it "is true" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(i + 5) { i } + match_data = matcher.match(partial) + match_data.matched?.should be_true + end + end + + describe "#values" do + context "expected before" do + it "is the negated resulting value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(5) { i } + match_data = matcher.match(partial) + match_data_value_sans_prefix(match_data.values, :"expected before")[:value].should eq(5) + match_data_value_sans_prefix(match_data.values, :"expected before")[:to_s].should start_with("Not ") + end + end + + context "actual before" do + it "is the initial value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(5) { i } + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :"actual before").value.should eq(0) + end + end + + context "expected after" do + it "is the expected value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(5) { i } + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :"expected after").value.should eq(5) + end + end + + context "actual after" do + it "is the resulting value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(5) { i } + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :"actual after").value.should eq(5) + end + end + end + + describe "#message" do + it "contains the action label" do + i = 0 + label = "ACTION" + partial = new_block_partial(label) { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(i + 5) { i } + match_data = matcher.match(partial) + match_data.message.should contain(label) + end + + it "contains the expression label" do + i = 0 + label = "EXPRESSION" + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(label, i + 5) { i } + match_data = matcher.match(partial) + match_data.message.should contain(label) + end + end + + describe "#negated_message" do + it "contains the action label" do + i = 0 + label = "ACTION" + partial = new_block_partial(label) { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(i + 5) { i } + match_data = matcher.match(partial) + match_data.negated_message.should contain(label) + end + + it "contains the expression label" do + i = 0 + label = "EXPRESSION" + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(label, i + 5) { i } + match_data = matcher.match(partial) + match_data.negated_message.should contain(label) + end + end + end + + context "with the wrong final value" do + describe "#matched?" do + it "is false" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(2) { i } + match_data = matcher.match(partial) + match_data.matched?.should be_false + end + end + + describe "#values" do + context "expected before" do + it "is the negated resulting value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(2) { i } + match_data = matcher.match(partial) + match_data_value_sans_prefix(match_data.values, :"expected before")[:value].should eq(2) + match_data_value_sans_prefix(match_data.values, :"expected before")[:to_s].should start_with("Not ") + end + end + + context "actual before" do + it "is the initial value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(2) { i } + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :"actual before").value.should eq(0) + end + end + + context "expected after" do + it "is the expected value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(2) { i } + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :"expected after").value.should eq(2) + end + end + + context "actual after" do + it "is the resulting value" do + i = 0 + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(2) { i } + match_data = matcher.match(partial) + match_data_value_with_key(match_data.values, :"actual after").value.should eq(5) + end + end + end + + describe "#message" do + it "contains the expression label" do + i = 0 + label = "EXPRESSION" + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(label, 2) { i } + match_data = matcher.match(partial) + match_data.message.should contain(label) + end + end + + describe "#negated_message" do + it "contains the expression label" do + i = 0 + label = "EXPRESSION" + partial = new_block_partial { i += 5 } + matcher = Spectator::Matchers::ChangeToMatcher.new(label, 2) { i } + match_data = matcher.match(partial) + match_data.negated_message.should contain(label) + end + end + end + end + end +end diff --git a/src/spectator/matchers/change_matcher.cr b/src/spectator/matchers/change_matcher.cr index 72aa1f0..0b56951 100644 --- a/src/spectator/matchers/change_matcher.cr +++ b/src/spectator/matchers/change_matcher.cr @@ -1,5 +1,6 @@ require "./change_from_matcher" -require "./value_matcher" +require "./change_to_matcher" +require "./matcher" module Spectator::Matchers # Matcher that tests whether an expression changed. @@ -35,6 +36,11 @@ module Spectator::Matchers ChangeFromMatcher.new(label, value, &@expression) end + # Specifies what the resulting value of the expression must be. + def to(value : T) forall T + ChangeToMatcher.new(label, value, &@expression) + end + # Match data specific to this matcher. private struct MatchData(ExpressionType) < MatchData # Creates the match data. diff --git a/src/spectator/matchers/change_to_matcher.cr b/src/spectator/matchers/change_to_matcher.cr new file mode 100644 index 0000000..0a8d1aa --- /dev/null +++ b/src/spectator/matchers/change_to_matcher.cr @@ -0,0 +1,102 @@ +require "./value_matcher" + +module Spectator::Matchers + # Matcher that tests whether an expression changed to a specific value. + struct ChangeToMatcher(ExpressionType, ToType) < Matcher + # Textual representation of what the matcher expects. + # This shouldn't be used in the conditional logic, + # but for verbose output to help the end-user. + getter label : String + + # Determines whether the matcher is satisfied with the partial given to it. + # `MatchData` is returned that contains information about the match. + def match(partial) + before = @expression.call # Retrieve the expression's initial value. + partial.actual # Invoke action that might change the expression's value. + after = @expression.call # Retrieve the expression's value again. + if @expected_after != after + # Resulting value isn't what was expected. + ResultingMatchData.new(before, @expected_after, after, partial.label, label) + else + # Check if the expression's value changed. + same = before == after + ChangeMatchData.new(!same, before, @expected_after, after, partial.label, label) + end + end + + # Creates a new change matcher with a custom label. + def initialize(@label, @expected_after : ToType, &expression : -> ExpressionType) + @expression = expression + end + + # Creates a new change matcher. + def initialize(@expected_after : ToType, &expression : -> ExpressionType) + @label = expression.to_s + @expression = expression + end + + # Match data for when the resulting value isn't the expected value. + private struct ResultingMatchData(ExpressionType, ToType) < MatchData + # Creates the match data. + def initialize(@before : ExpressionType, @expected_after : ToType, @actual_after : ExpressionType, + @action_label : String, @expression_label : String) + super(false) + end + + # Do not allow negation of this match data. + def override? + true + end + + # Information about the match. + def named_tuple + { + "expected before": NegatableMatchDataValue.new(@expected_after, true), + "actual before": @before, + "expected after": @expected_after, + "actual after": @actual_after, + } + end + + # This is informational and displayed to the end-user. + def message + "#{@expression_label} changes to #{@expected_after}" + end + + # This is informational and displayed to the end-user. + def negated_message + "#{@expression_label} did not change to #{@expected_after}" + end + end + + private struct ChangeMatchData(ExpressionType, ToType) < MatchData + # Creates the match data. + def initialize(matched, @before : ToType, @expected_after : ToType, @actual_after : ExpressionType, + @action_label : String, @expression_label : String) + super(matched) + end + + # Information about the match. + def named_tuple + { + "expected before": NegatableMatchDataValue.new(@expected_after, true), + "actual before": @before, + "expected after": @expected_after, + "actual after": @actual_after, + } + end + + # Describes the condition that satisfies the matcher. + # This is informational and displayed to the end-user. + def message + "#{@action_label} changed #{@expression_label} to #{@expected_after}" + end + + # Describes the condition that won't satsify the matcher. + # This is informational and displayed to the end-user. + def negated_message + "#{@action_label} did not change #{@expression_label} to #{@expected_after}" + end + end + end +end