Initial code for basic change matcher

This commit is contained in:
Michael Miller 2019-07-14 09:39:27 -06:00
parent 1521107934
commit 3e1ee7eb6d
3 changed files with 173 additions and 0 deletions

View File

@ -0,0 +1,81 @@
require "../spec_helper"
describe Spectator::Matchers::ChangeMatcher do
describe "#match" do
context "returned MatchData" do
context "with changing expression" do
describe "#matched?" do
it "is true" do
i = 0
partial = new_block_partial { i += 5 }
matcher = Spectator::Matchers::ChangeMatcher.new { i }
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
describe "#values" do
context "before" do
it "is the initial value" do
i = 0
partial = new_block_partial { i += 5 }
matcher = Spectator::Matchers::ChangeMatcher.new { i }
match_data = matcher.match(partial)
match_data_value_with_key(match_data.values, :before).value.should eq(0)
end
end
context "after" do
it "is the resulting value" do
i = 0
partial = new_block_partial { i += 5 }
matcher = Spectator::Matchers::ChangeMatcher.new { i }
match_data = matcher.match(partial)
match_data_value_with_key(match_data.values, :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::ChangeMatcher.new { 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::ChangeMatcher.new(label) { 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::ChangeMatcher.new { 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::ChangeMatcher.new(label) { i }
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
end
end
end

View File

@ -506,6 +506,38 @@ module Spectator::DSL
::Spectator::Matchers::AttributesMatcher.new(::Spectator::TestValue.new({{expected}}, {{expected.double_splat.stringify}}))
end
# Indicates that some expression's value should change after taking an action.
#
# Examples:
# ```
# i = 0
# expect { i += 1 }.to change { i }
# expect { i += 0 }.to_not change { i }
# ```
#
# ```
# i = 0
# expect { i += 5 }.to change { i }.from(0).to(5)
# ```
#
# ```
# i = 0
# expect { i += 5 }.to change { i }.to(5)
# ```
#
# ```
# i = 0
# expect { i += 5 }.to change { i }.from(0)
# ```
#
# ```
# i = 0
# expect { i += 42 }.to change { i }.by(42)
# ```
macro change(&expression)
::Spectator::Matchers::ChangeMatcher.new("`" + {{expression.body.stringify}} + "`") {{expression}}
end
# Indicates that some block should raise an error.
#
# Examples:

View File

@ -0,0 +1,60 @@
require "./value_matcher"
module Spectator::Matchers
# Matcher that tests whether an expression changed.
struct ChangeMatcher(ExpressionType) < ValueMatcher(ExpressionType)
# Determines whether the matcher is satisfied with the value given to it.
private def match?(after)
expected != after
end
# Determines whether the matcher is satisfied with the partial given to it.
# `MatchData` is returned that contains information about the match.
def match(partial)
partial.actual # Invoke action that might change the expression's value.
after = @expression.call # Retrieve the expression's value.
MatchData.new(match?(after), expected, after, partial.label, label)
end
# Creates a new change matcher with a custom label.
def initialize(label : String, &expression : -> ExpressionType)
super(yield, label)
@expression = expression
end
# Creates a new change matcher.
def initialize(&expression : -> ExpressionType)
super(yield, expression.to_s)
@expression = expression
end
# Match data specific to this matcher.
private struct MatchData(ExpressionType) < MatchData
# Creates the match data.
def initialize(matched, @before : ExpressionType, @after : ExpressionType,
@action_label : String, @expression_label : String)
super(matched)
end
# Information about the match.
def named_tuple
{
before: @before,
after: @after
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@action_label} changes #{@expression_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@action_label} does not change #{@expression_label}"
end
end
end
end