mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Merge branch 'have-matcher' into 'release/0.8'
Implement have predicate matcher See merge request arctic-fox/spectator!6
This commit is contained in:
commit
bc4d0117a3
6 changed files with 200 additions and 29 deletions
|
@ -302,8 +302,8 @@ Items not marked as completed may have partial implementations.
|
|||
- [X] Error matchers - `raise_error`
|
||||
- [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args`
|
||||
- [ ] Output matchers - `output[.to_stdout|.to_stderr]`
|
||||
- [X] Predicate matchers - `be_x`, `have_x`
|
||||
- [ ] Misc. matchers
|
||||
- [ ] `exist`
|
||||
- [X] `match`
|
||||
- [ ] `satisfy`
|
||||
- [ ] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]`
|
||||
|
|
87
spec/matchers/have_predicate_matcher_spec.cr
Normal file
87
spec/matchers/have_predicate_matcher_spec.cr
Normal file
|
@ -0,0 +1,87 @@
|
|||
require "../spec_helper"
|
||||
|
||||
describe Spectator::Matchers::HavePredicateMatcher do
|
||||
describe "#match" do
|
||||
context "returned MatchData" do
|
||||
describe "#match?" do
|
||||
context "with a true predicate" do
|
||||
it "is true" do
|
||||
value = "foo\\bar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, "back_references")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.matched?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context "with a false predicate" do
|
||||
it "is false" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, "back_references")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.matched?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#values" do
|
||||
it "contains a key for each expected attribute" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, "back_references")
|
||||
match_data = matcher.match(partial)
|
||||
match_data_has_key?(match_data.values, :back_references).should be_true
|
||||
end
|
||||
|
||||
it "has the actual values" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, "back_references")
|
||||
match_data = matcher.match(partial)
|
||||
match_data_value_sans_prefix(match_data.values, :back_references)[:value].should eq(value.has_back_references?)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#message" do
|
||||
it "contains the actual label" do
|
||||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value, label)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, "back_references")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.message.should contain(label)
|
||||
end
|
||||
|
||||
it "contains the expected label" do
|
||||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, label)
|
||||
match_data = matcher.match(partial)
|
||||
match_data.message.should contain(label)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#negated_message" do
|
||||
it "contains the actual label" do
|
||||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value, label)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, "back_references")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.negated_message.should contain(label)
|
||||
end
|
||||
|
||||
it "contains the expected label" do
|
||||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::HavePredicateMatcher.new({back_references: Tuple.new}, label)
|
||||
match_data = matcher.match(partial)
|
||||
match_data.negated_message.should contain(label)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,7 +8,7 @@ describe Spectator::Matchers::PredicateMatcher do
|
|||
it "is true" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({ascii_only: Tuple.new}, "ascii_only")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.matched?.should be_true
|
||||
end
|
||||
|
@ -18,7 +18,7 @@ describe Spectator::Matchers::PredicateMatcher do
|
|||
it "is false" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(empty: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({empty: Tuple.new}, "empty")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.matched?.should be_false
|
||||
end
|
||||
|
@ -29,7 +29,7 @@ describe Spectator::Matchers::PredicateMatcher do
|
|||
it "contains a key for each expected attribute" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(empty: Nil, ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({empty: Tuple.new, ascii_only: Tuple.new}, "empty, ascii_only")
|
||||
match_data = matcher.match(partial)
|
||||
match_data_has_key?(match_data.values, :empty).should be_true
|
||||
match_data_has_key?(match_data.values, :ascii_only).should be_true
|
||||
|
@ -38,7 +38,7 @@ describe Spectator::Matchers::PredicateMatcher do
|
|||
it "has the actual values" do
|
||||
value = "foobar"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(empty: Nil, ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({empty: Tuple.new, ascii_only: Tuple.new}, "empty, ascii_only")
|
||||
match_data = matcher.match(partial)
|
||||
match_data_value_sans_prefix(match_data.values, :empty)[:value].should eq(value.empty?)
|
||||
match_data_value_sans_prefix(match_data.values, :ascii_only)[:value].should eq(value.ascii_only?)
|
||||
|
@ -50,17 +50,18 @@ describe Spectator::Matchers::PredicateMatcher do
|
|||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value, label)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({ascii_only: Tuple.new}, "ascii_only")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.message.should contain(label)
|
||||
end
|
||||
|
||||
it "contains stringified form of predicate" do
|
||||
it "contains the expected label" do
|
||||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({ascii_only: Tuple.new}, label)
|
||||
match_data = matcher.match(partial)
|
||||
match_data.message.should contain("ascii_only")
|
||||
match_data.message.should contain(label)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -69,17 +70,18 @@ describe Spectator::Matchers::PredicateMatcher do
|
|||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value, label)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({ascii_only: Tuple.new}, "ascii_only")
|
||||
match_data = matcher.match(partial)
|
||||
match_data.negated_message.should contain(label)
|
||||
end
|
||||
|
||||
it "contains stringified form of predicate" do
|
||||
it "contains the expected label" do
|
||||
value = "foobar"
|
||||
label = "blah"
|
||||
partial = new_partial(value)
|
||||
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
|
||||
matcher = Spectator::Matchers::PredicateMatcher.new({ascii_only: Tuple.new}, label)
|
||||
match_data = matcher.match(partial)
|
||||
match_data.negated_message.should contain("ascii_only")
|
||||
match_data.negated_message.should contain(label)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -561,7 +561,7 @@ module Spectator::DSL
|
|||
end
|
||||
|
||||
# Used to create predicate matchers.
|
||||
# Any missing method that starts with 'be_' will be handled.
|
||||
# Any missing method that starts with 'be_' or 'have_' will be handled.
|
||||
# All other method names will be ignored and raise a compile-time error.
|
||||
#
|
||||
# This can be used to simply check a predicate method that ends in '?'.
|
||||
|
@ -570,14 +570,37 @@ module Spectator::DSL
|
|||
# expect("foobar").to be_ascii_only
|
||||
# # Is equivalent to:
|
||||
# expect("foobar".ascii_only?).to be_true
|
||||
#
|
||||
# expect("foobar").to_not have_back_references
|
||||
# # Is equivalent to:
|
||||
# expect("foobar".has_back_references?).to_not be_true
|
||||
# ```
|
||||
macro method_missing(call)
|
||||
{% if call.name.starts_with?("be_") %}
|
||||
{% method_name = call.name[3..-1] %} # Remove be_ prefix.
|
||||
::Spectator::Matchers::PredicateMatcher(NamedTuple({{method_name}}: Nil)).new
|
||||
# Remove `be_` prefix.
|
||||
{% method_name = call.name[3..-1] %}
|
||||
{% matcher = "PredicateMatcher" %}
|
||||
{% elsif call.name.starts_with?("have_") %}
|
||||
# Remove `have_` prefix.
|
||||
{% method_name = call.name[5..-1] %}
|
||||
{% matcher = "HavePredicateMatcher" %}
|
||||
{% else %}
|
||||
{% raise "Undefined local variable or method '#{call}'" %}
|
||||
{% raise "Undefined local variable or method '#{call}'" %}
|
||||
{% end %}
|
||||
|
||||
descriptor = { {{method_name}}: Tuple.new({{call.args.splat}}) }
|
||||
label = String::Builder.new({{method_name.stringify}})
|
||||
{% unless call.args.empty? %}
|
||||
label << '('
|
||||
{% for arg, index in call.args %}
|
||||
label << {{arg}}
|
||||
{% if index < call.args.size - 1 %}
|
||||
label << ", "
|
||||
{% end %}
|
||||
{% end %}
|
||||
label << ')'
|
||||
{% end %}
|
||||
::Spectator::Matchers::{{matcher.id}}.new(descriptor, label.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
64
src/spectator/matchers/have_predicate_matcher.cr
Normal file
64
src/spectator/matchers/have_predicate_matcher.cr
Normal file
|
@ -0,0 +1,64 @@
|
|||
require "./value_matcher"
|
||||
|
||||
module Spectator::Matchers
|
||||
# Matcher that tests one or more "has" predicates
|
||||
# (methods ending in '?' and starting with 'has_').
|
||||
# The `ExpectedType` type param should be a `NamedTuple`.
|
||||
# Each key in the tuple is a predicate (without the '?' and 'has_' prefix) to test.
|
||||
# Each value is a a `Tuple` of arguments to pass to the predicate method.
|
||||
struct HavePredicateMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
||||
# Determines whether the matcher is satisfied with the value given to it.
|
||||
private def match?(values)
|
||||
# Test each predicate and immediately return false if one is false.
|
||||
{% for attribute in ExpectedType.keys %}
|
||||
return false unless values[{{attribute.symbolize}}]
|
||||
{% end %}
|
||||
|
||||
# All checks passed if this point is reached.
|
||||
true
|
||||
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) : MatchData
|
||||
values = snapshot_values(partial.actual)
|
||||
MatchData.new(match?(values), values, partial.label, label)
|
||||
end
|
||||
|
||||
# Captures all of the actual values.
|
||||
# A `NamedTuple` is returned,
|
||||
# with each key being the attribute.
|
||||
private def snapshot_values(actual)
|
||||
{% begin %}
|
||||
{
|
||||
{% for attribute in ExpectedType.keys %}
|
||||
{{attribute}}: actual.has_{{attribute}}?(*@expected[{{attribute.symbolize}}]),
|
||||
{% end %}
|
||||
}
|
||||
{% end %}
|
||||
end
|
||||
|
||||
# Match data specific to this matcher.
|
||||
private struct MatchData(ActualType) < MatchData
|
||||
# Creates the match data.
|
||||
def initialize(matched, @named_tuple : ActualType, @actual_label : String, @expected_label : String)
|
||||
super(matched)
|
||||
end
|
||||
|
||||
# Information about the match.
|
||||
getter named_tuple
|
||||
|
||||
# Describes the condition that satisfies the matcher.
|
||||
# This is informational and displayed to the end-user.
|
||||
def message
|
||||
"#{@actual_label} has #{@expected_label}"
|
||||
end
|
||||
|
||||
# Describes the condition that won't satsify the matcher.
|
||||
# This is informational and displayed to the end-user.
|
||||
def negated_message
|
||||
"#{@actual_label} does not have #{@expected_label}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,13 +4,8 @@ module Spectator::Matchers
|
|||
# Matcher that tests one or more predicates (methods ending in '?').
|
||||
# The `ExpectedType` type param should be a `NamedTuple`.
|
||||
# Each key in the tuple is a predicate (without the '?') to test.
|
||||
struct PredicateMatcher(ExpectedType) < Matcher
|
||||
# Textual representation of what the matcher expects.
|
||||
# Constructs the label from the type parameters.
|
||||
def label
|
||||
{{ExpectedType.keys.splat.stringify}}
|
||||
end
|
||||
|
||||
# Each value is a a `Tuple` of arguments to pass to the predicate method.
|
||||
struct PredicateMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
||||
# Determines whether the matcher is satisfied with the value given to it.
|
||||
private def match?(values)
|
||||
# Test each predicate and immediately return false if one is false.
|
||||
|
@ -26,7 +21,7 @@ module Spectator::Matchers
|
|||
# `MatchData` is returned that contains information about the match.
|
||||
def match(partial) : MatchData
|
||||
values = snapshot_values(partial.actual)
|
||||
MatchData.new(match?(values), values, partial.label)
|
||||
MatchData.new(match?(values), values, partial.label, label)
|
||||
end
|
||||
|
||||
# Captures all of the actual values.
|
||||
|
@ -36,7 +31,7 @@ module Spectator::Matchers
|
|||
{% begin %}
|
||||
{
|
||||
{% for attribute in ExpectedType.keys %}
|
||||
{{attribute}}: actual.{{attribute}}?,
|
||||
{{attribute}}: actual.{{attribute}}?(*@expected[{{attribute.symbolize}}]),
|
||||
{% end %}
|
||||
}
|
||||
{% end %}
|
||||
|
@ -45,7 +40,7 @@ module Spectator::Matchers
|
|||
# Match data specific to this matcher.
|
||||
private struct MatchData(ActualType) < MatchData
|
||||
# Creates the match data.
|
||||
def initialize(matched, @named_tuple : ActualType, @actual_label : String)
|
||||
def initialize(matched, @named_tuple : ActualType, @actual_label : String, @expected_label : String)
|
||||
super(matched)
|
||||
end
|
||||
|
||||
|
@ -55,13 +50,13 @@ module Spectator::Matchers
|
|||
# Describes the condition that satisfies the matcher.
|
||||
# This is informational and displayed to the end-user.
|
||||
def message
|
||||
"#{@actual_label} is " + {{ActualType.keys.splat.stringify}}
|
||||
"#{@actual_label} is #{@expected_label}"
|
||||
end
|
||||
|
||||
# Describes the condition that won't satsify the matcher.
|
||||
# This is informational and displayed to the end-user.
|
||||
def negated_message
|
||||
"#{@actual_label} is not " + {{ActualType.keys.splat.stringify}}
|
||||
"#{@actual_label} is not #{@expected_label}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue