Merge branch 'all-matcher' into 'release/0.8'

All matcher

See merge request arctic-fox/spectator!14
This commit is contained in:
Mike Miller 2019-08-12 20:19:45 +00:00
commit db9715341e
4 changed files with 73 additions and 3 deletions

View file

@ -296,7 +296,7 @@ Items not marked as completed may have partial implementations.
- [X] `be_empty`
- [X] `have_key`
- [X] `have_value`
- [ ] `all`
- [X] `all`
- [ ] `all_satisfy`
- [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil`
- [X] Error matchers - `raise_error`

View file

@ -526,6 +526,19 @@ module Spectator::DSL
::Spectator::Matchers::AttributesMatcher.new(%test_value)
end
# Verifies that all elements of a collection satisfy some matcher.
# The collection should implement `Enumerable`.
#
# Examples:
# ```
# array = [1, 2, 3, 4]
# expect(array).to all(be_even) # Fails.
# expect(array).to all(be_lt(5)) # Passes.
# ```
macro all(matcher)
::Spectator::Matchers::AllMatcher.new({{matcher}})
end
# Indicates that some expression's value should change after taking an action.
#
# Examples:

View file

@ -0,0 +1,57 @@
require "../test_value"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that checks if all elements of a collection apply to some other matcher.
struct AllMatcher(TMatcher) < Matcher
# Other matcher that all elements must match successfully.
private getter matcher
# Creates the matcher with an expected successful matcher.
def initialize(@matcher : TMatcher)
end
# Short text about the matcher's purpose.
# This explains what condition satisfies the matcher.
# The description is used when the one-liner syntax is used.
def description
"all #{matcher.description}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
found = test_values(actual).each do |element|
match_data = matcher.match(element)
break match_data unless match_data.matched?
end
found ? found : SuccessfulMatchData.new
end
# Negated matching for this matcher is not supported.
# Attempting to call this method will result in a compilation error.
#
# This syntax has a logical problem.
# "All values do not satisfy some condition."
# Does this mean that all values don't satisfy the matcher?
# What if only one doesn't?
# What if the collection is empty?
#
# RSpec doesn't support this syntax either.
def negated_match(actual : TestExpression(T)) : MatchData forall T
{% raise "The `expect { }.to_not all()` syntax is not supported (ambiguous)." %}
end
# Maps all values in the test collection to their own test values.
# Each value is given their own label,
# which is the original label with an index appended.
private def test_values(actual)
label_prefix = actual.label
actual.value.map_with_index do |value, index|
label = "#{label_prefix}[#{index}]"
TestValue.new(value, label)
end
end
end
end

View file

@ -16,7 +16,7 @@ module Spectator
def self.create(proc : -> T, label : String) forall T
{% if T.id == "ReturnType".id %}
wrapper = -> { proc.call; nil }
wrapper = ->{ proc.call; nil }
TestBlock(Nil).new(wrapper, label)
{% else %}
TestBlock(T).new(proc, label)
@ -31,7 +31,7 @@ module Spectator
def self.create(proc : -> T) forall T
{% if T.id == "ReturnType".id %}
wrapper = -> { proc.call; nil }
wrapper = ->{ proc.call; nil }
TestBlock(Nil).new(wrapper)
{% else %}
TestBlock(T).new(proc)