mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Merge branch 'master' into example-api
This commit is contained in:
commit
4c6b6e6436
3 changed files with 169 additions and 78 deletions
|
@ -459,6 +459,26 @@ module Spectator
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Indicates that some value or set should contain specific items.
|
||||||
|
# This is typically used on a `String` or `Array` (any `Enumerable` works).
|
||||||
|
# The *expected* argument can be a `String` or `Char`
|
||||||
|
# when the actual type (being comapred against) is a `String`.
|
||||||
|
# For `Enumerable` types, items are compared using the underying implementation.
|
||||||
|
# In both cases, the `includes?` method is used.
|
||||||
|
#
|
||||||
|
# This is identical to `#contain`, but accepts an array (or enumerable type) instead of multiple arguments.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ```
|
||||||
|
# expect("foobar").to contain_elements(["foo", "bar"])
|
||||||
|
# expect("foobar").to contain_elements(['a', 'b'])
|
||||||
|
# expect(%i[a b c]).to contain_elements(%i[a b])
|
||||||
|
# ```
|
||||||
|
macro contain_elements(expected)
|
||||||
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
||||||
|
::Spectator::Matchers::ContainMatcher.new(%test_value)
|
||||||
|
end
|
||||||
|
|
||||||
# Indicates that some range (or collection) should contain another value.
|
# Indicates that some range (or collection) should contain another value.
|
||||||
# This is typically used on a `Range` (although any `Enumerable` works).
|
# This is typically used on a `Range` (although any `Enumerable` works).
|
||||||
# The `includes?` method is used.
|
# The `includes?` method is used.
|
||||||
|
@ -520,6 +540,29 @@ module Spectator
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Indicates that some value or set should contain specific items.
|
||||||
|
# This is similar to `#contain_elements`, but uses a different method for matching.
|
||||||
|
# Typically a `String` or `Array` (any `Enumerable` works) is checked against.
|
||||||
|
# The *expected* argument can be a `String` or `Char`
|
||||||
|
# when the actual type (being comapred against) is a `String`.
|
||||||
|
# The `includes?` method is used for this case.
|
||||||
|
# For `Enumerable` types, each item is inspected until one matches.
|
||||||
|
# The === operator is used for this case, which allows for equality, type, regex, and other matches.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ```
|
||||||
|
# expect("foobar").to have_elements(["foo", "bar"])
|
||||||
|
# expect("foobar").to have_elements(['a', 'b'])
|
||||||
|
#
|
||||||
|
# expect(%i[a b c]).to have_elements(%i[b c])
|
||||||
|
# expect(%w[FOO BAR BAZ]).to have_elements([/FOO/, /bar/i])
|
||||||
|
# expect([1, 2, 3, :a, :b, :c]).to have_elements([Int32, Symbol])
|
||||||
|
# ```
|
||||||
|
macro have_elements(expected)
|
||||||
|
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
|
||||||
|
::Spectator::Matchers::HaveMatcher.new(%test_value)
|
||||||
|
end
|
||||||
|
|
||||||
# Indicates that some set, such as a `Hash`, has a given key.
|
# Indicates that some set, such as a `Hash`, has a given key.
|
||||||
# The `has_key?` method is used for this check.
|
# The `has_key?` method is used for this check.
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
require "./value_matcher"
|
require "./matcher"
|
||||||
|
|
||||||
module Spectator::Matchers
|
module Spectator::Matchers
|
||||||
# Matcher that tests whether a value, such as a `String` or `Array`, contains one or more values.
|
# Matcher that tests whether a value, such as a `String` or `Array`, contains one or more values.
|
||||||
# The values are checked with the `includes?` method.
|
# The values are checked with the `includes?` method.
|
||||||
struct ContainMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
struct ContainMatcher(ExpectedType) < Matcher
|
||||||
|
# Expected value and label.
|
||||||
|
private getter expected
|
||||||
|
|
||||||
|
# Creates the matcher with an expected value.
|
||||||
|
def initialize(@expected : TestValue(ExpectedType))
|
||||||
|
end
|
||||||
|
|
||||||
# Short text about the matcher's purpose.
|
# Short text about the matcher's purpose.
|
||||||
# This explains what condition satisfies the matcher.
|
# This explains what condition satisfies the matcher.
|
||||||
# The description is used when the one-liner syntax is used.
|
# The description is used when the one-liner syntax is used.
|
||||||
|
@ -11,54 +18,48 @@ module Spectator::Matchers
|
||||||
"contains #{expected.label}"
|
"contains #{expected.label}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks whether the matcher is satisifed with the expression given to it.
|
# Actually performs the test against the expression.
|
||||||
private def match?(actual : TestExpression(T)) : Bool forall T
|
def match(actual : TestExpression(T)) : MatchData forall T
|
||||||
actual_value = actual.value
|
actual_value = actual.value
|
||||||
return unexpected(actual_value, actual.label) unless actual_value.responds_to?(:includes?)
|
return unexpected(actual_value, actual.label) unless actual_value.responds_to?(:includes?)
|
||||||
|
|
||||||
expected.value.all? do |item|
|
missing = expected.value.reject do |item|
|
||||||
actual_value.includes?(item)
|
actual_value.includes?(item)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if missing.empty?
|
||||||
|
# Contents are present.
|
||||||
|
SuccessfulMatchData.new(description)
|
||||||
|
else
|
||||||
|
# Content is missing.
|
||||||
|
FailedMatchData.new(description, "#{actual.label} does not contain #{expected.label}",
|
||||||
|
expected: expected.value.inspect,
|
||||||
|
actual: actual_value.inspect,
|
||||||
|
missing: missing.inspect,
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the expectation is negated, then this method is called instead of `#match?`.
|
# Performs the test against the expression, but inverted.
|
||||||
private def does_not_match?(actual : TestExpression(T)) : Bool forall T
|
# A successful match with `#match` should normally fail for this method, and vice-versa.
|
||||||
|
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||||
actual_value = actual.value
|
actual_value = actual.value
|
||||||
return unexpected(actual_value, actual.label) unless actual_value.responds_to?(:includes?)
|
return unexpected(actual_value, actual.label) unless actual_value.responds_to?(:includes?)
|
||||||
|
|
||||||
!expected.value.any? do |item|
|
satisfied = expected.value.any? do |item|
|
||||||
actual_value.includes?(item)
|
actual_value.includes?(item)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Message displayed when the matcher isn't satisifed.
|
if satisfied
|
||||||
#
|
# Contents are present.
|
||||||
# This is only called when `#match?` returns false.
|
FailedMatchData.new(description, "#{actual.label} contains #{expected.label}",
|
||||||
#
|
expected: "Not #{expected.value.inspect}",
|
||||||
# The message should typically only contain the test expression labels.
|
actual: actual_value.inspect
|
||||||
# Actual values should be returned by `#values`.
|
)
|
||||||
private def failure_message(actual) : String
|
else
|
||||||
"#{actual.label} does not contain #{expected.label}"
|
# Content is missing.
|
||||||
end
|
SuccessfulMatchData.new(description)
|
||||||
|
end
|
||||||
# Message displayed when the matcher isn't satisifed and is negated.
|
|
||||||
# This is essentially what would satisfy the matcher if it wasn't negated.
|
|
||||||
#
|
|
||||||
# This is only called when `#does_not_match?` returns false.
|
|
||||||
#
|
|
||||||
# The message should typically only contain the test expression labels.
|
|
||||||
# Actual values should be returned by `#values`.
|
|
||||||
private def failure_message_when_negated(actual) : String
|
|
||||||
"#{actual.label} contains #{expected.label}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Additional information about the match failure.
|
|
||||||
# The return value is a NamedTuple with Strings for each value.
|
|
||||||
private def values(actual)
|
|
||||||
{
|
|
||||||
subset: expected.value.inspect,
|
|
||||||
superset: actual.value.inspect,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private def unexpected(value, label)
|
private def unexpected(value, label)
|
||||||
|
|
|
@ -4,70 +4,117 @@ module Spectator::Matchers
|
||||||
# Matcher that tests whether a value, such as a `String` or `Array`, matches one or more values.
|
# Matcher that tests whether a value, such as a `String` or `Array`, matches one or more values.
|
||||||
# For a `String`, the `includes?` method is used.
|
# For a `String`, the `includes?` method is used.
|
||||||
# Otherwise, it expects an `Enumerable` and iterates over each item until === is true.
|
# Otherwise, it expects an `Enumerable` and iterates over each item until === is true.
|
||||||
struct HaveMatcher(ExpectedType) < ValueMatcher(ExpectedType)
|
struct HaveMatcher(ExpectedType) < Matcher
|
||||||
|
# Expected value and label.
|
||||||
|
private getter expected
|
||||||
|
|
||||||
|
# Creates the matcher with an expected value.
|
||||||
|
def initialize(@expected : TestValue(ExpectedType))
|
||||||
|
end
|
||||||
|
|
||||||
# Short text about the matcher's purpose.
|
# Short text about the matcher's purpose.
|
||||||
# This explains what condition satisfies the matcher.
|
# This explains what condition satisfies the matcher.
|
||||||
# The description is used when the one-liner syntax is used.
|
# The description is used when the one-liner syntax is used.
|
||||||
def description : String
|
def description : String
|
||||||
"includes #{expected.label}"
|
"has #{expected.label}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks whether the matcher is satisifed with the expression given to it.
|
# Entrypoint for the matcher, forwards to the correct method for string or enumerable.
|
||||||
private def match?(actual : TestExpression(T)) : Bool forall T
|
def match(actual : TestExpression(T)) : MatchData forall T
|
||||||
if (value = actual.value).is_a?(String)
|
if (value = actual.value).is_a?(String)
|
||||||
match_string?(value)
|
match_string(value, actual.label)
|
||||||
else
|
else
|
||||||
match_enumerable?(value)
|
match_enumerable(value, actual.label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Actually performs the test against the expression.
|
||||||
|
private def match_enumerable(actual_value, actual_label)
|
||||||
|
array = actual_value.to_a
|
||||||
|
missing = expected.value.reject do |item|
|
||||||
|
array.any? do |element|
|
||||||
|
item === element
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.empty?
|
||||||
|
# Contents are present.
|
||||||
|
SuccessfulMatchData.new(description)
|
||||||
|
else
|
||||||
|
# Content is missing.
|
||||||
|
FailedMatchData.new(description, "#{actual_label} does not have #{expected.label}",
|
||||||
|
expected: expected.value.inspect,
|
||||||
|
actual: actual_value.inspect,
|
||||||
|
missing: missing.inspect,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if a `String` matches the expected values.
|
# Checks if a `String` matches the expected values.
|
||||||
# The `includes?` method is used for this check.
|
# The `includes?` method is used for this check.
|
||||||
private def match_string?(value)
|
private def match_string(actual_value, actual_label)
|
||||||
expected.value.all? do |item|
|
missing = expected.value.reject do |item|
|
||||||
value.includes?(item)
|
actual_value.includes?(item)
|
||||||
|
end
|
||||||
|
|
||||||
|
if missing.empty?
|
||||||
|
SuccessfulMatchData.new(description)
|
||||||
|
else
|
||||||
|
FailedMatchData.new(description, "#{actual_label} does not have #{expected.label}",
|
||||||
|
expected: expected.value.inspect,
|
||||||
|
actual: actual_value.inspect,
|
||||||
|
missing: missing.inspect,
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if an `Enumerable` matches the expected values.
|
# Performs the test against the expression, but inverted.
|
||||||
# The `===` operator is used on every item.
|
# A successful match with `#match` should normally fail for this method, and vice-versa.
|
||||||
private def match_enumerable?(value)
|
def negated_match(actual : TestExpression(T)) : MatchData forall T
|
||||||
array = value.to_a
|
if (value = actual.value).is_a?(String)
|
||||||
expected.value.all? do |item|
|
negated_match_string(value, actual.label)
|
||||||
|
else
|
||||||
|
negated_match_enumerable(value, actual.label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Actually performs the negated test against the expression.
|
||||||
|
private def negated_match_enumerable(actual_value, actual_label)
|
||||||
|
array = actual_value.to_a
|
||||||
|
satisfied = expected.value.any? do |item|
|
||||||
array.any? do |element|
|
array.any? do |element|
|
||||||
item === element
|
item === element
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if satisfied
|
||||||
|
# Contents are present.
|
||||||
|
FailedMatchData.new(description, "#{actual_label} has #{expected.label}",
|
||||||
|
expected: "Not #{expected.value.inspect}",
|
||||||
|
actual: actual_value.inspect
|
||||||
|
)
|
||||||
|
else
|
||||||
|
# Content is missing.
|
||||||
|
SuccessfulMatchData.new(description)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Message displayed when the matcher isn't satisifed.
|
# Checks if a `String` doesn't match the expected values.
|
||||||
#
|
# The `includes?` method is used for this check.
|
||||||
# This is only called when `#match?` returns false.
|
private def negated_match_string(actual_value, actual_label)
|
||||||
#
|
satisfied = expected.value.any? do |item|
|
||||||
# The message should typically only contain the test expression labels.
|
actual_value.includes?(item)
|
||||||
# Actual values should be returned by `#values`.
|
end
|
||||||
private def failure_message(actual) : String
|
|
||||||
"#{actual.label} does not include #{expected.label}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Message displayed when the matcher isn't satisifed and is negated.
|
if satisfied
|
||||||
# This is essentially what would satisfy the matcher if it wasn't negated.
|
SuccessfulMatchData.new(description)
|
||||||
#
|
else
|
||||||
# This is only called when `#does_not_match?` returns false.
|
FailedMatchData.new(description, "#{actual_label} does not have #{expected.label}",
|
||||||
#
|
expected: expected.value.inspect,
|
||||||
# The message should typically only contain the test expression labels.
|
actual: actual_value.inspect,
|
||||||
# Actual values should be returned by `#values`.
|
missing: missing.inspect,
|
||||||
private def failure_message_when_negated(actual) : String
|
)
|
||||||
"#{actual.label} includes #{expected.label}"
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# Additional information about the match failure.
|
|
||||||
# The return value is a NamedTuple with Strings for each value.
|
|
||||||
private def values(actual)
|
|
||||||
{
|
|
||||||
subset: expected.value.inspect,
|
|
||||||
superset: actual.value.inspect,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue