Merge branch 'release/0.8' into 'master'

Version 0.8.0

Closes #18

See merge request arctic-fox/spectator!7
This commit is contained in:
Mike Miller 2019-08-12 21:27:18 +00:00
commit cf6cf5db59
98 changed files with 2709 additions and 8385 deletions

View file

@ -288,25 +288,25 @@ Items not marked as completed may have partial implementations.
- [X] `contain` - [X] `contain`
- [X] `have` - [X] `have`
- [X] `contain_exactly` - [X] `contain_exactly`
- [ ] `contain_exactly.in_any_order` - [X] `contain_exactly.in_any_order`
- [X] `match_array` - [X] `match_array`
- [ ] `match_array.in_any_order` - [X] `match_array.in_any_order`
- [X] `start_with` - [X] `start_with`
- [X] `end_with` - [X] `end_with`
- [X] `be_empty` - [X] `be_empty`
- [X] `have_key` - [X] `have_key`
- [X] `have_value` - [X] `have_value`
- [ ] `all` - [X] `all`
- [ ] `all_satisfy` - [ ] `all_satisfy`
- [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil` - [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil`
- [X] Error matchers - `raise_error` - [X] Error matchers - `raise_error`
- [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args` - [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args`
- [ ] Output matchers - `output[.to_stdout|.to_stderr]` - [ ] Output matchers - `output[.to_stdout|.to_stderr]`
- [X] Predicate matchers - `be_x`, `have_x`
- [ ] Misc. matchers - [ ] Misc. matchers
- [ ] `exist`
- [X] `match` - [X] `match`
- [ ] `satisfy` - [ ] `satisfy`
- [ ] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]` - [X] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]`
- [X] `have_attributes` - [X] `have_attributes`
- [ ] Compound - `and`, `or` - [ ] Compound - `and`, `or`
- [ ] Runner - [ ] Runner

View file

@ -1,12 +1,12 @@
name: spectator name: spectator
version: 0.7.2 version: 0.8.0
description: | description: |
A feature-rich spec testing framework for Crystal with similarities to RSpec. A feature-rich spec testing framework for Crystal with similarities to RSpec.
authors: authors:
- Michael Miller <icy.arctic.fox@gmail.com> - Michael Miller <icy.arctic.fox@gmail.com>
crystal: 0.28.0 crystal: 0.30.1
license: MIT license: MIT

View file

@ -13,7 +13,7 @@ describe Spectator::ExpectationFailed do
it "is the same as the expectation's #actual_message" do it "is the same as the expectation's #actual_message" do
expectation = new_unsatisfied_expectation expectation = new_unsatisfied_expectation
error = Spectator::ExpectationFailed.new(expectation) error = Spectator::ExpectationFailed.new(expectation)
error.message.should eq(expectation.actual_message) error.message.should eq(expectation.failure_message)
end end
end end
end end

View file

@ -1,168 +0,0 @@
require "../spec_helper"
describe Spectator::Expectations::BlockExpectationPartial do
describe "#actual" do
context "with a label" do
it "contains the value passed to the constructor" do
actual = ->{ 777 }
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, actual.to_s, __FILE__, __LINE__)
partial.actual.should eq(actual.call)
end
end
context "without a label" do
it "contains the value passed to the constructor" do
actual = ->{ 777 }
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
partial.actual.should eq(actual.call)
end
end
end
describe "#label" do
context "when provided" do
it "contains the value passed to the constructor" do
actual = ->{ 777 }
label = "lucky"
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, label, __FILE__, __LINE__)
partial.label.should eq(label)
end
end
context "when omitted" do
it "contains \"proc\"" do
actual = ->{ 777 }
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
partial.label.should match(/proc/i)
end
end
end
describe "#source_file" do
it "is the expected value" do
block = ->{ 42 }
file = __FILE__
partial = Spectator::Expectations::BlockExpectationPartial.new(block, file, __LINE__)
partial.source_file.should eq(file)
end
end
describe "#source_line" do
it "is the expected value" do
block = ->{ 42 }
line = __LINE__
partial = Spectator::Expectations::BlockExpectationPartial.new(block, __FILE__, line)
partial.source_line.should eq(line)
end
end
describe "#to" do
it "reports an expectation" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 777
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.to(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(1)
end
it "reports multiple expectations" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 777
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
5.times { partial.to(matcher) }
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(5)
end
context "with a met condition" do
it "reports a satisifed expectation" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 777
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.to(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_true
end
end
context "with an unmet condition" do
it "reports an unsatisfied expectation" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 42
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.to(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_false
end
end
end
{% for method in %i[to_not not_to] %}
describe "#" + {{method.id.stringify}} do
it "reports an expectation" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 777
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.{{method.id}}(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(1)
end
it "reports multiple expectations" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 42
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
5.times { partial.{{method.id}}(matcher) }
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(5)
end
context "with a met condition" do
it "reports an unsatisifed expectation" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 777
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.{{method.id}}(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_false
end
end
context "with an unmet condition" do
it "reports an satisfied expectation" do
spy = SpyExample.create do
actual = ->{ 777 }
expected = 42
partial = Spectator::Expectations::BlockExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.{{method.id}}(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_true
end
end
end
{% end %}
end

View file

@ -5,18 +5,22 @@ describe Spectator::Expectations::Expectation do
context "with a successful match" do context "with a successful match" do
it "is true" do it "is true" do
value = 42 value = 42
match_data = new_matcher(value).match(new_partial(value)) matcher = new_matcher(value)
partial = new_partial(value)
match_data = matcher.match(partial.actual)
match_data.matched?.should be_true # Sanity check. match_data.matched?.should be_true # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, false) expectation = Spectator::Expectations::Expectation.new(match_data, partial.source)
expectation.satisfied?.should be_true expectation.satisfied?.should be_true
end end
context "when negated" do context "when negated" do
it "is false" do it "is false" do
value = 42 value = 42
match_data = new_matcher(value).match(new_partial(value)) matcher = new_matcher(value)
match_data.matched?.should be_true # Sanity check. partial = new_partial(value)
expectation = Spectator::Expectations::Expectation.new(match_data, true) match_data = matcher.negated_match(partial.actual)
match_data.matched?.should be_false # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, partial.source)
expectation.satisfied?.should be_false expectation.satisfied?.should be_false
end end
end end
@ -24,126 +28,24 @@ describe Spectator::Expectations::Expectation do
context "with an unsuccessful match" do context "with an unsuccessful match" do
it "is false" do it "is false" do
match_data = new_matcher(42).match(new_partial(777)) matcher = new_matcher(42)
partial = new_partial(777)
match_data = matcher.match(partial.actual)
match_data.matched?.should be_false # Sanity check. match_data.matched?.should be_false # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, false) expectation = Spectator::Expectations::Expectation.new(match_data, partial.source)
expectation.satisfied?.should be_false expectation.satisfied?.should be_false
end end
context "when negated" do context "when negated" do
it "is true" do it "is true" do
match_data = new_matcher(42).match(new_partial(777)) matcher = new_matcher(42)
match_data.matched?.should be_false # Sanity check. partial = new_partial(777)
expectation = Spectator::Expectations::Expectation.new(match_data, true) match_data = matcher.negated_match(partial.actual)
match_data.matched?.should be_true # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, partial.source)
expectation.satisfied?.should be_true expectation.satisfied?.should be_true
end end
end end
end end
end end
describe "#values" do
it "is the same as the match data values" do
value = 42
match_data = new_matcher(value).match(new_partial(value))
expectation = Spectator::Expectations::Expectation.new(match_data, false)
expectation_values = expectation.values
match_data.values.zip(expectation_values).each do |m, e|
m.label.should eq(e.label)
m.value.value.should eq(e.value.value)
end
end
context "when negated" do
it "negates all negatable values" do
value = 42
match_data = new_matcher(value).match(new_partial(value))
expectation = Spectator::Expectations::Expectation.new(match_data, true)
expectation.values.each do |labeled_value|
label = labeled_value.label
value = labeled_value.value
value.to_s.should start_with(/not/i) if label == :expected
end
end
end
end
describe "#actual_message" do
context "with a successful match" do
it "equals the matcher's #message" do
value = 42
match_data = new_matcher(value).match(new_partial(value))
match_data.matched?.should be_true # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, false)
expectation.actual_message.should eq(match_data.message)
end
context "when negated" do
it "equals the matcher's #message" do
value = 42
match_data = new_matcher(value).match(new_partial(value))
match_data.matched?.should be_true # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, true)
expectation.actual_message.should eq(match_data.message)
end
end
end
context "with an unsuccessful match" do
it "equals the matcher's #negated_message" do
match_data = new_matcher(42).match(new_partial(777))
match_data.matched?.should be_false # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, false)
expectation.actual_message.should eq(match_data.negated_message)
end
context "when negated" do
it "equals the matcher's #negated_message" do
match_data = new_matcher(42).match(new_partial(777))
match_data.matched?.should be_false # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, true)
expectation.actual_message.should eq(match_data.negated_message)
end
end
end
end
describe "#expected_message" do
context "with a successful match" do
it "equals the matcher's #message" do
value = 42
match_data = new_matcher(value).match(new_partial(value))
match_data.matched?.should be_true # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, false)
expectation.expected_message.should eq(match_data.message)
end
context "when negated" do
it "equals the matcher's #negated_message" do
value = 42
match_data = new_matcher(value).match(new_partial(value))
match_data.matched?.should be_true # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, true)
expectation.expected_message.should eq(match_data.negated_message)
end
end
end
context "with an unsuccessful match" do
it "equals the matcher's #message" do
match_data = new_matcher(42).match(new_partial(777))
match_data.matched?.should be_false # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, false)
expectation.expected_message.should eq(match_data.message)
end
context "when negated" do
it "equals the matcher's #negated_message" do
match_data = new_matcher(42).match(new_partial(777))
match_data.matched?.should be_false # Sanity check.
expectation = Spectator::Expectations::Expectation.new(match_data, true)
expectation.expected_message.should eq(match_data.negated_message)
end
end
end
end
end end

View file

@ -1,166 +0,0 @@
require "../spec_helper"
describe Spectator::Expectations::ValueExpectationPartial do
describe "#actual" do
context "with a label" do
it "contains the value passed to the constructor" do
actual = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, actual.to_s, __FILE__, __LINE__)
partial.actual.should eq(actual)
end
end
context "without a label" do
it "contains the value passed to the constructor" do
actual = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
partial.actual.should eq(actual)
end
end
end
describe "#label" do
context "when provided" do
it "contains the value passed to the constructor" do
actual = 777
label = "lucky"
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, label, __FILE__, __LINE__)
partial.label.should eq(label)
end
end
context "when omitted" do
it "contains a stringified version of #actual" do
actual = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
partial.label.should eq(actual.to_s)
end
end
end
describe "#source_file" do
it "is the expected value" do
file = __FILE__
partial = Spectator::Expectations::ValueExpectationPartial.new(42, file, __LINE__)
partial.source_file.should eq(file)
end
end
describe "#source_line" do
it "is the expected value" do
line = __LINE__
partial = Spectator::Expectations::ValueExpectationPartial.new(42, __FILE__, line)
partial.source_line.should eq(line)
end
end
describe "#to" do
it "reports an expectation" do
spy = SpyExample.create do
actual = 777
expected = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.to(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(1)
end
it "reports multiple expectations" do
spy = SpyExample.create do
actual = 777
expected = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
5.times { partial.to(matcher) }
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(5)
end
context "with a met condition" do
it "reports a satisifed expectation" do
spy = SpyExample.create do
actual = 777
expected = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.to(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_true
end
end
context "with an unmet condition" do
it "reports an unsatisfied expectation" do
spy = SpyExample.create do
actual = 777
expected = 42
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.to(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_false
end
end
end
{% for method in %i[to_not not_to] %}
describe "#" + {{method.id.stringify}} do
it "reports an expectation" do
spy = SpyExample.create do
actual = 777
expected = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.{{method.id}}(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(1)
end
it "reports multiple expectations" do
spy = SpyExample.create do
actual = 777
expected = 42
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
5.times { partial.{{method.id}}(matcher) }
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.size.should eq(5)
end
context "with a met condition" do
it "reports an unsatisifed expectation" do
spy = SpyExample.create do
actual = 777
expected = 777
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.{{method.id}}(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_false
end
end
context "with an unmet condition" do
it "reports an satisfied expectation" do
spy = SpyExample.create do
actual = 777
expected = 42
partial = Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
partial.{{method.id}}(matcher)
end
Spectator::Internals::Harness.run(spy)
spy.harness.expectations.first.satisfied?.should be_true
end
end
end
{% end %}
end

View file

@ -1,30 +1,38 @@
# Utility methods for creating expectations, partials, and matchers. # Utility methods for creating expectations, partials, and matchers.
def new_partial(actual : T, label : String) forall T def new_partial(actual : T, label : String) forall T
Spectator::Expectations::ValueExpectationPartial.new(actual, label, __FILE__, __LINE__) test_value = Spectator::TestValue.new(actual, label)
source = Spectator::Source.new(__FILE__, __LINE__)
Spectator::Expectations::ExpectationPartial.new(test_value, source)
end end
def new_partial(actual : T = 123) forall T def new_partial(actual : T = 123) forall T
Spectator::Expectations::ValueExpectationPartial.new(actual, __FILE__, __LINE__) test_value = Spectator::TestValue.new(actual)
source = Spectator::Source.new(__FILE__, __LINE__)
Spectator::Expectations::ExpectationPartial.new(test_value, source)
end end
def new_block_partial(label = "BLOCK", &block) def new_block_partial(label = "BLOCK", &block)
Spectator::Expectations::BlockExpectationPartial.new(block, label, __FILE__, __LINE__) test_block = Spectator::TestBlock.new(block, label)
source = Spectator::Source.new(__FILE__, __LINE__)
Spectator::Expectations::ExpectationPartial.new(test_block, source)
end end
def new_matcher(expected : T, label : String) forall T def new_matcher(expected : T, label : String) forall T
Spectator::Matchers::EqualityMatcher.new(expected, label) test_value = Spectator::TestValue.new(expected, label)
Spectator::Matchers::EqualityMatcher.new(test_value)
end end
def new_matcher(expected : T = 123) forall T def new_matcher(expected : T = 123) forall T
Spectator::Matchers::EqualityMatcher.new(expected) test_value = Spectator::TestValue.new(expected)
Spectator::Matchers::EqualityMatcher.new(test_value)
end end
def new_expectation(expected : ExpectedType = 123, actual : ActualType = 123) forall ExpectedType, ActualType def new_expectation(expected : ExpectedType = 123, actual : ActualType = 123) forall ExpectedType, ActualType
partial = new_partial(actual, "foo") partial = new_partial(actual, "foo")
matcher = new_matcher(expected, "bar") matcher = new_matcher(expected, "bar")
match_data = matcher.match(partial) match_data = matcher.match(partial.actual)
Spectator::Expectations::Expectation.new(match_data, false) Spectator::Expectations::Expectation.new(match_data, partial.source)
end end
def new_satisfied_expectation(value : T = 123) forall T def new_satisfied_expectation(value : T = 123) forall T

View file

@ -1,382 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::ArrayMatcher do
describe "#match" do
context "returned MatchData" do
context "with identical arrays" do
describe "#matched?" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::ArrayMatcher.new(array)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
describe "#values" do
context "expected" do
it "is the expected array" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::ArrayMatcher.new(array)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(array)
end
end
context "actual" do
it "is the actual array" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::ArrayMatcher.new(array)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(array)
end
end
end
describe "#message" do
it "contains the actual label" do
array = %i[a b c]
label = "everything"
partial = new_partial(array, label)
matcher = Spectator::Matchers::ArrayMatcher.new(array)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
array = %i[a b c]
label = "everything"
partial = new_partial(array)
matcher = Spectator::Matchers::CaseMatcher.new(array, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected array" do
array1 = %i[a b c]
array2 = [1, 2, 3]
partial = new_partial(array1)
matcher = Spectator::Matchers::CaseMatcher.new(array2)
match_data = matcher.match(partial)
match_data.message.should contain(array2.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
array = %i[a b c]
label = "everything"
partial = new_partial(array, label)
matcher = Spectator::Matchers::ArrayMatcher.new(array)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
array = %i[a b c]
label = "everything"
partial = new_partial(array)
matcher = Spectator::Matchers::CaseMatcher.new(array, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected array" do
array1 = %i[a b c]
array2 = [1, 2, 3]
partial = new_partial(array1)
matcher = Spectator::Matchers::CaseMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(array2.to_s)
end
end
end
end
context "with arrays differing in size" do
describe "#matched?" do
it "is false" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
describe "#values" do
context "expected" do
it "is the expected array" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(array2)
end
end
context "actual" do
it "is the actual array" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(array1)
end
end
context "expected size" do
it "is the expected size" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"expected size")[:value].should eq(array2.size)
end
end
context "actual size" do
it "is the actual size" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"actual size")[:value].should eq(array1.size)
end
end
end
describe "#message" do
it "contains the actual label" do
array1 = %i[a b c d e]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1, label)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
array1 = %i[a b c d e]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected array" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.message.should contain(array2.to_s)
end
end
end
describe "#negated_message" do
it "mentions size" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain("size")
end
it "contains the actual label" do
array1 = %i[a b c d e]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1, label)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
array1 = %i[a b c d e]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected array" do
array1 = %i[a b c d e]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(array2.to_s)
end
end
end
end
context "with arrays differing in content" do
describe "#matched?" do
it "is false" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
describe "#values" do
context "expected" do
it "is the expected array" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(array2)
end
end
context "actual" do
it "is the actual array" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(array1)
end
end
context "expected element" do
it "is the first mismatch" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"expected element")[:value].should eq(array2.first)
end
end
context "actual element" do
it "is the first mismatch" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"actual element")[:value].should eq(array1.first)
end
end
context "index" do
it "is the mismatched index" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :index)[:value].should eq(0)
end
end
end
describe "#message" do
it "contains the actual label" do
array1 = %i[a b c]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1, label)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
array1 = %i[a b c]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected array" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.message.should contain(array2.to_s)
end
end
end
describe "#negated_message" do
it "mentions content" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain("content")
end
it "contains the actual label" do
array1 = %i[a b c]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1, label)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
array1 = %i[a b c]
array2 = %i[x y z]
label = "everything"
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected array" do
array1 = %i[a b c]
array2 = %i[x y z]
partial = new_partial(array1)
matcher = Spectator::Matchers::ArrayMatcher.new(array2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(array2.to_s)
end
end
end
end
end
end
end

View file

@ -1,358 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::AttributesMatcher do
describe "#match" do
it "uses ===" do
array = %i[a b c]
spy = SpySUT.new
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new({first: spy})
matcher.match(partial)
spy.case_eq_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "one argument" do
context "against an equal value" do
it "is true" do
array = %i[a b c]
attributes = {first: :a}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a different value" do
it "is false" do
array = %i[a b c]
attributes = {first: :z}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching type" do
it "is true" do
array = %i[a b c]
attributes = {first: Symbol}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a non-matching type" do
it "is false" do
array = %i[a b c]
attributes = {first: Int32}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching regex" do
it "is true" do
array = %w[FOO BAR BAZ]
attributes = {first: /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a non-matching regex" do
it "is false" do
array = %w[FOO BAR BAZ]
attributes = {first: /qux/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple attributes" do
context "against equal values" do
it "is true" do
array = %i[a b c]
attributes = {first: :a, last: :c}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "matching type" do
context "matching regex" do
it "is true" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "non-matching regex" do
it "is false" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /bar/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "non-matching type" do
context "matching regex" do
it "is false" do
array = [:a, 42, "FOO"]
attributes = {first: Float32, last: /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "non-matching regex" do
it "is false" do
array = [:a, 42, "FOO"]
attributes = {first: Float32, last: /bar/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
context "against one equal value" do
it "is false" do
array = %i[a b c]
attributes = {first: :a, last: :d}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no equal values" do
it "is false" do
array = %i[a b c]
attributes = {first: :d, last: :e}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching types" do
it "is true" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: String}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching type" do
it "is false" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: Float32}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching types" do
it "is false" do
array = [:a, 42, "FOO"]
attributes = {first: Float32, last: Bytes}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching regexes" do
it "is true" do
array = %w[FOO BAR BAZ]
attributes = {first: /foo/i, last: /baz/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching regex" do
it "is false" do
array = %w[FOO BAR BAZ]
attributes = {first: /foo/i, last: /qux/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching regexes" do
it "is false" do
array = %w[FOO BAR]
attributes = {first: /baz/i, last: /qux/i}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against equal and matching type and regex" do
it "is true" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /foo/i, size: 3}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
end
describe "#values" do
it "contains a key for each expected attribute" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /foo/i, size: 3}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data_has_key?(match_data.values, :"expected first").should be_true
match_data_has_key?(match_data.values, :"expected last").should be_true
match_data_has_key?(match_data.values, :"expected size").should be_true
end
it "contains a key for each actual value" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /foo/i, size: 3}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data_has_key?(match_data.values, :"actual first").should be_true
match_data_has_key?(match_data.values, :"actual last").should be_true
match_data_has_key?(match_data.values, :"actual size").should be_true
end
it "has the expected values" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /foo/i, size: 3}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"expected first")[:value].should eq(attributes[:first])
match_data_value_sans_prefix(match_data.values, :"expected last")[:value].should eq(attributes[:last])
match_data_value_sans_prefix(match_data.values, :"expected size")[:value].should eq(attributes[:size])
end
it "has the actual values" do
array = [:a, 42, "FOO"]
attributes = {first: Symbol, last: /foo/i, size: 3}
partial = new_partial(array)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"actual first")[:value].should eq(array.first)
match_data_value_sans_prefix(match_data.values, :"actual last")[:value].should eq(array.last)
match_data_value_sans_prefix(match_data.values, :"actual size")[:value].should eq(array.size)
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
attributes = {size: 6}
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
attributes = {size: 6}
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
attributes = {size: 6}
partial = new_partial(value)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.message.should contain(attributes.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
attributes = {size: 6}
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
attributes = {size: 6}
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
attributes = {size: 6}
partial = new_partial(value)
matcher = Spectator::Matchers::AttributesMatcher.new(attributes)
match_data = matcher.match(partial)
match_data.negated_message.should contain(attributes.to_s)
end
end
end
end
end
end

View file

@ -1,203 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::CaseMatcher do
describe "#match" do
it "compares using #===" do
spy = SpySUT.new
partial = new_partial(42)
matcher = Spectator::Matchers::CaseMatcher.new(spy)
matcher.match(partial)
spy.case_eq_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with identical values" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::CaseMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different values" do
it "is false" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::CaseMatcher.new(value2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with the same instance" do
it "is true" do
# Box is used because it is a reference type and doesn't override the == method.
ref = Box.new([] of Int32)
partial = new_partial(ref)
matcher = Spectator::Matchers::CaseMatcher.new(ref)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different instances" do
context "with same contents" do
it "is true" do
array1 = [1, 2, 3]
array2 = [1, 2, 3]
partial = new_partial(array1)
matcher = Spectator::Matchers::CaseMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different contents" do
it "is false" do
array1 = [1, 2, 3]
array2 = [4, 5, 6]
partial = new_partial(array1)
matcher = Spectator::Matchers::CaseMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with the same type" do
it "is true" do
value1 = "foobar"
value2 = String
partial = new_partial(value1)
matcher = Spectator::Matchers::CaseMatcher.new(value2)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a different type" do
it "is false" do
value1 = "foobar"
value2 = Array
partial = new_partial(value1)
matcher = Spectator::Matchers::CaseMatcher.new(value2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a matching regex" do
it "is true" do
value = "foobar"
pattern = /foo/
partial = new_partial(value)
matcher = Spectator::Matchers::CaseMatcher.new(pattern)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a non-matching regex" do
it "is false" do
value = "foo"
pattern = /bar/
partial = new_partial(value)
matcher = Spectator::Matchers::CaseMatcher.new(pattern)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
actual = "foobar"
expected = /foo/
partial = new_partial(actual)
matcher = Spectator::Matchers::CaseMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
end
context "actual" do
it "is the actual value" do
actual = "foobar"
expected = /foo/
partial = new_partial(actual)
matcher = Spectator::Matchers::CaseMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::CaseMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::CaseMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::CaseMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::CaseMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::CaseMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::CaseMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,391 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::CollectionMatcher do
describe "#match" do
it "compares using #includes?" do
spy = SpySUT.new
partial = new_partial(5)
matcher = Spectator::Matchers::CollectionMatcher.new(spy)
matcher.match(partial)
spy.includes_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "given a Range" do
context "inclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is true for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "exclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "given an Enumerable" do
it "is true for an existing item" do
array = %i[a b c]
value = :b
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(array)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for a non-existing item" do
array = %i[a b c]
value = :z
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(array)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "given a Range" do
context "collection" do
it "is the expected value" do
value = 5
range = Range.new(3, 9)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :collection)[:value].should eq(range)
end
end
context "actual" do
it "is the actual value" do
value = 5
range = Range.new(3, 9)
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
end
context "given an Enumerable" do
context "collection" do
it "is the expected value" do
array = %i[a b c]
value = :z
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(array)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :collection)[:value].should eq(array)
end
end
context "actual" do
it "is the actual value" do
array = %i[a b c]
value = :z
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(array)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
end
end
describe "#message" do
it "contains the actual label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.message.should contain(range.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(range)
match_data = matcher.match(partial)
match_data.negated_message.should contain(range.to_s)
end
end
end
end
end
describe "#of" do
it "is true for lower-bound" do
center = 5
diff = 4
lower = center - diff
value = lower
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
center = 5
diff = 4
lower = center - diff
value = lower - 1
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
center = 5
diff = 4
value = center
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is true for upper-bound" do
center = 5
diff = 4
upper = center + diff
value = upper
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound plus 1" do
center = 5
diff = 4
upper = center + diff
value = upper + 1
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
describe "#message" do
it "contains the original label" do
center = 5
diff = 4
value = 3
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff, label).of(center)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the center" do
center = 5
diff = 4
value = 3
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.message.should contain(center.to_s)
end
it "contains the diff" do
center = 5
diff = 4
value = 3
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.message.should contain(diff.to_s)
end
end
describe "#negated_message" do
it "contains the original label" do
center = 5
diff = 4
value = 3
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff, label).of(center)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the center" do
center = 5
diff = 4
value = 3
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.negated_message.should contain(center.to_s)
end
it "contains the diff" do
center = 5
diff = 4
value = 3
partial = new_partial(value)
matcher = Spectator::Matchers::CollectionMatcher.new(diff).of(center)
match_data = matcher.match(partial)
match_data.negated_message.should contain(diff.to_s)
end
end
end
end

View file

@ -1,386 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::ContainMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with a String" do
context "one argument" do
context "against a matching string" do
it "is true" do
value = "foobarbaz"
search = "bar"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
value = "foobar"
search = "foo"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
value = "foobar"
search = "bar"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a different string" do
it "is false" do
value = "foobar"
search = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching character" do
it "is true" do
value = "foobar"
search = 'o'
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
value = "foobar"
search = 'f'
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
value = "foobar"
search = 'r'
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a different character" do
it "is false" do
value = "foobar"
search = 'z'
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple arguments" do
context "against matching strings" do
it "is true" do
value = "foobarbaz"
search = {"foo", "bar", "baz"}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching string" do
it "is false" do
value = "foobarbaz"
search = {"foo", "qux"}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching strings" do
it "is false" do
value = "foobar"
search = {"baz", "qux"}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching characters" do
it "is true" do
value = "foobarbaz"
search = {'f', 'b', 'z'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching character" do
it "is false" do
value = "foobarbaz"
search = {'f', 'c', 'd'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching characters" do
it "is false" do
value = "foobarbaz"
search = {'c', 'd', 'e'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching string and character" do
it "is true" do
value = "foobarbaz"
search = {"foo", 'z'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a matching string and non-matching character" do
it "is false" do
value = "foobarbaz"
search = {"foo", 'c'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a non-matching string and matching character" do
it "is false" do
value = "foobarbaz"
search = {"qux", 'f'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a non-matching string and character" do
it "is false" do
value = "foobarbaz"
search = {"qux", 'c'}
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
context "with an Enumberable" do
context "one argument" do
context "against an equal value" do
it "is true" do
array = %i[a b c]
search = :b
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
array = %i[a b c]
search = :a
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
array = %i[a b c]
search = :c
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a different value" do
it "is false" do
array = %i[a b c]
search = :z
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple arguments" do
context "against equal values" do
it "is true" do
array = %i[a b c]
search = {:a, :b}
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one equal value" do
it "is false" do
array = %i[a b c]
search = {:a, :d}
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no equal values" do
it "is false" do
array = %i[a b c]
search = {:d, :e}
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
end
describe "#values" do
describe "subset" do
it "is the expected set" do
array = %i[a b c]
search = {:d, :e}
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :subset)[:value].should eq(search)
end
end
describe "superset" do
it "is the actual set" do
array = %i[a b c]
search = {:d, :e}
partial = new_partial(array)
matcher = Spectator::Matchers::ContainMatcher.new(search)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :superset)[:value].should eq(array)
end
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search}, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
search = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.message.should contain(search)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search}, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
search = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::ContainMatcher.new({search})
match_data = matcher.match(partial)
match_data.negated_message.should contain(search)
end
end
end
end
end
end

View file

@ -1,73 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::EmptyMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with an empty set" do
it "is true" do
array = [] of Symbol
partial = new_partial(array)
matcher = Spectator::Matchers::EmptyMatcher.new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a filled set" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EmptyMatcher.new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "expected" do
it "is an empty set" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EmptyMatcher.new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should eq("[]")
end
end
context "actual" do
it "is the actual set" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EmptyMatcher.new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(array)
end
end
end
describe "#message" do
it "contains the actual label" do
array = %i[a b c]
label = "everything"
partial = new_partial(array, label)
matcher = Spectator::Matchers::EmptyMatcher.new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
end
describe "#negated_message" do
it "contains the actual label" do
array = %i[a b c]
label = "everything"
partial = new_partial(array, label)
matcher = Spectator::Matchers::EmptyMatcher.new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
end
end

View file

@ -1,393 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::EndWithMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with a String" do
context "against a matching string" do
it "is true" do
value = "foobar"
last = "bar"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at end" do
it "is false" do
value = "foobar"
last = "foo"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a different string" do
it "is false" do
value = "foobar"
last = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching character" do
it "is true" do
value = "foobar"
last = 'r'
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at end" do
it "is false" do
value = "foobar"
last = 'b'
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a different character" do
it "is false" do
value = "foobar"
last = 'z'
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching regex" do
it "is true" do
value = "FOOBAR"
last = /bar/i
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at end" do
it "is false" do
value = "FOOBAR"
last = /foo/i
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a non-matching regex" do
it "is false" do
value = "FOOBAR"
last = /baz/i
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "with an Enumberable" do
context "against an equal value" do
it "is true" do
array = %i[a b c]
last = :c
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at end" do
it "is false" do
array = %i[a b c]
last = :b
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a different value" do
it "is false" do
array = %i[a b c]
last = :z
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching element type" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(Symbol)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at end" do
it "is false" do
array = [1, 2, 3, :a, :b, :c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(Int32)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against different element type" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(Int32)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching regex" do
it "is true" do
array = %w[FOO BAR BAZ]
last = /baz/i
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at end" do
it "is false" do
array = %w[FOO BAR BAZ]
last = /bar/i
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a non-matching regex" do
it "is false" do
array = %w[FOO BAR BAZ]
last = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
context "with a String" do
context "expected" do
it "is the expected value" do
value = "FOOBAR"
last = /baz/i
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(last)
end
end
context "actual" do
it "is the actual value" do
value = "FOOBAR"
last = /baz/i
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
end
context "with an Indexable" do
context "expected" do
it "is the expected value" do
array = %w[FOO BAR BAZ]
last = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(last)
end
end
context "actual" do
it "is the last element" do
array = %w[FOO BAR BAZ]
last = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(array.last)
end
end
context "list" do
it "is the full actual list" do
array = %w[FOO BAR BAZ]
last = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :list)[:value].should eq(array)
end
end
end
end
describe "#message" do
context "with a String" do
it "mentions #ends_with?" do
value = "foobar"
last = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.message.should contain("#ends_with?")
end
end
context "with an Indexable" do
it "mentions ===" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(array.last)
match_data = matcher.match(partial)
match_data.message.should contain("===")
end
it "mentions last" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(array.last)
match_data = matcher.match(partial)
match_data.message.should contain("last")
end
end
it "contains the actual label" do
value = "foobar"
last = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
last = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
last = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.message.should contain(last)
end
end
end
describe "#negated_message" do
context "with a String" do
it "mentions #starts_with?" do
value = "foobar"
last = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.negated_message.should contain("#ends_with?")
end
end
context "with an Indexable" do
it "mentions ===" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(array.last)
match_data = matcher.match(partial)
match_data.negated_message.should contain("===")
end
it "mentions last" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::EndWithMatcher.new(array.last)
match_data = matcher.match(partial)
match_data.negated_message.should contain("last")
end
end
it "contains the actual label" do
value = "foobar"
last = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
last = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
last = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::EndWithMatcher.new(last)
match_data = matcher.match(partial)
match_data.negated_message.should contain(last)
end
end
end
end
end
end

View file

@ -1,173 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::EqualityMatcher do
describe "#match" do
it "compares using #==" do
spy = SpySUT.new
partial = new_partial(spy)
matcher = Spectator::Matchers::EqualityMatcher.new(42)
matcher.match(partial)
spy.eq_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with identical values" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::EqualityMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different values" do
it "is false" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::EqualityMatcher.new(value2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with the same instance" do
it "is true" do
# Box is used because it is a reference type and doesn't override the == method.
ref = Box.new([] of Int32)
partial = new_partial(ref)
matcher = Spectator::Matchers::EqualityMatcher.new(ref)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different instances" do
context "with same contents" do
it "is true" do
array1 = [1, 2, 3]
array2 = [1, 2, 3]
partial = new_partial(array1)
matcher = Spectator::Matchers::EqualityMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different contents" do
it "is false" do
array1 = [1, 2, 3]
array2 = [4, 5, 6]
partial = new_partial(array1)
matcher = Spectator::Matchers::EqualityMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
expected, actual = 42, 777
partial = new_partial(actual)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
end
context "actual" do
it "is the actual value" do
expected, actual = 42, 777
partial = new_partial(actual)
matcher = Spectator::Matchers::EqualityMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "mentions ==" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::EqualityMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain("==")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::EqualityMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::EqualityMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::EqualityMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "mentions ==" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::EqualityMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain("==")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::EqualityMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::EqualityMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::EqualityMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,205 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::ExceptionMatcher do
describe "#match" do
it "compares the message using #===" do
spy = SpySUT.new
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, SpySUT).new(spy, "foo")
matcher.match(partial)
spy.case_eq_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with no exception" do
it "is false" do
partial = new_block_partial { 42 }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with an exception" do
context "of the same type" do
it "is true" do
partial = new_block_partial { raise ArgumentError.new }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, Nil).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "of a different type" do
it "is false" do
partial = new_block_partial { raise ArgumentError.new }
matcher = Spectator::Matchers::ExceptionMatcher(KeyError, Nil).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "of a sub-type" do
it "is true" do
partial = new_block_partial { raise ArgumentError.new }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "and an equal message" do
it "is true" do
message = "foobar"
partial = new_block_partial { raise ArgumentError.new(message) }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, String).new(message, "label")
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "and a different message" do
it "is false" do
partial = new_block_partial { raise ArgumentError.new("foobar") }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, String).new("different", "label")
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "and a matching regex" do
it "is true" do
partial = new_block_partial { raise ArgumentError.new("foobar") }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, Regex).new(/foo/, "label")
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "and a non-matching regex" do
it "is false" do
partial = new_block_partial { raise ArgumentError.new("foobar") }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, Regex).new(/baz/, "label")
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
describe "expected type" do
it "is the exception type" do
partial = new_block_partial { raise ArgumentError.new }
matcher = Spectator::Matchers::ExceptionMatcher(KeyError, Nil).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"expected type")[:value].should eq(KeyError)
end
end
describe "actual type" do
it "is the raised type" do
partial = new_block_partial { raise ArgumentError.new }
matcher = Spectator::Matchers::ExceptionMatcher(KeyError, Nil).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"actual type")[:value].should eq(ArgumentError)
end
context "when nothing is raised" do
it "is Nil" do
partial = new_block_partial { 42 }
matcher = Spectator::Matchers::ExceptionMatcher(KeyError, Nil).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"actual type")[:value].should eq(Nil)
end
end
end
describe "expected message" do
it "is the expected value" do
regex = /baz/
partial = new_block_partial { raise ArgumentError.new("foobar") }
matcher = Spectator::Matchers::ExceptionMatcher(KeyError, Regex).new(regex, "label")
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"expected message")[:value].should eq(regex)
end
end
describe "actual message" do
it "is the raised exception's message" do
message = "foobar"
partial = new_block_partial { raise ArgumentError.new(message) }
matcher = Spectator::Matchers::ExceptionMatcher(KeyError, Regex).new(/baz/, "label")
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"actual message")[:value].should eq(message)
end
end
end
describe "#message" do
it "mentions raise" do
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
match_data = matcher.match(partial)
match_data.message.should contain("raise")
end
it "contains the actual label" do
label = "everything"
partial = new_block_partial(label) { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
label = "everything"
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Regex).new(/foobar/, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the exception type" do
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, Nil).new
match_data = matcher.match(partial)
match_data.message.should contain("ArgumentError")
end
end
describe "#negated_message" do
it "mentions raise" do
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
match_data = matcher.match(partial)
match_data.negated_message.should contain("raise")
end
it "contains the actual label" do
label = "everything"
partial = new_block_partial(label) { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Nil).new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
label = "everything"
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(Exception, Regex).new(/foobar/, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the exception type" do
partial = new_block_partial { raise "foobar" }
matcher = Spectator::Matchers::ExceptionMatcher(ArgumentError, Nil).new
match_data = matcher.match(partial)
match_data.negated_message.should contain("ArgumentError")
end
end
end
end
end

View file

@ -1,160 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::GreaterThanEqualMatcher do
describe "#match" do
it "compares using #>=" do
spy = SpySUT.new
partial = new_partial(spy)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(42)
matcher.match(partial)
spy.ge_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with a larger value" do
it "is false" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a smaller value" do
it "is true" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with an equal value" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
it "is prefixed with >=" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should start_with(">=")
end
end
context "actual" do
it "is the actual value" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "mentions >=" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(">=")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "mentions >=" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(">=")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::GreaterThanEqualMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,160 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::GreaterThanMatcher do
describe "#match" do
it "compares using #>" do
spy = SpySUT.new
partial = new_partial(spy)
matcher = Spectator::Matchers::GreaterThanMatcher.new(42)
matcher.match(partial)
spy.gt_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with a larger value" do
it "is false" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a smaller value" do
it "is true" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with an equal value" do
it "is false" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
it "is prefixed with >" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should start_with(">")
end
end
context "actual" do
it "is the actual value" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::GreaterThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "mentions >" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(">")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "mentions >" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(">")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::GreaterThanMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,166 +0,0 @@
require "../spec_helper"
private struct FakeKeySet
def has_key?(key)
true
end
end
describe Spectator::Matchers::HaveKeyMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "against a Hash" do
context "with an existing key" do
it "is true" do
hash = Hash{"foo" => "bar"}
key = "foo"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a non-existent key" do
it "is false" do
hash = Hash{"foo" => "bar"}
key = "baz"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a NamedTuple" do
context "with an existing key" do
it "is true" do
tuple = {foo: "bar"}
key = :foo
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a non-existent key" do
it "is false" do
tuple = {foo: "bar"}
key = :baz
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
context "key" do
it "is the expected key" do
tuple = {foo: "bar"}
key = :baz
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :key)[:value].should eq(key)
end
end
context "actual" do
context "when #keys is available" do
it "is the list of keys" do
tuple = {foo: "bar"}
key = :baz
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(tuple.keys)
end
end
context "when #keys isn't available" do
it "is the actual value" do
actual = FakeKeySet.new
key = :baz
partial = new_partial(actual)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
end
describe "#message" do
it "contains the actual label" do
tuple = {foo: "bar"}
key = :foo
label = "blah"
partial = new_partial(tuple, label)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
tuple = {foo: "bar"}
key = :foo
label = "blah"
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when the expected label is omitted" do
it "contains the stringified key" do
tuple = {foo: "bar"}
key = :foo
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.message.should contain(key.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
tuple = {foo: "bar"}
key = :foo
label = "blah"
partial = new_partial(tuple, label)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
tuple = {foo: "bar"}
key = :foo
label = "blah"
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when the expected label is omitted" do
it "contains the stringified key" do
tuple = {foo: "bar"}
key = :foo
partial = new_partial(tuple)
matcher = Spectator::Matchers::HaveKeyMatcher.new(key)
match_data = matcher.match(partial)
match_data.negated_message.should contain(key.to_s)
end
end
end
end
end
end

View file

@ -1,604 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::HaveMatcher do
describe "#match" do
it "uses ===" do
array = %i[a b c]
spy = SpySUT.new
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({spy})
matcher.match(partial)
spy.case_eq_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with a String" do
context "one argument" do
context "against a matching string" do
it "is true" do
value = "foobarbaz"
search = "bar"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
value = "foobar"
search = "foo"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
value = "foobar"
search = "bar"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a different string" do
it "is false" do
value = "foobar"
search = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching character" do
it "is true" do
value = "foobar"
search = 'o'
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
value = "foobar"
search = 'f'
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
value = "foobar"
search = 'r'
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a different character" do
it "is false" do
value = "foobar"
search = 'z'
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple arguments" do
context "against matching strings" do
it "is true" do
value = "foobarbaz"
search = {"foo", "bar", "baz"}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching string" do
it "is false" do
value = "foobarbaz"
search = {"foo", "qux"}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching strings" do
it "is false" do
value = "foobar"
search = {"baz", "qux"}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching characters" do
it "is true" do
value = "foobarbaz"
search = {'f', 'b', 'z'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching character" do
it "is false" do
value = "foobarbaz"
search = {'f', 'c', 'd'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching characters" do
it "is false" do
value = "foobarbaz"
search = {'c', 'd', 'e'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching string and character" do
it "is true" do
value = "foobarbaz"
search = {"foo", 'z'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a matching string and non-matching character" do
it "is false" do
value = "foobarbaz"
search = {"foo", 'c'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a non-matching string and matching character" do
it "is false" do
value = "foobarbaz"
search = {"qux", 'f'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a non-matching string and character" do
it "is false" do
value = "foobarbaz"
search = {"qux", 'c'}
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
context "with an Enumberable" do
context "one argument" do
context "against an equal value" do
it "is true" do
array = %i[a b c]
search = :b
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
array = %i[a b c]
search = :a
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
array = %i[a b c]
search = :c
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a different value" do
it "is false" do
array = %i[a b c]
search = :z
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching type" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({Symbol})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
array = [:a, 1, 2]
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({Symbol})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
array = [0, 1, :c]
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({Symbol})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a non-matching type" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({Int32})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching regex" do
it "is true" do
array = %w[FOO BAR BAZ]
search = /bar/i
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "at the beginning" do
it "is true" do
array = %w[FOO BAR BAZ]
search = /foo/i
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "at the end" do
it "is true" do
array = %w[FOO BAR BAZ]
search = /baz/i
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
context "against a non-matching regex" do
it "is false" do
array = %w[FOO BAR BAZ]
search = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple arguments" do
context "against equal values" do
it "is true" do
array = %i[a b c]
search = {:a, :b}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "matching type" do
context "matching regex" do
it "is true" do
array = [:a, 42, "FOO"]
search = {:a, Int32, /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "non-matching regex" do
it "is false" do
array = [:a, 42, "FOO"]
search = {:a, Int32, /bar/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "non-matching type" do
context "matching regex" do
it "is false" do
array = [:a, 42, "FOO"]
search = {:a, Float32, /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "non-matching regex" do
it "is false" do
array = [:a, 42, "FOO"]
search = {:a, Float32, /bar/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
context "against one equal value" do
it "is false" do
array = %i[a b c]
search = {:a, :d}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no equal values" do
it "is false" do
array = %i[a b c]
search = {:d, :e}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching types" do
it "is true" do
array = [:a, 42, "FOO"]
search = {Symbol, String}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching type" do
it "is false" do
array = [:a, 42, "FOO"]
search = {Symbol, Float32}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching types" do
it "is false" do
array = [:a, 42, "FOO"]
search = {Float32, Bytes}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching regexes" do
it "is true" do
array = %w[FOO BAR BAZ]
search = {/foo/i, /bar/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against one matching regex" do
it "is false" do
array = %w[FOO BAR BAZ]
search = {/foo/i, /qux/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against no matching regexes" do
it "is false" do
array = %w[FOO BAR]
search = {/baz/i, /qux/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against equal and matching type and regex" do
it "is true" do
array = [:a, 42, "FOO"]
search = {:a, Int32, /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
end
end
describe "#values" do
context "subset" do
it "has the expected value" do
array = [:a, 42, "FOO"]
search = {:a, Int32, /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :subset)[:value].should eq(search)
end
end
context "superset" do
it "has the actual value" do
array = [:a, 42, "FOO"]
search = {:a, Int32, /foo/i}
partial = new_partial(array)
matcher = Spectator::Matchers::HaveMatcher.new(search)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :superset)[:value].should eq(array)
end
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search}, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
search = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.message.should contain(search)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
search = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search}, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
search = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::HaveMatcher.new({search})
match_data = matcher.match(partial)
match_data.negated_message.should contain(search)
end
end
end
end
end
end

View file

@ -1,140 +0,0 @@
require "../spec_helper"
private struct FakeValueSet
def has_value?(value)
true
end
end
describe Spectator::Matchers::HaveValueMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with an existing value" do
it "is true" do
hash = Hash{"foo" => "bar"}
value = "bar"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a non-existent value" do
it "is false" do
hash = Hash{"foo" => "bar"}
value = "baz"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "value" do
it "is the expected value" do
hash = {"foo" => "bar"}
value = "baz"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :value)[:value].should eq(value)
end
end
context "actual" do
context "when #values is available" do
it "is the list of values" do
hash = Hash{"foo" => "bar"}
value = "baz"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(hash.values)
end
end
context "when #values isn't available" do
it "is the actual value" do
actual = FakeValueSet.new
value = "baz"
partial = new_partial(actual)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
end
describe "#message" do
it "contains the actual label" do
hash = Hash{"foo" => "bar"}
value = "bar"
label = "blah"
partial = new_partial(hash, label)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
hash = Hash{"foo" => "bar"}
value = "bar"
label = "blah"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when the expected label is omitted" do
it "contains the stringified key" do
hash = Hash{"foo" => "bar"}
value = "bar"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(value.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
hash = Hash{"foo" => "bar"}
value = "bar"
label = "blah"
partial = new_partial(hash, label)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
hash = Hash{"foo" => "bar"}
value = "bar"
label = "blah"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when the expected label is omitted" do
it "contains the stringified key" do
hash = Hash{"foo" => "bar"}
value = "bar"
partial = new_partial(hash)
matcher = Spectator::Matchers::HaveValueMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value.to_s)
end
end
end
end
end
end

View file

@ -1,181 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::InequalityMatcher do
describe "#match" do
it "compares using #!=" do
spy = SpySUT.new
partial = new_partial(spy)
matcher = Spectator::Matchers::InequalityMatcher.new(42)
matcher.match(partial)
spy.ne_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with identical values" do
it "is false" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::InequalityMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with different values" do
it "is true" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::InequalityMatcher.new(value2)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with the same instance" do
it "is false" do
# Box is used because it is a reference type and doesn't override the == method.
ref = Box.new([] of Int32)
partial = new_partial(ref)
matcher = Spectator::Matchers::InequalityMatcher.new(ref)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with different instances" do
context "with same contents" do
it "is false" do
array1 = [1, 2, 3]
array2 = [1, 2, 3]
partial = new_partial(array1)
matcher = Spectator::Matchers::InequalityMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with different contents" do
it "is true" do
array1 = [1, 2, 3]
array2 = [4, 5, 6]
partial = new_partial(array1)
matcher = Spectator::Matchers::InequalityMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
expected, actual = 42, 777
partial = new_partial(actual)
matcher = Spectator::Matchers::InequalityMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
it "is prefixed with 'Not'" do
expected, actual = 42, 777
partial = new_partial(actual)
matcher = Spectator::Matchers::InequalityMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should start_with("Not")
end
end
context "actual" do
it "is the actual value" do
expected, actual = 42, 777
partial = new_partial(actual)
matcher = Spectator::Matchers::InequalityMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "mentions !=" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::InequalityMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain("!=")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::InequalityMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::InequalityMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::InequalityMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "mentions !=" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::InequalityMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain("!=")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::InequalityMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::InequalityMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::InequalityMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,160 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::LessThanEqualMatcher do
describe "#match" do
it "compares using #<=" do
spy = SpySUT.new
partial = new_partial(spy)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(42)
matcher.match(partial)
spy.le_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with a larger value" do
it "is true" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a smaller value" do
it "is false" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with an equal value" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
it "is prefixed with <=" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should start_with("<=")
end
end
context "actual" do
it "is the actual value" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "mentions <=" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain("<=")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "mentions <=" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain("<=")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::LessThanEqualMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,160 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::LessThanMatcher do
describe "#match" do
it "compares using #<" do
spy = SpySUT.new
partial = new_partial(spy)
matcher = Spectator::Matchers::LessThanMatcher.new(42)
matcher.match(partial)
spy.lt_call_count.should be > 0
end
context "returned MatchData" do
describe "#matched?" do
context "with a larger value" do
it "is true" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a smaller value" do
it "is false" do
actual = 777
expected = 42
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with an equal value" do
it "is false" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
it "is prefixed with <" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should start_with("<")
end
end
context "actual" do
it "is the actual value" do
actual = 42
expected = 777
partial = new_partial(actual)
matcher = Spectator::Matchers::LessThanMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "mentions <" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain("<")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::LessThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::LessThanMatcher.new(value2)
match_data = matcher.match(partial)
match_data.message.should contain(value2.to_s)
end
end
end
describe "#negated_message" do
it "mentions <" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain("<")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::LessThanMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = 42
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::LessThanMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value1 = 42
value2 = 777
partial = new_partial(value1)
matcher = Spectator::Matchers::LessThanMatcher.new(value2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(value2.to_s)
end
end
end
end
end
end

View file

@ -1,86 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::NilMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with nil" do
it "is true" do
value = nil.as(Bool?)
partial = new_partial(value)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with not nil" do
it "is false" do
value = true.as(Bool?)
partial = new_partial(value)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "expected" do
it "is nil" do
partial = new_partial(42)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(nil)
end
end
context "actual" do
it "is the actual value" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
end
describe "#message" do
it "mentions nil" do
partial = new_partial(42)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data.message.should contain("nil")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
end
describe "#negated_message" do
it "mentions nil" do
partial = new_partial(42)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data.negated_message.should contain("nil")
end
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::NilMatcher.new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
end
end

View file

@ -1,87 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::PredicateMatcher do
describe "#match" do
context "returned MatchData" do
describe "#match?" do
context "with a true predicate" do
it "is true" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
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::PredicateMatcher(NamedTuple(empty: Nil)).new
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::PredicateMatcher(NamedTuple(empty: Nil, ascii_only: Nil)).new
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
end
it "has the actual values" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(empty: Nil, ascii_only: Nil)).new
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?)
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
label = "blah"
partial = new_partial(value, label)
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains stringified form of predicate" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain("ascii_only")
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
label = "blah"
partial = new_partial(value, label)
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains stringified form of predicate" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::PredicateMatcher(NamedTuple(ascii_only: Nil)).new
match_data = matcher.match(partial)
match_data.negated_message.should contain("ascii_only")
end
end
end
end
end

View file

@ -1,686 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::RangeMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "inclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is true for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "exclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "lower" do
it "is #begin from the expected range" do
range = Range.new(3, 9)
partial = new_partial(5)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :lower)[:value].should eq(range.begin)
end
it "is prefixed with >=" do
range = Range.new(3, 9)
partial = new_partial(5)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :lower)[:to_s].should start_with(">=")
end
end
context "upper" do
it "is #end from the expected range" do
range = Range.new(3, 9)
partial = new_partial(5)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :upper)[:value].should eq(range.end)
end
context "when inclusive" do
it "is prefixed with <=" do
range = Range.new(3, 9, exclusive: false)
partial = new_partial(5)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :upper)[:to_s].should start_with("<=")
end
end
context "when exclusive" do
it "is prefixed with <" do
range = Range.new(3, 9, exclusive: false)
partial = new_partial(5)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :upper)[:to_s].should start_with("<")
end
end
end
context "actual" do
it "is the actual value" do
value = 5
range = Range.new(3, 9)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
end
describe "#message" do
it "contains the actual label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.message.should contain(range.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
range = 1..10
value = 5
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range)
match_data = matcher.match(partial)
match_data.negated_message.should contain(range.to_s)
end
end
end
end
end
describe "#inclusive" do
context "initially exclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is true for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
describe "#message" do
it "mentions inclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.message.should contain("inclusive")
end
it "does not mention exclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.message.should_not contain("exclusive")
end
it "contains the original label" do
range = 1...10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).inclusive
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
end
describe "#negated_message" do
it "mentions inclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain("inclusive")
end
it "does not mention exclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.negated_message.should_not contain("exclusive")
end
it "contains the original label" do
range = 1...10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).inclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
context "initially inclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is true for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
describe "#message" do
it "mentions inclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.message.should contain("inclusive")
end
it "contains the original label" do
range = 1..10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).inclusive
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
end
describe "#negated_message" do
it "mentions inclusive" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).inclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain("inclusive")
end
it "contains the original label" do
range = 1...10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).inclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
end
describe "#exclusive" do
context "initially inclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: false)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
describe "#message" do
it "mentions exclusive" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.message.should contain("exclusive")
end
it "does not mention inclusive" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.message.should_not contain("inclusive")
end
it "contains the original label" do
range = 1..10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).exclusive
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
end
describe "#negated_message" do
it "mentions exclusive" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain("exclusive")
end
it "does not mention inclusive" do
range = 1..10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.negated_message.should_not contain("inclusive")
end
it "contains the original label" do
range = 1..10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).exclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
context "initially exclusive" do
it "is true for lower-bound" do
lower = 3
upper = 9
value = lower
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for lower-bound minus 1" do
lower = 3
upper = 9
value = lower - 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is true for mid-range" do
lower = 3
upper = 9
value = 5
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
it "is false for upper-bound" do
lower = 3
upper = 9
value = upper
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
it "is false for upper-bound plus 1" do
lower = 3
upper = 9
value = upper + 1
range = Range.new(lower, upper, exclusive: true)
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
describe "#message" do
it "mentions exclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.message.should contain("exclusive")
end
it "contains the original label" do
range = 1...10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).exclusive
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
end
describe "#negated_message" do
it "mentions exclusive" do
range = 1...10
value = 5
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range).exclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain("exclusive")
end
it "contains the original label" do
range = 1...10
value = 5
label = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RangeMatcher.new(range, label).exclusive
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
end
end
end
end

View file

@ -1,152 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::ReferenceMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with the same instance" do
it "is true" do
# Box is used because it is a reference type and doesn't override the == method.
ref = Box.new([] of Int32)
partial = new_partial(ref)
matcher = Spectator::Matchers::ReferenceMatcher.new(ref)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with different instances" do
context "with same contents" do
it "is false" do
array1 = [1, 2, 3]
array2 = [1, 2, 3]
partial = new_partial(array1)
matcher = Spectator::Matchers::ReferenceMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a duplicated instance" do
it "is false" do
array1 = [1, 2, 3]
array2 = array1.dup
partial = new_partial(array1)
matcher = Spectator::Matchers::ReferenceMatcher.new(array2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with the same type" do
it "is false" do
obj1 = "foo"
obj2 = "bar"
partial = new_partial(obj1)
matcher = Spectator::Matchers::ReferenceMatcher.new(obj2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a different type" do
it "is false" do
obj1 = "foobar"
obj2 = [1, 2, 3]
partial = new_partial(obj1)
matcher = Spectator::Matchers::ReferenceMatcher.new(obj2)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
context "expected" do
it "is the expected value" do
actual = "foobar"
expected = /foo/
partial = new_partial(actual)
matcher = Spectator::Matchers::ReferenceMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(expected)
end
end
context "actual" do
it "is the actual value" do
actual = "foobar"
expected = /foo/
partial = new_partial(actual)
matcher = Spectator::Matchers::ReferenceMatcher.new(expected)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(actual)
end
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::ReferenceMatcher.new(value)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::ReferenceMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
obj1 = "foo"
obj2 = "bar"
partial = new_partial(obj1)
matcher = Spectator::Matchers::ReferenceMatcher.new(obj2)
match_data = matcher.match(partial)
match_data.message.should contain(obj2.to_s)
end
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::ReferenceMatcher.new(value)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::ReferenceMatcher.new(value, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
obj1 = "foo"
obj2 = "bar"
partial = new_partial(obj1)
matcher = Spectator::Matchers::ReferenceMatcher.new(obj2)
match_data = matcher.match(partial)
match_data.negated_message.should contain(obj2.to_s)
end
end
end
end
end
end

View file

@ -1,123 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::RespondMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "one method" do
context "with a responding method" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "against a non-responding method" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(downcase: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "multiple methods" do
context "with one responding method" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with all responding methods" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, to_a: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with no responding methods" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(downcase: Nil, upcase: Nil)).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
it "contains a key for each expected method" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data_has_key?(match_data.values, :"responds to #size").should be_true
match_data_has_key?(match_data.values, :"responds to #downcase").should be_true
end
it "has the actual values" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :"responds to #size")[:value].should be_true
match_data_value_sans_prefix(match_data.values, :"responds to #downcase")[:value].should be_false
end
end
describe "#message" do
it "contains the actual label" do
value = "foobar"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the method names" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain("#size")
match_data.message.should contain("#downcase")
end
end
describe "#negated_message" do
it "contains the actual label" do
value = "foobar"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the method names" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::RespondMatcher(NamedTuple(size: Nil, downcase: Nil)).new
match_data = matcher.match(partial)
match_data.message.should contain("#size")
match_data.negated_message.should contain("#downcase")
end
end
end
end
end

View file

@ -1,393 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::StartWithMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with a String" do
context "against a matching string" do
it "is true" do
value = "foobar"
start = "foo"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at start" do
it "is false" do
value = "foobar"
start = "bar"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a different string" do
it "is false" do
value = "foobar"
start = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching character" do
it "is true" do
value = "foobar"
start = 'f'
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at start" do
it "is false" do
value = "foobar"
start = 'b'
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a different character" do
it "is false" do
value = "foobar"
start = 'z'
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching regex" do
it "is true" do
value = "FOOBAR"
start = /foo/i
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at start" do
it "is false" do
value = "FOOBAR"
start = /bar/i
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a non-matching regex" do
it "is false" do
value = "FOOBAR"
start = /baz/i
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "with an Enumberable" do
context "against an equal value" do
it "is true" do
array = %i[a b c]
start = :a
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at start" do
it "is false" do
array = %i[a b c]
start = :b
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a different value" do
it "is false" do
array = %i[a b c]
start = :z
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against matching element type" do
it "is true" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(Symbol)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at start" do
it "is false" do
array = [1, 2, 3, :a, :b, :c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(Symbol)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against different element type" do
it "is false" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(Int32)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "against a matching regex" do
it "is true" do
array = %w[FOO BAR BAZ]
start = /foo/i
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
context "not at start" do
it "is false" do
array = %w[FOO BAR BAZ]
start = /bar/i
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
context "against a non-matching regex" do
it "is false" do
array = %w[FOO BAR BAZ]
start = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
end
describe "#values" do
context "with a String" do
context "expected" do
it "is the expected value" do
value = "FOOBAR"
first = /baz/i
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(first)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(first)
end
end
context "actual" do
it "is the actual value" do
value = "FOOBAR"
first = /baz/i
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(first)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
end
context "with an Indexable" do
context "expected" do
it "is the expected value" do
array = %w[FOO BAR BAZ]
first = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(first)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(first)
end
end
context "actual" do
it "is the first element" do
array = %w[FOO BAR BAZ]
first = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(first)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(array.first)
end
end
context "list" do
it "is the full actual list" do
array = %w[FOO BAR BAZ]
first = /qux/i
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(first)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :list)[:value].should eq(array)
end
end
end
end
describe "#message" do
context "with a String" do
it "mentions #starts_with?" do
value = "foobar"
start = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.message.should contain("#starts_with?")
end
end
context "with an Enumerable" do
it "mentions ===" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(array.first)
match_data = matcher.match(partial)
match_data.message.should contain("===")
end
it "mentions first" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(array.first)
match_data = matcher.match(partial)
match_data.message.should contain("first")
end
end
it "contains the actual label" do
value = "foobar"
start = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
start = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start, label)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
start = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.message.should contain(start)
end
end
end
describe "#negated_message" do
context "with a String" do
it "mentions #starts_with?" do
value = "foobar"
start = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.negated_message.should contain("#starts_with?")
end
end
context "with an Enumerable" do
it "mentions ===" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(array.first)
match_data = matcher.match(partial)
match_data.negated_message.should contain("===")
end
it "mentions first" do
array = %i[a b c]
partial = new_partial(array)
matcher = Spectator::Matchers::StartWithMatcher.new(array.first)
match_data = matcher.match(partial)
match_data.negated_message.should contain("first")
end
end
it "contains the actual label" do
value = "foobar"
start = "baz"
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected label" do
value = "foobar"
start = "baz"
label = "everything"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start, label)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
context "when expected label is omitted" do
it "contains stringified form of expected value" do
value = "foobar"
start = "baz"
partial = new_partial(value)
matcher = Spectator::Matchers::StartWithMatcher.new(start)
match_data = matcher.match(partial)
match_data.negated_message.should contain(start)
end
end
end
end
end
end

View file

@ -1,374 +0,0 @@
require "../spec_helper"
# This is a terrible hack,
# but I don't want to expose `ValueMatcher#expected` publicly
# just for this spec.
module Spectator::Matchers
struct ValueMatcher(ExpectedType)
def expected_value
expected
end
end
end
def be_comparison
Spectator::Matchers::TruthyMatcher.new(true)
end
describe Spectator::Matchers::TruthyMatcher do
describe "#match" do
context "returned MatchData" do
context "truthy" do
describe "#matched?" do
context "with a truthy value" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with false" do
it "is false" do
value = false
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with nil" do
it "is false" do
value = nil
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
end
describe "#values" do
context "expected" do
it "contains the definition of falsey" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should match(/false or nil/i)
end
it "is prefixed with \"Not\"" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should start_with(/not/i)
end
end
context "actual" do
it "is the actual value" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
context "truthy" do
context "when the actual value is truthy" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :truthy)[:value].should be_true
end
end
context "when the actual value is false" do
it "is false" do
value = false
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :truthy)[:value].should be_false
end
end
context "when the actual value is nil" do
it "is false" do
value = nil
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :truthy)[:value].should be_false
end
end
end
end
describe "#message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the \"truthy\"" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.message.should contain("truthy")
end
end
describe "#negated_message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the \"truthy\"" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(true)
match_data = matcher.match(partial)
match_data.negated_message.should contain("truthy")
end
end
end
context "falsey" do
describe "#matched?" do
context "with a truthy value" do
it "is false" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with false" do
it "is true" do
value = false
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with nil" do
it "is true" do
value = nil
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
describe "#values" do
context "expected" do
it "contains the definition of falsey" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should match(/false or nil/i)
end
it "is not prefixed with \"Not\"" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:to_s].should_not start_with(/not/i)
end
end
context "actual" do
it "is the actual value" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(value)
end
end
context "truthy" do
context "when the actual value is truthy" do
it "is true" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :truthy)[:value].should be_true
end
end
context "when the actual value is false" do
it "is false" do
value = false
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :truthy)[:value].should be_false
end
end
context "when the actual value is nil" do
it "is false" do
value = nil
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :truthy)[:value].should be_false
end
end
end
end
describe "#message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the \"falsey\"" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.message.should contain("falsey")
end
end
describe "#negated_message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the \"falsey\"" do
value = 42
partial = new_partial(value)
matcher = Spectator::Matchers::TruthyMatcher.new(false)
match_data = matcher.match(partial)
match_data.negated_message.should contain("falsey")
end
end
end
end
end
describe "#<" do
it "returns a LessThanMatcher" do
value = 0
matcher = be_comparison < value
matcher.should be_a(Spectator::Matchers::LessThanMatcher(typeof(value)))
end
it "passes along the expected value" do
value = 42
matcher = be_comparison < value
matcher.expected_value.should eq(value)
end
end
describe "#<=" do
it "returns a LessThanEqualMatcher" do
value = 0
matcher = be_comparison <= value
matcher.should be_a(Spectator::Matchers::LessThanEqualMatcher(typeof(value)))
end
it "passes along the expected value" do
value = 42
matcher = be_comparison <= value
matcher.expected_value.should eq(value)
end
end
describe "#>" do
it "returns a GreaterThanMatcher" do
value = 0
matcher = be_comparison > value
matcher.should be_a(Spectator::Matchers::GreaterThanMatcher(typeof(value)))
end
it "passes along the expected value" do
value = 42
matcher = be_comparison > value
matcher.expected_value.should eq(value)
end
end
describe "#>=" do
it "returns a GreaterThanEqualMatcher" do
value = 0
matcher = be_comparison >= value
matcher.should be_a(Spectator::Matchers::GreaterThanEqualMatcher(typeof(value)))
end
it "passes along the expected value" do
value = 42
matcher = be_comparison >= value
matcher.expected_value.should eq(value)
end
end
describe "#==" do
it "returns an EqualityMatcher" do
value = 0
matcher = be_comparison == value
matcher.should be_a(Spectator::Matchers::EqualityMatcher(typeof(value)))
end
it "passes along the expected value" do
value = 42
matcher = be_comparison == value
matcher.expected_value.should eq(value)
end
end
describe "#!=" do
it "returns an InequalityMatcher" do
value = 0
matcher = be_comparison != value
matcher.should be_a(Spectator::Matchers::InequalityMatcher(typeof(value)))
end
it "passes along the expected value" do
value = 42
matcher = be_comparison != value
matcher.expected_value.should eq(value)
end
end
end

View file

@ -1,117 +0,0 @@
require "../spec_helper"
describe Spectator::Matchers::TypeMatcher do
describe "#match" do
context "returned MatchData" do
describe "#matched?" do
context "with the same type" do
it "is true" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a different type" do
it "is false" do
value = "foobar"
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(Int32).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a parent type" do
it "is true" do
value = IO::Memory.new
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(IO).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
context "with a child type" do
it "is false" do
value = Exception.new("foobar")
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(ArgumentError).new
match_data = matcher.match(partial)
match_data.matched?.should be_false
end
end
context "with a mix-in" do
it "is true" do
value = %i[a b c]
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(Enumerable(Symbol)).new
match_data = matcher.match(partial)
match_data.matched?.should be_true
end
end
end
describe "#values" do
context "expected" do
it "is the expected type name" do
value = %i[a b c]
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(String)
end
end
context "actual" do
it "is the actual type name" do
value = %i[a b c]
partial = new_partial(value)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data_value_sans_prefix(match_data.values, :actual)[:value].should eq(typeof(value))
end
end
end
describe "#message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data.message.should contain(label)
end
it "contains the expected type" do
partial = new_partial(42)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data.message.should contain("String")
end
end
describe "#negated_message" do
it "contains the actual label" do
value = 42
label = "everything"
partial = new_partial(value, label)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data.negated_message.should contain(label)
end
it "contains the expected type" do
partial = new_partial(42)
matcher = Spectator::Matchers::TypeMatcher(String).new
match_data = matcher.match(partial)
match_data.negated_message.should contain("String")
end
end
end
end
end

View file

@ -1,3 +1,7 @@
require "../expectations/expectation_partial"
require "../source"
require "../test_block"
require "../test_value"
require "./matcher_dsl" require "./matcher_dsl"
module Spectator::DSL module Spectator::DSL
@ -19,7 +23,9 @@ module Spectator::DSL
# Where the actual value is returned by the system-under-test, # Where the actual value is returned by the system-under-test,
# and the expected value is what the actual value should be to satisfy the condition. # and the expected value is what the actual value should be to satisfy the condition.
macro expect(actual, _source_file = __FILE__, _source_line = __LINE__) macro expect(actual, _source_file = __FILE__, _source_line = __LINE__)
::Spectator::Expectations::ValueExpectationPartial.new({{actual}}, {{actual.stringify}}, {{_source_file}}, {{_source_line}}) test_value = ::Spectator::TestValue.new({{actual}}, {{actual.stringify}})
source = ::Spectator::Source.new({{_source_file}}, {{_source_line}})
::Spectator::Expectations::ExpectationPartial.new(test_value, source)
end end
# Starts an expectation on a block of code. # Starts an expectation on a block of code.
@ -49,9 +55,6 @@ module Spectator::DSL
{% raise "Argument or block must be provided to expect" %} {% raise "Argument or block must be provided to expect" %}
{% end %} {% end %}
# Create a proc to capture the block.
%proc = ->({{block.args.splat}}) {{block}}
# Check if the short-hand method syntax is used. # Check if the short-hand method syntax is used.
# This is a hack, since macros don't get this as a "literal" or something similar. # This is a hack, since macros don't get this as a "literal" or something similar.
# The Crystal compiler will translate: # The Crystal compiler will translate:
@ -67,13 +70,19 @@ module Spectator::DSL
# Extract the method name to make it clear to the user what is tested. # Extract the method name to make it clear to the user what is tested.
# The raw block can't be used because it's not clear to the user. # The raw block can't be used because it's not clear to the user.
{% method_name = block.body.id.split('.')[1..-1].join('.') %} {% method_name = block.body.id.split('.')[1..-1].join('.') %}
%partial = %proc.partial(subject) %proc = ->{ subject.{{method_name.id}} }
::Spectator::Expectations::BlockExpectationPartial.new(%partial, {{"#" + method_name}}, {{_source_file}}, {{_source_line}}) %test_block = ::Spectator::TestBlock.create(%proc, {{"#" + method_name}})
{% else %} {% elsif block.args.empty? %}
# In this case, it looks like the short-hand method syntax wasn't used. # In this case, it looks like the short-hand method syntax wasn't used.
# Just drop in the proc as-is. # Capture the block as a proc and pass along.
::Spectator::Expectations::BlockExpectationPartial.new(%proc, "`" + {{block.body.stringify}} + "`", {{_source_file}}, {{_source_line}}) %proc = ->{{block}}
%test_block = ::Spectator::TestBlock.create(%proc, {{"`" + block.body.stringify + "`"}})
{% else %}
{% raise "Unexpected block arguments in expect call" %}
{% end %} {% end %}
%source = ::Spectator::Source.new({{_source_file}}, {{_source_line}})
::Spectator::Expectations::ExpectationPartial.new(%test_block, %source)
end end
# Starts an expectation. # Starts an expectation.

View file

@ -1,4 +1,6 @@
require "../matchers" require "../matchers"
require "../test_block"
require "../test_value"
module Spectator::DSL module Spectator::DSL
# Methods for defining matchers for expectations. # Methods for defining matchers for expectations.
@ -12,7 +14,8 @@ module Spectator::DSL
# expect(1 + 2).to eq(3) # expect(1 + 2).to eq(3)
# ``` # ```
macro eq(expected) macro eq(expected)
::Spectator::Matchers::EqualityMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::EqualityMatcher.new(%test_value)
end end
# Indicates that some value should not equal another. # Indicates that some value should not equal another.
@ -24,7 +27,8 @@ module Spectator::DSL
# expect(1 + 2).to ne(5) # expect(1 + 2).to ne(5)
# ``` # ```
macro ne(expected) macro ne(expected)
::Spectator::Matchers::InequalityMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::InequalityMatcher.new(%test_value)
end end
# Indicates that some value when compared to another satisfies an operator. # Indicates that some value when compared to another satisfies an operator.
@ -44,7 +48,7 @@ module Spectator::DSL
# expect("foo").to be_truthy # expect("foo").to be_truthy
# ``` # ```
macro be macro be
::Spectator::Matchers::TruthyMatcher.new(true) ::Spectator::Matchers::TruthyMatcher.new
end end
# Indicates that some object should be the same as another. # Indicates that some object should be the same as another.
@ -58,7 +62,8 @@ module Spectator::DSL
# expect(obj.dup).to_not be(obj) # expect(obj.dup).to_not be(obj)
# ``` # ```
macro be(expected) macro be(expected)
::Spectator::Matchers::ReferenceMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::ReferenceMatcher.new(%test_value)
end end
# Indicates that some value should be of a specified type. # Indicates that some value should be of a specified type.
@ -115,7 +120,8 @@ module Spectator::DSL
# expect(3 - 1).to be_lt(3) # expect(3 - 1).to be_lt(3)
# ``` # ```
macro be_lt(expected) macro be_lt(expected)
::Spectator::Matchers::LessThanMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::LessThanMatcher.new(%test_value)
end end
# Indicates that some value should be less than or equal to another. # Indicates that some value should be less than or equal to another.
@ -127,7 +133,8 @@ module Spectator::DSL
# expect(3 - 1).to be_le(3) # expect(3 - 1).to be_le(3)
# ``` # ```
macro be_le(expected) macro be_le(expected)
::Spectator::Matchers::LessThanEqualMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::LessThanEqualMatcher.new(%test_value)
end end
# Indicates that some value should be greater than another. # Indicates that some value should be greater than another.
@ -139,7 +146,8 @@ module Spectator::DSL
# expect(3 + 1).to be_gt(3) # expect(3 + 1).to be_gt(3)
# ``` # ```
macro be_gt(expected) macro be_gt(expected)
::Spectator::Matchers::GreaterThanMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::GreaterThanMatcher.new(%test_value)
end end
# Indicates that some value should be greater than or equal to another. # Indicates that some value should be greater than or equal to another.
@ -151,7 +159,8 @@ module Spectator::DSL
# expect(3 + 1).to be_ge(3) # expect(3 + 1).to be_ge(3)
# ``` # ```
macro be_ge(expected) macro be_ge(expected)
::Spectator::Matchers::GreaterThanEqualMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::GreaterThanEqualMatcher.new(%test_value)
end end
# Indicates that some value should match another. # Indicates that some value should match another.
@ -168,7 +177,8 @@ module Spectator::DSL
# expect({:foo, 5}).to match({Symbol, Int32}) # expect({:foo, 5}).to match({Symbol, Int32})
# ``` # ```
macro match(expected) macro match(expected)
::Spectator::Matchers::CaseMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::CaseMatcher.new(%test_value)
end end
# Indicates that some value should be true. # Indicates that some value should be true.
@ -202,7 +212,7 @@ module Spectator::DSL
# expect(true).to be_truthy # expect(true).to be_truthy
# ``` # ```
macro be_truthy macro be_truthy
::Spectator::Matchers::TruthyMatcher.new(true) ::Spectator::Matchers::TruthyMatcher.new
end end
# Indicates that some value should be falsey. # Indicates that some value should be falsey.
@ -258,7 +268,8 @@ module Spectator::DSL
# NOTE: Do not attempt to mix the two use cases. # NOTE: Do not attempt to mix the two use cases.
# It likely won't work and will result in a compilation error. # It likely won't work and will result in a compilation error.
macro be_within(expected) macro be_within(expected)
::Spectator::Matchers::CollectionMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::CollectionMatcher.new(%test_value)
end end
# Indicates that some value should be between a lower and upper-bound. # Indicates that some value should be between a lower and upper-bound.
@ -278,10 +289,10 @@ module Spectator::DSL
# expect(100).to be_between(97, 101).exclusive # 97, 98, 99, or 100 (not 101) # expect(100).to be_between(97, 101).exclusive # 97, 98, 99, or 100 (not 101)
# ``` # ```
macro be_between(min, max) macro be_between(min, max)
:Spectator::Matchers::RangeMatcher.new( %range = Range.new({{min}}, {{max}}))
Range.new({{min}}, {{max}}), %label = [{{min.stringify}}, {{max.stringify}}].join(" to ")
[{{min.stringify}}, {{max.stringify}}].join(" to ") %test_value = ::Spectator::TestValue.new(%range, %label)
) :Spectator::Matchers::RangeMatcher.new(%test_value)
end end
# Indicates that some value should be within a delta of an expected value. # Indicates that some value should be within a delta of an expected value.
@ -339,7 +350,8 @@ module Spectator::DSL
# expect(%w[foo bar]).to start_with(/foo/) # expect(%w[foo bar]).to start_with(/foo/)
# ``` # ```
macro start_with(expected) macro start_with(expected)
::Spectator::Matchers::StartWithMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::StartWithMatcher.new(%test_value)
end end
# Indicates that some value or set should end with another value. # Indicates that some value or set should end with another value.
@ -361,7 +373,8 @@ module Spectator::DSL
# expect(%w[foo bar]).to end_with(/bar/) # expect(%w[foo bar]).to end_with(/bar/)
# ``` # ```
macro end_with(expected) macro end_with(expected)
::Spectator::Matchers::EndWithMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::EndWithMatcher.new(%test_value)
end end
# Indicates that some value or set should contain another value. # Indicates that some value or set should contain another value.
@ -384,7 +397,8 @@ module Spectator::DSL
# expect(%i[a b c]).to contain(:a, :b) # expect(%i[a b c]).to contain(:a, :b)
# ``` # ```
macro contain(*expected) macro contain(*expected)
::Spectator::Matchers::ContainMatcher.new({{expected}}, {{expected.splat.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}})
::Spectator::Matchers::ContainMatcher.new(%test_value)
end end
# Indicates that some value or set should contain another value. # Indicates that some value or set should contain another value.
@ -413,7 +427,8 @@ module Spectator::DSL
# expect(%w[FOO BAR BAZ]).to have(/foo/i, String) # expect(%w[FOO BAR BAZ]).to have(/foo/i, String)
# ``` # ```
macro have(*expected) macro have(*expected)
::Spectator::Matchers::HaveMatcher.new({{expected}}, {{expected.splat.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.splat.stringify}})
::Spectator::Matchers::HaveMatcher.new(%test_value)
end 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.
@ -425,7 +440,8 @@ module Spectator::DSL
# expect({"lucky" => 7}).to have_key("lucky") # expect({"lucky" => 7}).to have_key("lucky")
# ``` # ```
macro have_key(expected) macro have_key(expected)
::Spectator::Matchers::HaveKeyMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::HaveKeyMatcher.new(%test_value)
end end
# ditto # ditto
@ -442,7 +458,8 @@ module Spectator::DSL
# expect({"lucky" => 7}).to have_value(7) # expect({"lucky" => 7}).to have_value(7)
# ``` # ```
macro have_value(expected) macro have_value(expected)
::Spectator::Matchers::HaveValueMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::HaveValueMatcher.new(%test_value)
end end
# ditto # ditto
@ -457,7 +474,8 @@ module Spectator::DSL
# expect([1, 2, 3]).to contain_exactly(1, 2, 3) # expect([1, 2, 3]).to contain_exactly(1, 2, 3)
# ``` # ```
macro contain_exactly(*expected) macro contain_exactly(*expected)
::Spectator::Matchers::ArrayMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::ArrayMatcher.new(%test_value)
end end
# Indicates that some set should contain the same values in exact order as another set. # Indicates that some set should contain the same values in exact order as another set.
@ -467,7 +485,30 @@ module Spectator::DSL
# expect([1, 2, 3]).to match_array([1, 2, 3]) # expect([1, 2, 3]).to match_array([1, 2, 3])
# ``` # ```
macro match_array(expected) macro match_array(expected)
::Spectator::Matchers::ArrayMatcher.new({{expected}}, {{expected.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::ArrayMatcher.new(%test_value)
end
# Indicates that some set should have a specified size.
#
# Example:
# ```
# expect([1, 2, 3]).to have_size(3)
# ```
macro have_size(expected)
%test_value = ::Spectator::TestValue.new({{expected}}, {{expected.stringify}})
::Spectator::Matchers::SizeMatcher.new(%test_value)
end
# Indicates that some set should have the same size (number of elements) as another set.
#
# Example:
# ```
# expect([1, 2, 3]).to have_size_of(%i[x y z])
# ```
macro have_size_of(expected)
%test_value = ::Spectator::TestValue.new(({{expected}}), {{expected.stringify}})
::Spectator::Matchers::SizeOfMatcher.new(%test_value)
end end
# Indicates that some value should have a set of attributes matching some conditions. # Indicates that some value should have a set of attributes matching some conditions.
@ -481,7 +522,90 @@ module Spectator::DSL
# expect(%i[a b c]).to have_attributes(size: 1..5, first: Symbol) # expect(%i[a b c]).to have_attributes(size: 1..5, first: Symbol)
# ``` # ```
macro have_attributes(**expected) macro have_attributes(**expected)
::Spectator::Matchers::AttributesMatcher.new({{expected}}, {{expected.double_splat.stringify}}) %test_value = ::Spectator::TestValue.new({{expected}}, {{expected.double_splat.stringify}})
::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:
# ```
# 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)
# ```
#
# The block short-hand syntax can be used here.
# It will reference the current subject.
#
# ```
# expect { subject << :foo }.to change(&.size).by(1)
# ```
macro change(&expression)
{% if expression.is_a?(Nop) %}
{% raise "Block must be provided to change matcher" %}
{% end %}
# Check if the short-hand method syntax is used.
# This is a hack, since macros don't get this as a "literal" or something similar.
# The Crystal compiler will translate:
# ```
# &.foo
# ```
# to:
# ```
# { |__arg0| __arg0.foo }
# ```
# The hack used here is to check if it looks like a compiler-generated block.
{% if expression.args == ["__arg0".id] && expression.body.is_a?(Call) && expression.body.id =~ /^__arg0\./ %}
# Extract the method name to make it clear to the user what is tested.
# The raw block can't be used because it's not clear to the user.
{% method_name = expression.body.id.split('.')[1..-1].join('.') %}
%proc = ->{ subject.{{method_name.id}} }
%test_block = ::Spectator::TestBlock.create(%proc, {{"#" + method_name}})
{% elsif expression.args.empty? %}
# In this case, it looks like the short-hand method syntax wasn't used.
# Capture the block as a proc and pass along.
%proc = ->{{expression}}
%test_block = ::Spectator::TestBlock.create(%proc, {{"`" + expression.body.stringify + "`"}})
{% else %}
{% raise "Unexpected block arguments in change matcher" %}
{% end %}
::Spectator::Matchers::ChangeMatcher.new(%test_block)
end end
# Indicates that some block should raise an error. # Indicates that some block should raise an error.
@ -561,7 +685,7 @@ module Spectator::DSL
end end
# Used to create predicate matchers. # 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. # 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 '?'. # This can be used to simply check a predicate method that ends in '?'.
@ -570,14 +694,38 @@ module Spectator::DSL
# expect("foobar").to be_ascii_only # expect("foobar").to be_ascii_only
# # Is equivalent to: # # Is equivalent to:
# expect("foobar".ascii_only?).to be_true # 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) macro method_missing(call)
{% if call.name.starts_with?("be_") %} {% if call.name.starts_with?("be_") %}
{% method_name = call.name[3..-1] %} # Remove be_ prefix. # Remove `be_` prefix.
::Spectator::Matchers::PredicateMatcher(NamedTuple({{method_name}}: Nil)).new {% 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 %} {% else %}
{% raise "Undefined local variable or method '#{call}'" %} {% raise "Undefined local variable or method '#{call}'" %}
{% end %} {% 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 %}
test_value = ::Spectator::TestValue.new(descriptor, label.to_s)
::Spectator::Matchers::{{matcher.id}}.new(test_value)
end end
end end
end end

View file

@ -789,7 +789,53 @@ module Spectator::DSL
# The name can be used in examples to retrieve the value (basically a method). # The name can be used in examples to retrieve the value (basically a method).
# This can be used to define a value once and reuse it in multiple examples. # This can be used to define a value once and reuse it in multiple examples.
# #
# This macro expects a name and a block. # There are two variants - assignment and block.
# Both must be given a name.
#
# For the assignment variant:
# ```
# let string = "foobar"
#
# it "isn't empty" do
# expect(string.empty?).to be_false
# end
#
# it "is six characters" do
# expect(string.size).to eq(6)
# end
# ```
#
# The value is evaluated and stored immediately.
# This is different from other `#let` variants that lazily-evaluate.
#
# ```
# let current_time = Time.utc
# let(lazy_time) { Time.utc }
#
# it "lazy evaluates" do
# sleep 5
# expect(lazy_time).to_not eq(now)
# end
# ```
#
# However, the value is not reused across tests.
# Each test will have its own copy.
#
# ```
# let array = [0, 1, 2]
#
# it "modifies the array" do
# array[0] = 42
# expect(array).to eq([42, 1, 2])
# end
#
# it "doesn't carry across tests" do
# array[1] = 777
# expect(array).to eq([0, 777, 2])
# end
# ```
#
# The block variant expects a name and a block.
# The name can be a symbol or a literal - same as `Object#getter`. # The name can be a symbol or a literal - same as `Object#getter`.
# The block should return the value. # The block should return the value.
# #
@ -838,6 +884,16 @@ module Spectator::DSL
# end # end
# ``` # ```
macro let(name, &block) macro let(name, &block)
{% if block.is_a?(Nop) %}
# Assignment variant.
@%value = {{name.value}}
def {{name.target}}
@%value
end
{% else %}
# Block variant.
# Create a block that returns the value. # Create a block that returns the value.
let!(%value) {{block}} let!(%value) {{block}}
@ -864,6 +920,7 @@ module Spectator::DSL
end end
end end
end end
{% end %}
end end
# The noisier sibling to `#let`. # The noisier sibling to `#let`.

View file

@ -9,7 +9,7 @@ module Spectator
# Creates the exception. # Creates the exception.
# The exception string is generated from the expecation message. # The exception string is generated from the expecation message.
def initialize(@expectation) def initialize(@expectation)
super(@expectation.actual_message) super(@expectation.failure_message)
end end
end end
end end

View file

@ -1,25 +0,0 @@
require "./expectation_partial"
module Spectator::Expectations
# Expectation partial variation that operates on a block.
struct BlockExpectationPartial(ReturnType) < ExpectationPartial
# Actual value produced by calling the block.
def actual
@block.call
end
# Creates the expectation partial.
# The label should be a string representation of the block.
# The block is stored for later use.
def initialize(@block : Proc(ReturnType), label, source_file, source_line)
super(label, source_file, source_line)
end
# Creates the expectation partial.
# The label is generated by calling to_s on the block.
# The block is stored for later use.
def initialize(@block : Proc(ReturnType), source_file, source_line)
super("<Proc>", source_file, source_line)
end
end
end

View file

@ -1,57 +1,67 @@
require "../matchers/failed_match_data"
require "../matchers/match_data"
require "../source"
module Spectator::Expectations module Spectator::Expectations
# Ties together a partial, matcher, and their outcome. # Result of evaluating a matcher on an expectation partial.
class Expectation struct Expectation
# Populates the base portiion of the expectation with values. # Location where this expectation was defined.
# The *negated* flag should be true if the expectation is inverted. getter source : Source
# The *match_data* is the value returned by `Spectator::Matcher#match`
# when the expectation was evaluated. # Creates the expectation.
# The *negated* flag and `MatchData#matched?` flag def initialize(@match_data : Matchers::MatchData, @source : Source)
# are mutually-exclusive in this context.
def initialize(@match_data : Spectator::Matchers::MatchData, @negated : Bool)
end end
# Indicates whether the expectation was satisifed. # Indicates whether the matcher was satisified.
# This is true if:
# - The matcher was satisified and the expectation is not negated.
# - The matcher wasn't satisfied and the expectation is negated.
def satisfied? def satisfied?
@match_data.matched? ^ @negated @match_data.matched?
end end
# Information about the match. # Indicates that the expectation was not satisified.
# Returned value and type will something that has key-value pairs (like a `NamedTuple`). def failure?
!satisfied?
end
# Description of why the match failed.
# If nil, then the match was successful.
def failure_message?
@match_data.as?(Matchers::FailedMatchData).try(&.failure_message)
end
# Description of why the match failed.
def failure_message
failure_message?.not_nil!
end
# Additional information about the match, useful for debug.
# If nil, then the match was successful.
def values?
@match_data.as?(Matchers::FailedMatchData).try(&.values)
end
# Additional information about the match, useful for debug.
def values def values
@match_data.values.tap do |labeled_values| values?.not_nil!
if @negated
labeled_values.each do |labeled_value|
value = labeled_value.value
value.negate
end
end
end
end
# Text that indicates the condition that must be met for the expectation to be satisifed.
def expected_message
@negated ? @match_data.negated_message : @match_data.message
end
# Text that indicates what the outcome was.
def actual_message
@match_data.matched? ? @match_data.message : @match_data.negated_message
end end
# Creates the JSON representation of the expectation. # Creates the JSON representation of the expectation.
def to_json(json : ::JSON::Builder) def to_json(json : ::JSON::Builder)
json.object do json.object do
json.field("source") { @source.to_json(json) }
json.field("satisfied", satisfied?) json.field("satisfied", satisfied?)
json.field("expected", expected_message) if (failed = @match_data.as?(Matchers::FailedMatchData))
json.field("actual", actual_message) failed_to_json(failed, json)
end
end
end
# Adds failure information to a JSON structure.
private def failed_to_json(failed : Matchers::FailedMatchData, json : ::JSON::Builder)
json.field("failure", failed.failure_message)
json.field("values") do json.field("values") do
json.object do json.object do
values.each do |labeled_value| failed.values.each do |pair|
json.field(labeled_value.label, labeled_value.value.to_s) json.field(pair.first, pair.last)
end
end end
end end
end end

View file

@ -1,39 +1,34 @@
require "../matchers/match_data"
require "../source"
require "../test_expression"
module Spectator::Expectations module Spectator::Expectations
# Base class for all expectation partials. # Stores part of an expectation (obviously).
# An "expectation partial" stores part of an expectation (obviously). # The part of the expectation this type covers is the actual value and source.
# The part of the expectation this class covers is the actual value.
# This can also cover a block's behavior. # This can also cover a block's behavior.
# Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call. struct ExpectationPartial(T)
abstract struct ExpectationPartial # The actual value being tested.
# User-friendly string displayed for the actual expression being tested. # This also contains its label.
# For instance, in the expectation: getter actual : TestExpression(T)
# ```
# expect(foo).to eq(bar)
# ```
# This property will be "foo".
# It will be the literal string "foo",
# and not the actual value of the foo.
getter label : String
# Source file the expectation originated from. # Location where this expectation was defined.
getter source_file : String getter source : Source
# Line number in the source file the expectation originated from. # Creates the partial.
getter source_line : Int32 def initialize(@actual : TestExpression(T), @source : Source)
# Creates the base of the partial.
private def initialize(@label, @source_file, @source_line)
end end
# Asserts that some criteria defined by the matcher is satisfied. # Asserts that some criteria defined by the matcher is satisfied.
def to(matcher) : Nil def to(matcher) : Nil
report(eval(matcher)) match_data = matcher.match(@actual)
report(match_data)
end end
# Asserts that some criteria defined by the matcher is not satisfied. # Asserts that some criteria defined by the matcher is not satisfied.
# This is effectively the opposite of `#to`. # This is effectively the opposite of `#to`.
def to_not(matcher) : Nil def to_not(matcher) : Nil
report(eval(matcher, true)) match_data = matcher.negated_match(@actual)
report(match_data)
end end
# ditto # ditto
@ -42,14 +37,9 @@ module Spectator::Expectations
to_not(matcher) to_not(matcher)
end end
# Evaluates the expectation and returns it.
private def eval(matcher, negated = false)
match_data = matcher.match(self)
Expectation.new(match_data, negated)
end
# Reports an expectation to the current harness. # Reports an expectation to the current harness.
private def report(expectation : Expectation) private def report(match_data : Matchers::MatchData)
expectation = Expectation.new(match_data, @source)
Internals::Harness.current.report_expectation(expectation) Internals::Harness.current.report_expectation(expectation)
end end
end end

View file

@ -1,24 +0,0 @@
require "./expectation_partial"
module Spectator::Expectations
# Expectation partial variation that operates on a value.
struct ValueExpectationPartial(ActualType) < ExpectationPartial
# Actual value produced by the test.
# This is the value passed to the `Spectator::DSL::ExampleDSL#expect` macro.
getter actual
# Creates the expectation partial.
# The label should be a string representation of the actual value.
# The actual value is stored for later use.
def initialize(@actual : ActualType, label, source_file, source_line)
super(label, source_file, source_line)
end
# Creates the expectation partial.
# The label is generated by calling `to_s` on the actual value.
# The actual value is stored for later use.
def initialize(@actual : ActualType, source_file, source_line)
super(@actual.to_s, source_file, source_line)
end
end
end

View file

@ -53,7 +53,7 @@ module Spectator::Formatting
# Produces a list of unsatisfied expectations and their values. # Produces a list of unsatisfied expectations and their values.
private def unsatisfied_expectations(indent) private def unsatisfied_expectations(indent)
@result.expectations.each_unsatisfied do |expectation| @result.expectations.each_unsatisfied do |expectation|
indent.line(Color.failure(LabeledText.new("Failure", expectation.actual_message))) indent.line(Color.failure(LabeledText.new("Failure", expectation.failure_message)))
indent.line indent.line
indent.increase do indent.increase do
matcher_values(indent, expectation) matcher_values(indent, expectation)

View file

@ -19,7 +19,7 @@ module Spectator::Formatting
private def content(xml) private def content(xml)
super super
@result.expectations.each_unsatisfied do |expectation| @result.expectations.each_unsatisfied do |expectation|
xml.element("failure", message: expectation.actual_message) do xml.element("failure", message: expectation.failure_message) do
expectation_values(expectation.values, xml) expectation_values(expectation.values, xml)
end end
end end
@ -28,8 +28,8 @@ module Spectator::Formatting
# Adds the expectation values to the failure block. # Adds the expectation values to the failure block.
private def expectation_values(labeled_values, xml) private def expectation_values(labeled_values, xml)
labeled_values.each do |entry| labeled_values.each do |entry|
label = entry.label label = entry.first
value = entry.value value = entry.last
xml.text("#{label}: #{value}\n") xml.text("#{label}: #{value}\n")
end end
end end

View file

@ -1,8 +1,8 @@
module Spectator::Formatting module Spectator::Formatting
# A single labeled value from the `Spectator::Matchers::MatchData#value` method. # A single labeled value from the `Spectator::Matchers::MatchData#value` method.
private struct MatchDataValuePair(T) private struct MatchDataValuePair
# Creates the pair formatter. # Creates the pair formatter.
def initialize(@key : Symbol, @value : T, @padding : Int32) def initialize(@key : Symbol, @value : String, @padding : Int32)
end end
# Appends the pair to the output. # Appends the pair to the output.
@ -10,7 +10,7 @@ module Spectator::Formatting
@padding.times { io << ' ' } @padding.times { io << ' ' }
io << @key io << @key
io << ": " io << ": "
@value.inspect(io) io << @value
end end
end end
end end

View file

@ -2,22 +2,22 @@ module Spectator::Formatting
# Produces a `MatchDataValuePair` for each key-value pair # Produces a `MatchDataValuePair` for each key-value pair
# from `Spectator::Matchers::MatchData#values`. # from `Spectator::Matchers::MatchData#values`.
private struct MatchDataValues private struct MatchDataValues
include Enumerable(MatchDataValuePair) include Enumerable(Tuple(Symbol, String))
@max_key_length : Int32 @max_key_length : Int32
# Creates the values mapper. # Creates the values mapper.
def initialize(@values : Array(Spectator::Matchers::MatchDataLabeledValue)) def initialize(@values : Array(Tuple(Symbol, String)))
@max_key_length = @values.map(&.label.to_s.size).max @max_key_length = @values.map(&.first.to_s.size).max
end end
# Yields pairs that can be printed to output. # Yields pairs that can be printed to output.
def each def each
@values.each do |labeled_value| @values.each do |labeled_value|
key = labeled_value.label key = labeled_value.first
key_length = key.to_s.size key_length = key.to_s.size
padding = @max_key_length - key_length padding = @max_key_length - key_length
yield MatchDataValuePair.new(key, labeled_value.value, padding) yield MatchDataValuePair.new(key, labeled_value.last, padding)
end end
end end
end end

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

@ -1,32 +0,0 @@
require "./match_data_value"
module Spectator::Matchers
# Selects a value based on whether the value is negated.
# This is used when a matcher is negated.
private class AlternativeMatchDataValue(T1, T2) < MatchDataValue
# Creates the wrapper.
def initialize(@value1 : T1, @value2 : T2)
@negated = false
end
# Negates (toggles) the value.
def negate
@negated = !@negated
end
# Returns the correct value based on the negated status.
def value
@negated ? @value2 : @value1
end
# Produces a stringified value.
def to_s(io)
io << value
end
# Produces a stringified value with additional information.
def inspect(io)
io << value
end
end
end

View file

@ -1,108 +1,113 @@
require "./value_matcher" require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
require "./unordered_array_matcher"
module Spectator::Matchers module Spectator::Matchers
# Matcher for checking that the contents of one array (or similar type) # Matcher for checking that the contents of one array (or similar type)
# has the exact same contents as another and in the same order. # has the exact same contents as another and in the same order.
struct ArrayMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct ArrayMatcher(ExpectedType) < Matcher
# Determines whether the matcher is satisfied with the partial given to it. # Expected value and label.
# `MatchData` is returned that contains information about the match. private getter expected
def match(partial)
actual = partial.actual.to_a # Creates the matcher with an expected value.
values = ExpectedActual.new(expected, label, actual, partial.label) def initialize(@expected : TestValue(ExpectedType))
if values.expected.size == values.actual.size 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
"contains exactly #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
index = compare_arrays(expected_elements, actual_elements)
case index
when Int # Content differs.
failed_content_mismatch(expected_elements, actual_elements, index, actual.label)
when true # Contents are identical.
SuccessfulMatchData.new
else # Size differs.
failed_size_mismatch(expected_elements, actual_elements, actual.label)
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
case compare_arrays(expected_elements, actual_elements)
when Int # Contents differ.
SuccessfulMatchData.new
when true # Contents are identical.
failed_content_identical(expected_elements, actual_elements, actual.label)
else # Size differs.
SuccessfulMatchData.new
end
end
# Ensures the arrays elements are compared in order.
# This is the default behavior for the matcher.
def in_order
self
end
# Specifies that the arrays elements can be compared in any order.
# The elements can be in a different order, but both arrays must have the same elements.
def in_any_order
UnorderedArrayMatcher.new(expected)
end
# Compares two arrays to determine whether they contain the same elements, and in the same order.
# If the arrays are the same, then `true` is returned.
# If they are different, `false` or an integer is returned.
# `false` is returned when the sizes of the arrays don't match.
# An integer is returned, that is the index of the mismatched elements in the arrays.
private def compare_arrays(expected_elements, actual_elements)
if expected_elements.size == actual_elements.size
index = 0 index = 0
values.expected.zip(values.actual) do |expected, element| expected_elements.zip(actual_elements) do |expected_element, actual_element|
return ContentMatchData.new(index, values) unless expected == element return index unless expected_element == actual_element
index += 1 index += 1
end end
IdenticalMatchData.new(values) true
else else
SizeMatchData.new(values) false
end end
end end
# Common functionality for all match data for this matcher. # Produces match data for a failure when the array sizes differ.
private abstract struct CommonMatchData(ExpectedType, ActualType) < MatchData private def failed_size_mismatch(expected_elements, actual_elements, actual_label)
# Creates the match data. FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (size mismatch)",
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) expected: expected_elements.inspect,
super(matched) actual: actual_elements.inspect,
"expected size": expected_elements.size.to_s,
"actual size": actual_elements.size.to_s
)
end end
# Basic information about the match. # Produces match data for a failure when the array content is mismatched.
def named_tuple private def failed_content_mismatch(expected_elements, actual_elements, index, actual_label)
{ FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (element mismatch)",
expected: NegatableMatchDataValue.new(@values.expected), expected: expected_elements[index].inspect,
actual: @values.actual, actual: actual_elements[index].inspect,
} index: index.to_s
)
end end
# Describes the condition that satisfies the matcher. # Produces match data for a failure when the arrays are identical, but they shouldn't be (negation).
# This is informational and displayed to the end-user. private def failed_content_identical(expected_elements, actual_elements, actual_label)
def message FailedMatchData.new("#{actual_label} contains exactly #{expected.label}",
"#{@values.actual_label} contains exactly #{@values.expected_label}" expected: "Not #{expected_elements.inspect}",
end actual: actual_elements.inspect
end )
# Match data specific to this matcher.
# This type is used when the actual value matches the expected value.
private struct IdenticalMatchData(ExpectedType, ActualType) < CommonMatchData(ExpectedType, ActualType)
# Creates the match data.
def initialize(values : ExpectedActual(ExpectedType, ActualType))
super(true, values)
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain exactly #{@values.expected_label}"
end
end
# Match data specific to this matcher.
# This type is used when the actual size differs from the expected size.
private struct SizeMatchData(ExpectedType, ActualType) < CommonMatchData(ExpectedType, ActualType)
# Creates the match data.
def initialize(values : ExpectedActual(ExpectedType, ActualType))
super(false, values)
end
# Information about the match.
def named_tuple
super.merge({
"expected size": NegatableMatchDataValue.new(@values.expected.size),
"actual size": @values.actual.size,
})
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain exactly #{@values.expected_label} (size differs)"
end
end
# Match data specific to this matcher.
# This type is used when the actual contents differs from the expected contents.
private struct ContentMatchData(ExpectedType, ActualType) < CommonMatchData(ExpectedType, ActualType)
# Creates the match data.
def initialize(@index : Int32, values : ExpectedActual(ExpectedType, ActualType))
super(false, values)
end
# Information about the match.
def named_tuple
super.merge({
index: @index,
"expected element": NegatableMatchDataValue.new(@values.expected[@index]),
"actual element": @values.actual[@index],
})
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain exactly #{@values.expected_label} (content differs)"
end
end end
end end
end end

View file

@ -1,72 +1,83 @@
require "./value_matcher" require "../test_value"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests that multiple attributes match specified conditions. # Matcher that tests that multiple attributes match specified conditions.
# The attributes are tested with the === operator. # The attributes are tested with the === operator.
# The `ExpectedType` type param should be a `NamedTuple`. # The `ExpectedType` type param should be a `NamedTuple`.
struct AttributesMatcher(ExpectedType) < ValueMatcher(ExpectedType) # Each key in the tuple is the attribute/method name,
# Determines whether the matcher is satisfied with the value given to it. # and the corresponding value is the expected value to match against.
private def match?(actual) struct AttributesMatcher(ExpectedType) < Matcher
# Test each attribute and immediately return false if a comparison fails. # Expected value and label.
{% for attribute in ExpectedType.keys %} private getter expected
return false unless expected[{{attribute.symbolize}}] === actual[{{attribute.symbolize}}]
{% end %}
# All checks passed if this point is reached. # Creates the matcher with an expected value.
true def initialize(@expected : TestValue(ExpectedType))
end end
# Determines whether the matcher is satisfied with the partial given to it. # Short text about the matcher's purpose.
# `MatchData` is returned that contains information about the match. # This explains what condition satisfies the matcher.
def match(partial) # The description is used when the one-liner syntax is used.
actual_values = snapshot_values(partial.actual) def description
values = ExpectedActual.new(expected, label, actual_values, partial.label) "has attributes #{expected.label}"
MatchData.new(match?(actual_values), values) end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} does not have attributes #{expected.label}", **values(snapshot))
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
FailedMatchData.new("#{actual.label} has attributes #{expected.label}", **values(snapshot))
else
SuccessfulMatchData.new
end
end end
# Captures all of the actual values. # Captures all of the actual values.
# A `NamedTuple` is returned, # A `NamedTuple` is returned, with each key being the attribute.
# with each key being the attribute. private def snapshot_values(object)
private def snapshot_values(actual)
{% begin %} {% begin %}
{ {
{% for attribute in ExpectedType.keys %} {% for attribute in ExpectedType.keys %}
{{attribute}}: actual.{{attribute}}, {{attribute}}: object.{{attribute}},
{% end %} {% end %}
} }
{% end %} {% end %}
end end
# Match data specific to this matcher. # Checks if all attributes from the snapshot of them are satisified.
private struct MatchData(ExpectedType, ActualType) < MatchData private def match?(snapshot)
# Creates the match data. # Test that every attribute has the expected value.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) {% for attribute in ExpectedType.keys %}
super(matched) return false unless expected.value[{{attribute.symbolize}}] === snapshot[{{attribute.symbolize}}]
{% end %}
# At this point, none of the checks failed, so the match was successful.
true
end end
# Information about the match. # Produces the tuple for the failed match data from a snapshot of the attributes.
def named_tuple private def values(snapshot)
{% begin %} {% begin %}
{ {
{% for attribute in ExpectedType.keys %} {% for attribute in ExpectedType.keys %}
{{"expected " + attribute.stringify}}: NegatableMatchDataValue.new(@values.expected[{{attribute.symbolize}}]), {{"expected " + attribute.stringify}}: expected.value[{{attribute.symbolize}}].inspect,
{{"actual " + attribute.stringify}}: @values.actual[{{attribute.symbolize}}], {{"actual " + attribute.stringify}}: snapshot[{{attribute.symbolize}}].inspect,
{% end %} {% end %}
} }
{% end %} {% end %}
end end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} has attributes #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not have attributes #{@values.expected_label}"
end
end
end end
end end

View file

@ -4,44 +4,37 @@ module Spectator::Matchers
# Common matcher that tests whether two values semantically equal each other. # Common matcher that tests whether two values semantically equal each other.
# The values are compared with the === operator. # The values are compared with the === operator.
struct CaseMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct CaseMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
expected === actual # The description is used when the one-liner syntax is used.
def description
"matches #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) expected.value === actual.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not match #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
{ #
expected: NegatableMatchDataValue.new(@values.expected), # This is only called when `#does_not_match?` returns false.
actual: @values.actual, #
} # The message should typically only contain the test expression labels.
end # Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
# Describes the condition that satisfies the matcher. "#{actual.label} matched #{expected.label}"
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} matches #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not match #{@values.expected_label}"
end
end end
end end
end end

View file

@ -0,0 +1,84 @@
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an expression changed from and to specific values.
struct ChangeExactMatcher(ExpressionType, FromType, ToType) < Matcher
# The expression that is expected to (not) change.
private getter expression
# The expected value of the expression before the change.
private getter expected_before
# The expected value of the expression after the change.
private getter expected_after
# Creates a new change matcher.
def initialize(@expression : TestBlock(ExpressionType), @expected_before : FromType, @expected_after : ToType)
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
"changes #{expression.label} from #{expected_before.inspect} to #{expected_after.inspect}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if expected_before == before
if before == after
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
before: before.inspect,
after: after.inspect
)
elsif expected_after == after
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} did not change #{expression.label} to #{expected_after.inspect}",
before: before.inspect,
after: after.inspect,
expected: expected_after.inspect
)
end
else
FailedMatchData.new("#{expression.label} was not initially #{expected_before.inspect}",
expected: expected_before.inspect,
actual: before.inspect,
)
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if expected_before == before
if expected_after == after
FailedMatchData.new("#{actual.label} changed #{expression.label} from #{expected_before.inspect} to #{expected_after.inspect}",
before: before.inspect,
after: after.inspect
)
else
SuccessfulMatchData.new
end
else
FailedMatchData.new("#{expression.label} was not initially #{expected_before.inspect}",
expected: expected_before.inspect,
actual: before.inspect,
)
end
end
# Performs the change and reports the before and after values.
private def change(actual)
before = expression.value # Retrieve the expression's initial value.
actual.value # Invoke action that might change the expression's value.
after = expression.value # Retrieve the expression's value again.
{before, after}
end
end
end

View file

@ -0,0 +1,84 @@
require "./change_exact_matcher"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an expression changed from a specific value.
struct ChangeFromMatcher(ExpressionType, FromType) < Matcher
# The expression that is expected to (not) change.
private getter expression
# The expected value of the expression before the change.
private getter expected
# Creates a new change matcher.
def initialize(@expression : TestBlock(ExpressionType), @expected : FromType)
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
"changes #{expression.label} from #{expected}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if expected != before
FailedMatchData.new("#{expression.label} was not initially #{expected}",
expected: expected.inspect,
actual: before.inspect,
)
elsif before == after
FailedMatchData.new("#{actual.label} did not change #{expression.label} from #{expected}",
before: before.inspect,
after: after.inspect,
expected: "Not #{expected.inspect}"
)
else
SuccessfulMatchData.new
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if expected != before
FailedMatchData.new("#{expression.label} was not initially #{expected}",
expected: expected.inspect,
actual: before.inspect
)
elsif before == after
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} changed #{expression.label} from #{expected}",
before: before.inspect,
after: after.inspect,
expected: expected.inspect
)
end
end
# Specifies what the resulting value of the expression must be.
def to(value : T) forall T
ChangeExactMatcher.new(@expression, @expected, value)
end
# Specifies what the resulting value of the expression should change by.
def by(amount : T) forall T
ChangeExactMatcher.new(@expression, @expected, @expected + value)
end
# Performs the change and reports the before and after values.
private def change(actual)
before = expression.value # Retrieve the expression's initial value.
actual.value # Invoke action that might change the expression's value.
after = expression.value # Retrieve the expression's value again.
{before, after}
end
end
end

View file

@ -0,0 +1,85 @@
require "./change_from_matcher"
require "./change_to_matcher"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an expression changed.
struct ChangeMatcher(ExpressionType) < Matcher
# The expression that is expected to (not) change.
private getter expression
# Creates a new change matcher.
def initialize(@expression : TestBlock(ExpressionType))
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
"changes #{expression.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if before == after
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
before: before.inspect,
after: after.inspect
)
else
SuccessfulMatchData.new
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if before == after
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} changed #{expression.label}",
before: before.inspect,
after: after.inspect
)
end
end
# Specifies what the initial value of the expression must be.
def from(value : T) forall T
ChangeFromMatcher.new(@expression, value)
end
# Specifies what the resulting value of the expression must be.
def to(value : T) forall T
ChangeToMatcher.new(@expression, value)
end
# Specifies that t he resulting value must be some amount different.
def by(amount : T) forall T
ChangeRelativeMatcher.new(@expression, "by #{amount}") { |before, after| amount == after - before }
end
# Specifies that the resulting value must be at least some amount different.
def by_at_least(minimum : T) forall T
ChangeRelativeMatcher.new(@expression, "by at least #{minimum}") { |before, after| minimum <= after - before }
end
# Specifies that the resulting value must be at most some amount different.
def by_at_most(maximum : T) forall T
ChangeRelativeMatcher.new(@expression, "by at most #{maximum}") { |before, after| maximum >= after - before }
end
# Performs the change and reports the before and after values.
private def change(actual)
before = expression.value # Retrieve the expression's initial value.
actual.value # Invoke action that might change the expression's value.
after = expression.value # Retrieve the expression's value again.
{before, after}
end
end
end

View file

@ -0,0 +1,57 @@
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an expression changed by an amount.
struct ChangeRelativeMatcher(ExpressionType) < Matcher
# The expression that is expected to (not) change.
private getter expression
# Creates a new change matcher.
def initialize(@expression : TestBlock(ExpressionType), @relativity : String,
&evaluator : ExpressionType, ExpressionType -> Bool)
@evaluator = evaluator
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
"changes #{expression.label} #{@relativity}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if before == after
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
before: before.inspect,
after: after.inspect
)
elsif @evaluator.call(before, after)
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} did not change #{expression.label} #{@relativity}",
before: before.inspect,
after: after.inspect
)
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
{% raise "The `expect { }.to_not change { }.by_...()` syntax is not supported (ambiguous)." %}
end
# Performs the change and reports the before and after values.
private def change(actual)
before = expression.value # Retrieve the expression's initial value.
actual.value # Invoke action that might change the expression's value.
after = expression.value # Retrieve the expression's value again.
{before, after}
end
end
end

View file

@ -0,0 +1,78 @@
require "./change_exact_matcher"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Matcher that tests whether an expression changed to a specific value.
struct ChangeToMatcher(ExpressionType, ToType) < Matcher
# The expression that is expected to (not) change.
private getter expression
# The expected value of the expression after the change.
private getter expected
# Creates a new change matcher.
def initialize(@expression : TestBlock(ExpressionType), @expected : ToType)
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
"changes #{expression.label} to #{expected}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
before, after = change(actual)
if before == after
FailedMatchData.new("#{actual.label} did not change #{expression.label}",
before: before.inspect,
after: after.inspect,
expected: expected.inspect
)
elsif expected == after
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} did not change #{expression.label} to #{expected}",
before: before.inspect,
after: after.inspect,
expected: expected.inspect
)
end
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.
# "The action does not change the expression to some value."
# Is it a failure if the value is not changed,
# but it is the expected value?
#
# RSpec doesn't support this syntax either.
def negated_match(actual : TestExpression(T)) : MatchData forall T
{% raise "The `expect { }.to_not change { }.to()` syntax is not supported (ambiguous)." %}
end
# Specifies what the initial value of the expression must be.
def from(value : T) forall T
ChangeExactMatcher.new(@expression, value, @expected)
end
# Specifies how much the initial value should change by.
def by(amount : T) forall T
ChangeExactMatcher.new(@expression, @expected - amount, @expected)
end
# Performs the change and reports the before and after values.
private def change(actual)
before = expression.value # Retrieve the expression's initial value.
actual.value # Invoke action that might change the expression's value.
after = expression.value # Retrieve the expression's value again.
{before, after}
end
end
end

View file

@ -1,19 +1,41 @@
require "../test_value"
require "./range_matcher"
require "./value_matcher" require "./value_matcher"
module Spectator::Matchers module Spectator::Matchers
# Matcher for checking that a value is in a collection of other values. # Matcher for checking that a value is in a collection of other values.
struct CollectionMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct CollectionMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
expected.includes?(actual) # The description is used when the one-liner syntax is used.
def description
"is in #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) expected.value.includes?(actual.value)
actual = partial.actual end
matched = match?(actual)
MatchData.new(matched, ExpectedActual.new(partial, self)) # Message displayed when the matcher isn't satisifed.
#
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not in #{expected.label}"
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)
"#{actual.label} is in #{expected.label}"
end end
# Creates a new range matcher with bounds based off of *center*. # Creates a new range matcher with bounds based off of *center*.
@ -21,7 +43,7 @@ module Spectator::Matchers
# This method expects that the original matcher was created with a "difference" value. # This method expects that the original matcher was created with a "difference" value.
# That is: # That is:
# ``` # ```
# RangeMatcher.new(diff).of(center) # CollectionMatcher.new(diff).of(center)
# ``` # ```
# This implies that the `#match` method would not work on the original matcher. # This implies that the `#match` method would not work on the original matcher.
# #
@ -29,39 +51,12 @@ module Spectator::Matchers
# and have upper and lower bounds equal to *center* plus and minus diff. # and have upper and lower bounds equal to *center* plus and minus diff.
# The range will be inclusive. # The range will be inclusive.
def of(center) def of(center)
diff = @expected diff = @expected.value
lower = center - diff lower = center - diff
upper = center + diff upper = center + diff
range = Range.new(lower, upper) range = Range.new(lower, upper)
RangeMatcher.new(range, "#{center} +/- #{label}") test_value = TestValue.new(range, "#{center} +/- #{expected.label}")
end RangeMatcher.new(test_value)
# Match data specific to this matcher.
private struct MatchData(ExpectedType, ActualType) < MatchData
# Creates the match data.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType))
super(matched)
end
# Information about the match.
def named_tuple
{
collection: NegatableMatchDataValue.new(@values.expected),
actual: @values.actual,
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} is in #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is not in #{@values.expected_label}"
end
end end
end end
end end

View file

@ -4,46 +4,48 @@ 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) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
expected.all? do |item| # The description is used when the one-liner syntax is used.
actual.includes?(item) def description
"contains #{expected.label}"
end
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual : TestExpression(T)) forall T
expected.value.all? do |item|
actual.value.includes?(item)
end end
end end
# Determines whether the matcher is satisfied with the partial given to it. # Message displayed when the matcher isn't satisifed.
# `MatchData` is returned that contains information about the match. #
def match(partial) # This is only called when `#match?` returns false.
values = ExpectedActual.new(partial, self) #
MatchData.new(match?(values.actual), values) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not match #{expected.label}"
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed and is negated.
private struct MatchData(ExpectedType, ActualType) < MatchData # This is essentially what would satisfy the matcher if it wasn't negated.
# Creates the match data. #
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) # This is only called when `#does_not_match?` returns false.
super(matched) #
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
"#{actual.label} contains #{expected.label}"
end end
# Information about the match. # Additional information about the match failure.
def named_tuple # The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
subset: NegatableMatchDataValue.new(@values.expected), subset: expected.value.inspect,
superset: @values.actual, superset: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} contains #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not contain #{@values.expected_label}"
end
end
end end
end end

View file

@ -1,32 +0,0 @@
module Spectator::Matchers
# Match data that does nothing.
# This is to workaround a Crystal compiler bug.
# See: [Issue 4225](https://github.com/crystal-lang/crystal/issues/4225)
# If there are no concrete implementations of an abstract class,
# the compiler gives an error.
# The error indicates an abstract method is undefined.
# This class shouldn't be used, it's just to trick the compiler.
private struct DummyMatchData < MatchData
# Creates the match data.
def initialize
super(false)
end
# Dummy values.
def named_tuple
{
you: "shouldn't be calling this.",
}
end
# Dummy message.
def message
"You shouldn't be calling this."
end
# Dummy message
def negated_message
"You shouldn't be calling this."
end
end
end

View file

@ -3,46 +3,38 @@ require "./matcher"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests whether a collection is empty. # Matcher that tests whether a collection is empty.
# The values are checked with the `empty?` method. # The values are checked with the `empty?` method.
struct EmptyMatcher < Matcher struct EmptyMatcher < StandardMatcher
# Textual representation of what the matcher expects. # Short text about the matcher's purpose.
def label # This explains what condition satisfies the matcher.
"empty?" # The description is used when the one-liner syntax is used.
def description
"is empty"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value.empty?
actual = partial.actual
matched = actual.empty?
MatchData.new(matched, actual, partial.label)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(T) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @actual : T, @actual_label : String) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not empty"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
{ #
expected: NegatableMatchDataValue.new([] of Nil), # This is only called when `#does_not_match?` returns false.
actual: @actual, #
} # The message should typically only contain the test expression labels.
end # Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
# Describes the condition that satisfies the matcher. "#{actual.label} is empty"
# This is informational and displayed to the end-user.
def message
"#{@actual_label} is empty"
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 empty"
end
end end
end end
end end

View file

@ -1,89 +1,102 @@
require "./value_matcher" require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests whether a value, such as a `String` or `Array`, ends with a value. # Matcher that tests whether a value, such as a `String` or `Array`, ends with a value.
# The `ends_with?` method is used if it's defined on the actual type. # The `ends_with?` method is used if it's defined on the actual type.
# Otherwise, it is treated as an `Indexable` and the `last` value is compared against. # Otherwise, it is treated as an `Indexable` and the `last` value is compared against.
struct EndWithMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct EndWithMatcher(ExpectedType) < Matcher
# Determines whether the matcher is satisfied with the value given to it. # Expected value and label.
private def match_ends_with?(actual) private getter expected
actual.ends_with?(expected)
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end end
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match_last?(actual) # This explains what condition satisfies the matcher.
expected === actual # The description is used when the one-liner syntax is used.
def description
"ends with #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Actually performs the test against the expression.
# `MatchData` is returned that contains information about the match. def match(actual : TestExpression(T)) : MatchData forall T
def match(partial) if (value = actual.value).responds_to?(:ends_with?)
values = ExpectedActual.new(partial, self) match_ends_with(value, actual.label)
actual = values.actual
if actual.responds_to?(:ends_with?)
EndsWithMatchData.new(match_ends_with?(actual), values)
else else
last = actual.last match_last(value, actual.label)
LastMatchData.new(match_last?(last), values, last)
end end
end end
# Match data specific to this matcher. # Performs the test against the expression, but inverted.
# This type is used when the actual value responds to `ends_with?`. # A successful match with `#match` should normally fail for this method, and vice-versa.
private struct EndsWithMatchData(ExpectedType, ActualType) < MatchData def negated_match(actual : TestExpression(T)) : MatchData forall T
# Creates the match data. if actual.value.responds_to?(:ends_with?)
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) negated_match_ends_with(actual)
super(matched) else
end negated_match_last(actual)
# Information about the match.
def named_tuple
{
expected: NegatableMatchDataValue.new(@values.expected),
actual: @values.actual,
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} ends with #{@values.expected_label} (using #ends_with?)"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not end with #{@values.expected_label} (using #ends_with?)"
end end
end end
# Match data specific to this matcher. # Checks whether the actual value ends with the expected value.
# This type is used when the actual value does not respond to `ends_with?`. # This method expects (and uses) the `#ends_with?` method on the value.
private struct LastMatchData(ExpectedType, ActualType, LastType) < MatchData private def match_ends_with(actual_value, actual_label)
# Creates the match data. if actual_value.ends_with?(expected.value)
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType), @last : LastType) SuccessfulMatchData.new
super(matched) else
FailedMatchData.new("#{actual_label} does not end with #{expected.label} (using #ends_with?)",
expected: expected.value.inspect,
actual: actual_value.inspect
)
end
end end
# Information about the match. # Checks whether the last element of the value is the expected value.
def named_tuple # This method expects that the actual value is a set (enumerable).
{ private def match_last(actual_value, actual_label)
expected: @values.expected, list = actual_value.to_a
actual: @last, last = list.last
list: @values.actual,
} if expected.value === last
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual_label} does not end with #{expected.label} (using expected === last)",
expected: expected.value.inspect,
actual: last.inspect,
list: list.inspect
)
end
end end
# Describes the condition that satisfies the matcher. # Checks whether the actual value does not end with the expected value.
# This is informational and displayed to the end-user. # This method expects (and uses) the `#ends_with?` method on the value.
def message private def negated_match_ends_with(actual)
"#{@values.actual_label} ends with #{@values.expected_label} (using expected === actual.last)" if actual.value.ends_with?(expected.value)
FailedMatchData.new("#{actual.label} ends with #{expected.label} (using #ends_with?)",
expected: expected.value.inspect,
actual: actual.value.inspect
)
else
SuccessfulMatchData.new
end
end end
# Describes the condition that won't satsify the matcher. # Checks whether the last element of the value is not the expected value.
# This is informational and displayed to the end-user. # This method expects that the actual value is a set (enumerable).
def negated_message private def negated_match_last(actual)
"#{@values.actual_label} does not end with #{@values.expected_label} (using expected === actual.last)" list = actual.value.to_a
last = list.last
if expected.value === last
FailedMatchData.new("#{actual.label} ends with #{expected.label} (using expected === last)",
expected: expected.value.inspect,
actual: last.inspect,
list: list.inspect
)
else
SuccessfulMatchData.new
end end
end end
end end

View file

@ -4,44 +4,37 @@ module Spectator::Matchers
# Common matcher that tests whether two values equal each other. # Common matcher that tests whether two values equal each other.
# The values are compared with the == operator. # The values are compared with the == operator.
struct EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual == expected # The description is used when the one-liner syntax is used.
def description
"equals #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) expected.value == actual.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not equal #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
{ #
expected: NegatableMatchDataValue.new(@values.expected), # This is only called when `#does_not_match?` returns false.
actual: @values.actual, #
} # The message should typically only contain the test expression labels.
end # Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
# Describes the condition that satisfies the matcher. "#{actual.label} equals #{expected.label}"
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} is #{@values.expected_label} (using ==)"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is not #{@values.expected_label} (using ==)"
end
end end
end end
end end

View file

@ -1,11 +1,93 @@
require "./value_matcher" require "../test_value"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests whether an exception is raised. # Matcher that tests whether an exception is raised.
struct ExceptionMatcher(ExceptionType, ExpectedType) < ValueMatcher(ExpectedType) struct ExceptionMatcher(ExceptionType, ExpectedType) < Matcher
# Determines whether the matcher is satisfied with the value given to it. # Expected value and label.
private def match?(exception) private getter expected
exception.is_a?(ExceptionType) && (expected.nil? || expected === exception.message)
# Creates the matcher with no expectation of the message.
def initialize
@expected = TestValue.new(nil, ExceptionType.to_s)
end
# Creates the matcher with an expected message.
def initialize(@expected : TestValue(ExpectedType))
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
if (message = @expected)
"raises #{ExceptionType} with message #{message}"
else
"raises #{ExceptionType}"
end
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
exception = capture_exception { actual.value }
if exception.nil?
FailedMatchData.new("#{actual.label} did not raise", expected: ExceptionType.inspect)
else
if exception.is_a?(ExceptionType)
if (value = expected.value).nil?
SuccessfulMatchData.new
else
if value === exception.message
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} raised #{exception.class}, but the message is not #{expected.label}",
"expected type": ExceptionType.inspect,
"actual type": exception.class.inspect,
"expected message": value.inspect,
"actual message": exception.message.to_s
)
end
end
else
FailedMatchData.new("#{actual.label} did not raise #{ExceptionType}",
expected: ExceptionType.inspect,
actual: exception.class.inspect
)
end
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
exception = capture_exception { actual.value }
if exception.nil?
SuccessfulMatchData.new
else
if exception.is_a?(ExceptionType)
if (value = expected.value).nil?
FailedMatchData.new("#{actual.label} raised #{exception.class}",
expected: "Not #{ExceptionType}",
actual: exception.class.inspect
)
else
if value === exception.message
FailedMatchData.new("#{actual.label} raised #{exception.class} with message matching #{expected.label}",
"expected type": ExceptionType.inspect,
"actual type": exception.class.inspect,
"expected message": value.inspect,
"actual message": exception.message.to_s
)
else
SuccessfulMatchData.new
end
end
else
SuccessfulMatchData.new
end
end
end end
# Runs a block of code and returns the exception it threw. # Runs a block of code and returns the exception it threw.
@ -20,104 +102,21 @@ module Spectator::Matchers
exception exception
end 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)
exception = capture_exception { partial.actual }
matched = match?(exception)
if exception.nil?
MatchData.new(ExceptionType, matched, ExpectedActual.new(expected, label, exception, partial.label))
else
values = ExpectedActual.new(expected, label, exception, partial.label)
if expected.nil?
MatchData.new(ExceptionType, matched, values)
else
MessageMatchData.new(ExceptionType, matched, values)
end
end
end
# Creates a new exception matcher with no message check.
def initialize
super(nil, ExceptionType.to_s)
end
# Creates a new exception matcher with a message check.
def initialize(expected : ExpectedType, label : String)
super(expected, label)
end
# Creates a new exception matcher with no message check. # Creates a new exception matcher with no message check.
def self.create(exception_type : T.class, label : String) forall T def self.create(exception_type : T.class, label : String) forall T
ExceptionMatcher(T, Nil).new ExceptionMatcher(T, Nil).new
end end
# Creates a new exception matcher with a message check. # Creates a new exception matcher with a message check.
def self.create(expected, label : String) def self.create(value, label : String)
ExceptionMatcher(Exception, typeof(expected)).new(expected, label) expected = TestValue.new(value, label)
ExceptionMatcher(Exception, typeof(value)).new(expected)
end end
# Creates a new exception matcher with a type and message check. # Creates a new exception matcher with a type and message check.
def self.create(exception_type : T.class, expected, label : String) forall T def self.create(exception_type : T.class, value, label : String) forall T
ExceptionMatcher(T, typeof(expected)).new(expected, label) expected = TestValue.new(value, label)
end ExceptionMatcher(T, typeof(value)).new(expected)
# Match data specific to this matcher.
private struct MatchData(ExceptionType, ExpectedType, ActualType) < MatchData
# Creates the match data.
def initialize(t : ExceptionType.class, matched, @values : ExpectedActual(ExpectedType, ActualType))
super(matched)
end
# Information about the match.
def named_tuple
{
"expected type": NegatableMatchDataValue.new(ExceptionType),
"actual type": @values.actual.class,
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} raises #{ExceptionType}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not raise #{ExceptionType}"
end
end
# Match data specific to this matcher with an expected message.
private struct MessageMatchData(ExceptionType, ExpectedType) < ::Spectator::Matchers::MatchData
# Creates the match data.
def initialize(t : ExceptionType.class, matched, @values : ExpectedActual(ExpectedType, Exception))
super(matched)
end
# Information about the match.
def named_tuple
{
"expected type": NegatableMatchDataValue.new(ExceptionType),
"actual type": @values.actual.class,
"expected message": NegatableMatchDataValue.new(@values.expected),
"actual message": @values.actual.message,
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} raises #{ExceptionType} with message #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not raise #{ExceptionType} with message #{@values.expected_label}"
end
end end
end end
end end

View file

@ -1,30 +0,0 @@
module Spectator::Matchers
# Stores values and labels for expected and actual values.
private struct ExpectedActual(ExpectedType, ActualType)
# The expected value.
getter expected : ExpectedType
# The user label for the expected value.
getter expected_label : String
# The actual value.
getter actual : ActualType
# The user label for the actual value.
getter actual_label : String
# Creates the value and label store.
def initialize(@expected : ExpectedType, @expected_label, @actual : ActualType, @actual_label)
end
# Creates the value and label store.
# Attributes are pulled from an expectation partial and matcher.
def initialize(
partial : Spectator::Expectations::ValueExpectationPartial(ActualType) |
Spectator::Expectations::BlockExpectationPartial(ActualType),
matcher : ValueMatcher(ExpectedType)
)
initialize(matcher.expected, matcher.label, partial.actual, partial.label)
end
end
end

View file

@ -0,0 +1,22 @@
require "./match_data"
module Spectator::Matchers
# Information about a failed match.
struct FailedMatchData < MatchData
# Indicates that the match failed.
def matched?
false
end
# Description from the matcher as to why it failed.
getter failure_message : String
# Additional information from the match that can be used to debug the problem.
getter values : Array(Tuple(Symbol, String))
# Creates the match data.
def initialize(@failure_message, **values)
@values = values.to_a
end
end
end

View file

@ -1,23 +0,0 @@
require "./match_data_value"
module Spectator::Matchers
# Wraps a value for used in match data.
private class GenericMatchDataValue(T) < MatchDataValue
# Underlying value.
getter value
# Creates the wrapper.
def initialize(@value : T)
end
# Stringifies the value.
def to_s(io)
@value.inspect(io)
end
# Inspects the value.
def inspect(io)
@value.inspect(io)
end
end
end

View file

@ -4,44 +4,55 @@ module Spectator::Matchers
# Matcher that tests whether one value is greater than or equal to another. # Matcher that tests whether one value is greater than or equal to another.
# The values are compared with the >= operator. # The values are compared with the >= operator.
struct GreaterThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct GreaterThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual >= expected # The description is used when the one-liner syntax is used.
def description
"greater than or equal to #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value >= expected.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is less than #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # 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)
"#{actual.label} is greater than or equal to #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: NegatablePrefixedMatchDataValue.new(">=", "<", @values.expected), expected: ">= #{expected.value.inspect}",
actual: @values.actual, actual: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@values.actual_label} is greater than or equal to #{@values.expected_label} (using >=)" {
end expected: "< #{expected.value.inspect}",
actual: actual.value.inspect,
# Describes the condition that won't satsify the matcher. }
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is less than #{@values.expected_label} (using >=)"
end
end end
end end
end end

View file

@ -4,44 +4,55 @@ module Spectator::Matchers
# Matcher that tests whether one value is greater than another. # Matcher that tests whether one value is greater than another.
# The values are compared with the > operator. # The values are compared with the > operator.
struct GreaterThanMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct GreaterThanMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual > expected # The description is used when the one-liner syntax is used.
def description
"greater than #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value > expected.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is less than or equal to #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # 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)
"#{actual.label} is greater than #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: NegatablePrefixedMatchDataValue.new(">", "<=", @values.expected), expected: "> #{expected.value.inspect}",
actual: @values.actual, actual: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@values.actual_label} is greater than #{@values.expected_label} (using >)" {
end expected: "<= #{expected.value.inspect}",
actual: actual.value.inspect,
# Describes the condition that won't satsify the matcher. }
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is less than or equal to #{@values.expected_label} (using >)"
end
end end
end end
end end

View file

@ -4,45 +4,48 @@ module Spectator::Matchers
# Matcher that tests whether a `Hash` (or similar type) has a given key. # Matcher that tests whether a `Hash` (or similar type) has a given key.
# The set is checked with the `has_key?` method. # The set is checked with the `has_key?` method.
struct HaveKeyMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct HaveKeyMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual.has_key?(expected) # The description is used when the one-liner syntax is used.
def description
"has key #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value.has_key?(expected.value)
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not have key #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
actual = @values.actual #
# 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)
"#{actual.label} has key #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
actual_value = actual.value
set = actual_value.responds_to?(:keys) ? actual_value.keys : actual_value
{ {
key: NegatableMatchDataValue.new(@values.expected), key: expected.value.inspect,
actual: actual.responds_to?(:keys) ? actual.keys : actual, actual: set.inspect,
} }
end end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} has key #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not have key #{@values.expected_label}"
end
end
end end
end end

View file

@ -5,68 +5,69 @@ module Spectator::Matchers
# 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) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
# True is returned if the match was successful, false otherwise. # This explains what condition satisfies the matcher.
private def match?(actual) # The description is used when the one-liner syntax is used.
if actual.is_a?(String) def description
match_string?(actual) "includes #{expected.label}"
end
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual : TestExpression(T)) forall T
if (value = actual.value).is_a?(String)
match_string?(value)
else else
match_enumerable?(actual) match_enumerable?(value)
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?(actual) private def match_string?(value)
expected.all? do |item| expected.value.all? do |item|
actual.includes?(item) value.includes?(item)
end end
end end
# Checks if an `Enumerable` matches the expected values. # Checks if an `Enumerable` matches the expected values.
# The `===` operator is used on every item. # The `===` operator is used on every item.
private def match_enumerable?(actual) private def match_enumerable?(value)
array = actual.to_a array = value.to_a
expected.all? do |item| expected.value.all? do |item|
array.any? do |elem| array.any? do |element|
item === elem item === element
end end
end end
end end
# Determines whether the matcher is satisfied with the partial given to it. # Message displayed when the matcher isn't satisifed.
# `MatchData` is returned that contains information about the match. #
def match(partial) # This is only called when `#match?` returns false.
values = ExpectedActual.new(partial, self) #
MatchData.new(match?(values.actual), values) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not include #{expected.label}"
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed and is negated.
private struct MatchData(ExpectedType, ActualType) < MatchData # This is essentially what would satisfy the matcher if it wasn't negated.
# Creates the match data. #
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) # This is only called when `#does_not_match?` returns false.
super(matched) #
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
"#{actual.label} includes #{expected.label}"
end end
# Information about the match. # Additional information about the match failure.
def named_tuple # The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
subset: NegatableMatchDataValue.new(@values.expected), subset: expected.value.inspect,
superset: @values.actual, superset: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} includes #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not include #{@values.expected_label}"
end
end
end end
end end

View file

@ -0,0 +1,100 @@
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)
# Expected value and label.
private getter expected
# Creates the matcher with a expected values.
def initialize(@expected : TestValue(ExpectedType))
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
"has #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} does not have #{expected.label}", **values(snapshot))
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
FailedMatchData.new("#{actual.label} has #{expected.label}", **values(snapshot))
else
SuccessfulMatchData.new
end
end
# Message displayed when the matcher isn't satisifed.
#
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not have #{expected.label}"
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)
"#{actual.label} has #{expected.label}"
end
# Captures all of the actual values.
# A `NamedTuple` is returned, with each key being the attribute.
private def snapshot_values(object)
{% begin %}
{
{% for attribute in ExpectedType.keys %}
{{attribute}}: object.has_{{attribute}}?(*@expected.value[{{attribute.symbolize}}]),
{% end %}
}
{% end %}
end
# Checks if all predicate methods from the snapshot of them are satisified.
private def match?(snapshot)
# Test each predicate and immediately return false if one is false.
{% for attribute in ExpectedType.keys %}
return false unless snapshot[{{attribute.symbolize}}]
{% end %}
# All checks passed if this point is reached.
true
end
# Produces the tuple for the failed match data from a snapshot of the predicate methods.
private def values(snapshot)
{% begin %}
{
{% for attribute in ExpectedType.keys %}
{{attribute}}: snapshot[{{attribute.symbolize}}].inspect,
{% end %}
}
{% end %}
end
end
end

View file

@ -4,45 +4,48 @@ module Spectator::Matchers
# Matcher that tests whether a `Hash` (or similar type) has a given value. # Matcher that tests whether a `Hash` (or similar type) has a given value.
# The set is checked with the `has_value?` method. # The set is checked with the `has_value?` method.
struct HaveValueMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct HaveValueMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual.has_value?(expected) # The description is used when the one-liner syntax is used.
def description
"has value #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value.has_value?(expected.value)
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not have value #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
actual = @values.actual #
# 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)
"#{actual.label} has value #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
actual_value = actual.value
set = actual_value.responds_to?(:values) ? actual_value.values : actual_value
{ {
value: NegatableMatchDataValue.new(@values.expected), value: expected.value.inspect,
actual: actual.responds_to?(:values) ? actual.values : actual, actual: set.inspect,
} }
end end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} has value #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not have value #{@values.expected_label}"
end
end
end end
end end

View file

@ -4,44 +4,55 @@ module Spectator::Matchers
# Matcher that tests whether two values do not equal each other. # Matcher that tests whether two values do not equal each other.
# The values are compared with the != operator. # The values are compared with the != operator.
struct InequalityMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct InequalityMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual != expected # The description is used when the one-liner syntax is used.
def description
"is not equal to #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) expected.value != actual.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is equal to #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # 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)
"#{actual.label} is not equal to #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: NegatablePrefixedMatchDataValue.new("Not", "", @values.expected), expected: "Not #{expected.value.inspect}",
actual: @values.actual, actual: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@values.actual_label} is not #{@values.expected_label} (using !=)" {
end expected: expected.value.inspect,
actual: actual.value.inspect,
# Describes the condition that won't satsify the matcher. }
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is #{@values.expected_label} (using !=)"
end
end end
end end
end end

View file

@ -4,44 +4,55 @@ module Spectator::Matchers
# Matcher that tests whether one value is less than or equal to another. # Matcher that tests whether one value is less than or equal to another.
# The values are compared with the <= operator. # The values are compared with the <= operator.
struct LessThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct LessThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual <= expected # The description is used when the one-liner syntax is used.
def description
"less than or equal to #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value <= expected.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is greater than #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # 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)
"#{actual.label} is less than or equal to #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: NegatablePrefixedMatchDataValue.new("<=", ">", @values.expected), expected: "<= #{expected.value.inspect}",
actual: @values.actual, actual: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@values.actual_label} is less than or equal to #{@values.expected_label} (using <=)" {
end expected: "> #{expected.value.inspect}",
actual: actual.value.inspect,
# Describes the condition that won't satsify the matcher. }
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is greater than #{@values.expected_label} (using <=)"
end
end end
end end
end end

View file

@ -4,44 +4,55 @@ module Spectator::Matchers
# Matcher that tests whether one value is less than another. # Matcher that tests whether one value is less than another.
# The values are compared with the < operator. # The values are compared with the < operator.
struct LessThanMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct LessThanMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual < expected # The description is used when the one-liner syntax is used.
def description
"less than #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value < expected.value
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is greater than or equal to #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # 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)
"#{actual.label} is less than #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: NegatablePrefixedMatchDataValue.new("<", ">=", @values.expected), expected: "< #{expected.value.inspect}",
actual: @values.actual, actual: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@values.actual_label} is less than #{@values.expected_label} (using <)" {
end expected: ">= #{expected.value.inspect}",
actual: actual.value.inspect,
# Describes the condition that won't satsify the matcher. }
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is greater than or equal to #{@values.expected_label} (using <)"
end
end end
end end
end end

View file

@ -1,46 +1,7 @@
require "./match_data_labeled_value"
require "./match_data_value"
require "./generic_match_data_value"
module Spectator::Matchers module Spectator::Matchers
# Information regarding a expectation parial and matcher. # Information about the outcome of a match.
# `Matcher#match` will return a sub-type of this.
abstract struct MatchData abstract struct MatchData
# Indicates whether the matcher was satisified with the expectation partial. # Indicates whether the match as successful or not.
getter? matched : Bool abstract def matched? : Bool
# Creates the base of the match data.
# The *matched* argument indicates
# whether the matcher was satisified with the expectation partial.
def initialize(@matched)
end
# Information about the match.
# Returned elments will differ by matcher,
# but all will return a set of labeled values.
def values : Array(MatchDataLabeledValue)
named_tuple.map do |key, value|
if value.is_a?(MatchDataValue)
MatchDataLabeledValue.new(key, value)
else
wrapper = GenericMatchDataValue.new(value)
MatchDataLabeledValue.new(key, wrapper)
end
end
end
# Raw information about the match.
# Sub-types must implement this and return a `NamedTuple`
# containing the match data values.
# This will be transformed and returned by `#values`.
private abstract def named_tuple
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
abstract def message : String
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
abstract def negated_message : String
end end
end end

View file

@ -1,15 +0,0 @@
module Spectator::Matchers
# A value from match data with a label.
struct MatchDataLabeledValue
# Label tied to the value.
# This annotates what the value is.
getter label : Symbol
# The actual value from the match data.
getter value : MatchDataValue
# Creates a new labeled value.
def initialize(@label, @value)
end
end
end

View file

@ -1,10 +0,0 @@
module Spectator::Matchers
# Abstract base for all match data values.
# All sub-classes are expected to implement their own `#to_s`.
private abstract class MatchDataValue
# Placeholder for negating the value.
def negate
# ...
end
end
end

View file

@ -5,13 +5,21 @@ module Spectator::Matchers
# A matcher looks at something produced by the SUT # A matcher looks at something produced by the SUT
# and evaluates whether it is correct or not. # and evaluates whether it is correct or not.
abstract struct Matcher abstract struct Matcher
# Textual representation of what the matcher expects. # Short text about the matcher's purpose.
# This shouldn't be used in the conditional logic, # This explains what condition satisfies the matcher.
# but for verbose output to help the end-user. # The description is used when the one-liner syntax is used.
abstract def label : String # ```
# it { is_expected.to do_something }
# ```
# The phrasing should be such that it reads "it ___."
# where the blank is what is returned by this method.
abstract def description : String
# Determines whether the matcher is satisfied with the value given to it. # Actually performs the test against the expression (value or block).
# True is returned if the match was successful, false otherwise. abstract def match(actual : TestExpression(T)) : MatchData forall T
abstract def match(partial) : MatchData
# Performs the test against the expression (value or block), but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
abstract def negated_match(actual : TestExpression(T)) : MatchData forall T
end end
end end

View file

@ -1,34 +0,0 @@
require "./match_data_value"
module Spectator::Matchers
# Wraps an expected value that can be negated.
# This is used when a matcher is negated.
private class NegatableMatchDataValue(T) < MatchDataValue
# Negatable value.
getter value
# Creates the wrapper.
def initialize(@value : T)
@negated = false
end
# Negates (toggles) the value.
def negate
@negated = !@negated
end
# Produces a stringified value.
# The string will be prefixed with "Not" when negated.
def to_s(io)
io << "Not " if @negated
@value.inspect(io)
end
# Produces a stringified value with additional information.
# The string will be prefixed with "Not" when negated.
def inspect(io)
io << "Not " if @negated
@value.inspect(io)
end
end
end

View file

@ -1,39 +0,0 @@
require "./match_data_value"
module Spectator::Matchers
# Wraps a prefixed value that can be negated.
# This is used when a matcher is negated.
private class NegatablePrefixedMatchDataValue(T) < MatchDataValue
# Negatable value.
getter value
# Creates the wrapper.
def initialize(@positive_prefix : String, @negative_prefix : String, @value : T)
@negated = false
end
# Negates (toggles) the value.
def negate
@negated = !@negated
end
# Returns the correct prefix based on the negated status.
private def prefix
@negated ? @negative_prefix : @positive_prefix
end
# Produces a stringified value.
def to_s(io)
io << prefix
io << ' '
@value.inspect(io)
end
# Produces a stringified value with additional information.
def inspect(io)
io << prefix
io << ' '
@value.inspect(io)
end
end
end

View file

@ -3,46 +3,38 @@ require "./matcher"
module Spectator::Matchers module Spectator::Matchers
# Common matcher that tests whether a value is nil. # Common matcher that tests whether a value is nil.
# The `Object#nil?` method is used for this. # The `Object#nil?` method is used for this.
struct NilMatcher < Matcher struct NilMatcher < StandardMatcher
# Textual representation of what the matcher expects. # Short text about the matcher's purpose.
def label # This explains what condition satisfies the matcher.
"nil?" # The description is used when the one-liner syntax is used.
def description
"is nil"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) actual.value.nil?
actual = partial.actual
matched = actual.nil?
MatchData.new(matched, actual, partial.label)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(T) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @actual : T, @actual_label : String) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not nil"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
{ #
expected: NegatableMatchDataValue.new(nil), # This is only called when `#does_not_match?` returns false.
actual: @actual, #
} # The message should typically only contain the test expression labels.
end # Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
# Describes the condition that satisfies the matcher. "#{actual.label} is nil"
# This is informational and displayed to the end-user.
def message
"#{@actual_label} is nil"
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 nil"
end
end end
end end
end end

View file

@ -4,65 +4,96 @@ module Spectator::Matchers
# Matcher that tests one or more predicates (methods ending in '?'). # Matcher that tests one or more predicates (methods ending in '?').
# The `ExpectedType` type param should be a `NamedTuple`. # The `ExpectedType` type param should be a `NamedTuple`.
# Each key in the tuple is a predicate (without the '?') to test. # Each key in the tuple is a predicate (without the '?') to test.
# Each value is a a `Tuple` of arguments to pass to the predicate method.
struct PredicateMatcher(ExpectedType) < Matcher struct PredicateMatcher(ExpectedType) < Matcher
# Textual representation of what the matcher expects. # Expected value and label.
# Constructs the label from the type parameters. private getter expected
def label
{{ExpectedType.keys.splat.stringify}} # Creates the matcher with a expected values.
def initialize(@expected : TestValue(ExpectedType))
end end
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(values) # This explains what condition satisfies the matcher.
# The description is used when the one-liner syntax is used.
def description
"is #{expected.label}"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} is not #{expected.label}", **values(snapshot))
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
FailedMatchData.new("#{actual.label} is #{expected.label}", **values(snapshot))
else
SuccessfulMatchData.new
end
end
# Message displayed when the matcher isn't satisifed.
#
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not #{expected.label}"
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)
"#{actual.label} is #{expected.label}"
end
# Captures all of the actual values.
# A `NamedTuple` is returned, with each key being the attribute.
private def snapshot_values(object)
{% begin %}
{
{% for attribute in ExpectedType.keys %}
{{attribute}}: object.{{attribute}}?(*@expected.value[{{attribute.symbolize}}]),
{% end %}
}
{% end %}
end
# Checks if all predicate methods from the snapshot of them are satisified.
private def match?(snapshot)
# Test each predicate and immediately return false if one is false. # Test each predicate and immediately return false if one is false.
{% for attribute in ExpectedType.keys %} {% for attribute in ExpectedType.keys %}
return false unless values[{{attribute.symbolize}}] return false unless snapshot[{{attribute.symbolize}}]
{% end %} {% end %}
# All checks passed if this point is reached. # All checks passed if this point is reached.
true true
end end
# Determines whether the matcher is satisfied with the partial given to it. # Produces the tuple for the failed match data from a snapshot of the predicate methods.
# `MatchData` is returned that contains information about the match. private def values(snapshot)
def match(partial) : MatchData
values = snapshot_values(partial.actual)
MatchData.new(match?(values), values, partial.label)
end
# Captures all of the actual values.
# A `NamedTuple` is returned,
# with each key being the attribute.
private def snapshot_values(actual)
{% begin %} {% begin %}
{ {
{% for attribute in ExpectedType.keys %} {% for attribute in ExpectedType.keys %}
{{attribute}}: actual.{{attribute}}?, {{attribute}}: snapshot[{{attribute.symbolize}}].inspect,
{% end %} {% end %}
} }
{% end %} {% 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)
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} is " + {{ActualType.keys.splat.stringify}}
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}}
end
end
end end
end end

View file

@ -1,27 +0,0 @@
require "./match_data_value"
module Spectator::Matchers
# Prefixes (for output formatting) an actual or expected value.
private class PrefixedMatchDataValue(T) < MatchDataValue
# Value being prefixed.
getter value : T
# Creates the prefixed value.
def initialize(@prefix : String, @value : T)
end
# Outputs the formatted value with a prefix.
def to_s(io)
io << @prefix
io << ' '
@value.inspect(io)
end
# Outputs details of the formatted value with a prefix.
def inspect(io)
io << @prefix
io << ' '
@prefix.inspect(io)
end
end
end

View file

@ -4,64 +4,76 @@ module Spectator::Matchers
# Matcher that tests whether a value is in a given range. # Matcher that tests whether a value is in a given range.
# The `Range#includes?` method is used for this check. # The `Range#includes?` method is used for this check.
struct RangeMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct RangeMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
expected.includes?(actual) # The description is used when the one-liner syntax is used.
end def description
"is in #{expected.label}"
# Determines whether the matcher is satisfied with the partial given to it.
# `MatchData` is returned that contains information about the match.
def match(partial)
actual = partial.actual
matched = match?(actual)
expected_value = @expected
MatchData.new(matched, ExpectedActual.new(expected_value, label, actual, partial.label))
end end
# Returns a new matcher, with the same bounds, but uses an inclusive range. # Returns a new matcher, with the same bounds, but uses an inclusive range.
def inclusive def inclusive
range = Range.new(@expected.begin, @expected.end, exclusive: false) new_range = Range.new(range.begin, range.end, exclusive: false)
RangeMatcher.new(range, label) expected = TestValue.new(new_range, label)
RangeMatcher.new(expected)
end end
# Returns a new matcher, with the same bounds, but uses an exclusive range. # Returns a new matcher, with the same bounds, but uses an exclusive range.
def exclusive def exclusive
range = Range.new(@expected.begin, @expected.end, exclusive: true) new_range = Range.new(range.begin, range.end, exclusive: true)
RangeMatcher.new(range, label) expected = TestValue.new(new_range, label)
RangeMatcher.new(expected)
end end
# Match data specific to this matcher. # Checks whether the matcher is satisifed with the expression given to it.
# This is used when the expected type is a `Range`. private def match?(actual : TestExpression(T)) forall T
private struct MatchData(B, E, ActualType) < MatchData expected.value.includes?(actual.value)
# Creates the match data.
def initialize(matched, @values : ExpectedActual(Range(B, E), ActualType))
super(matched)
end end
# Information about the match. # Message displayed when the matcher isn't satisifed.
def named_tuple #
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not in #{expected.label} (#{exclusivity})"
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)
"#{actual.label} is in #{expected.label} (#{exclusivity})"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
lower: NegatablePrefixedMatchDataValue.new(">=", "<", range.begin), lower: ">= #{range.begin.inspect}",
upper: NegatablePrefixedMatchDataValue.new(exclusive? ? "<" : "<=", exclusive? ? ">=" : ">", range.end), upper: "#{exclusive? ? "<" : "<="} #{range.end.inspect}",
actual: @values.actual, actual: actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@values.actual_label} is in #{@values.expected_label} (#{exclusivity})" {
end lower: "< #{range.begin.inspect}",
upper: "#{exclusive? ? ">=" : ">"} #{range.end.inspect}",
# Describes the condition that won't satsify the matcher. actual: actual.value.inspect,
# This is informational and displayed to the end-user. }
def negated_message
"#{@values.actual_label} is not in #{@values.expected_label} (#{exclusivity})"
end end
# Gets the expected range. # Gets the expected range.
private def range private def range
@values.expected expected.value
end end
# Indicates whether the range is inclusive or exclusive. # Indicates whether the range is inclusive or exclusive.
@ -75,4 +87,3 @@ module Spectator::Matchers
end end
end end
end end
end

View file

@ -4,44 +4,37 @@ module Spectator::Matchers
# Matcher that tests whether two references are the same. # Matcher that tests whether two references are the same.
# The values are compared with the `Reference#same?` method. # The values are compared with the `Reference#same?` method.
struct ReferenceMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct ReferenceMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
actual.same?(expected) # The description is used when the one-liner syntax is used.
def description
"is #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Checks whether the matcher is satisifed with the expression given to it.
# `MatchData` is returned that contains information about the match. private def match?(actual : TestExpression(T)) forall T
def match(partial) expected.value.same?(actual.value)
values = ExpectedActual.new(partial, self)
MatchData.new(match?(values.actual), values)
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed.
private struct MatchData(ExpectedType, ActualType) < MatchData #
# Creates the match data. # This is only called when `#match?` returns false.
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) #
super(matched) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not #{expected.label}"
end end
# Information about the match. # Message displayed when the matcher isn't satisifed and is negated.
def named_tuple # This is essentially what would satisfy the matcher if it wasn't negated.
{ #
expected: NegatableMatchDataValue.new(@values.expected), # This is only called when `#does_not_match?` returns false.
actual: @values.actual, #
} # The message should typically only contain the test expression labels.
end # Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
# Describes the condition that satisfies the matcher. "#{actual.label} is #{expected.label}"
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} is #{@values.expected_label}"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} is not #{@values.expected_label}"
end
end end
end end
end end

View file

@ -6,68 +6,68 @@ module Spectator::Matchers
# The `ExpectedType` type param should be a `NamedTuple`, # The `ExpectedType` type param should be a `NamedTuple`,
# with each key being the method to check and the value is ignored. # with each key being the method to check and the value is ignored.
struct RespondMatcher(ExpectedType) < Matcher struct RespondMatcher(ExpectedType) < Matcher
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match?(actual) # This explains what condition satisfies the matcher.
# The snapshot did the hard work. # The description is used when the one-liner syntax is used.
# Here just check if all values are true. def description
actual.values.all? "responds to #{label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Actually performs the test against the expression.
# `MatchData` is returned that contains information about the match. def match(actual : TestExpression(T)) : MatchData forall T
def match(partial) snapshot = snapshot_values(actual.value)
values = snapshot_values(partial.actual) if match?(snapshot)
MatchData.new(match?(values), values, partial.label, label) SuccessfulMatchData.new
else
FailedMatchData.new("#{actual.label} does not respond to #{label}", **values(snapshot))
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
snapshot = snapshot_values(actual.value)
if match?(snapshot)
FailedMatchData.new("#{actual.label} responds to #{label}", **values(snapshot))
else
SuccessfulMatchData.new
end
end end
# Captures all of the actual values. # Captures all of the actual values.
# A `NamedTuple` is returned, # A `NamedTuple` is returned, with each key being the attribute.
# with each key being the attribute. private def snapshot_values(object)
private def snapshot_values(actual)
{% begin %} {% begin %}
{ {
{% for method in ExpectedType.keys %} {% for attribute in ExpectedType.keys %}
{{method.stringify}}: actual.responds_to?({{method.symbolize}}), {{attribute}}: object.responds_to?({{attribute.symbolize}}),
{% end %} {% end %}
} }
{% end %} {% end %}
end end
# Textual representation of what the matcher expects. # Checks if all results from the snapshot are satisified.
def label private def match?(snapshot)
# The snapshot did the hard work.
# Here just check if all values are true.
snapshot.values.all?
end
# Produces the tuple for the failed match data from a snapshot of the results.
private def values(snapshot)
{% begin %}
{
{% for attribute in ExpectedType.keys %}
{{attribute}}: snapshot[{{attribute.symbolize}}].inspect,
{% end %}
}
{% end %}
end
# Generated, user-friendly, string for the expected value.
private def label
# Prefix every method name with # and join them with commas. # Prefix every method name with # and join them with commas.
{{ExpectedType.keys.map { |e| "##{e}".id }.splat.stringify}} {{ExpectedType.keys.map { |e| "##{e}".id }.splat.stringify}}
end end
# Match data specific to this matcher.
private struct MatchData(ActualType) < MatchData
# Creates the match data.
def initialize(matched, @actual : ActualType, @actual_label : String, @expected_label : String)
super(matched)
end
# Information about the match.
def named_tuple
{% begin %}
{
{% for method in ActualType.keys %}
{{"responds to #" + method.stringify}}: @actual[{{method.symbolize}}],
{% end %}
}
{% end %}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@actual_label} responds to #{@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 respond to #{@expected_label}"
end
end
end end
end end

View file

@ -0,0 +1,58 @@
require "./value_matcher"
module Spectator::Matchers
# Matcher that tests whether a set has a specified number of elements.
# The set's `#size` method is used for this check.
struct SizeMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# 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
"has size #{expected.label}"
end
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual : TestExpression(T)) forall T
expected.value == actual.value.size
end
# Message displayed when the matcher isn't satisifed.
#
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} does not have #{expected.label} elements"
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)
"#{actual.label} has #{expected.label} elements"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: expected.value.inspect,
actual: actual.value.size.inspect,
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "Not #{expected.value.inspect}",
actual: actual.value.size.inspect,
}
end
end
end

View file

@ -0,0 +1,58 @@
require "./value_matcher"
module Spectator::Matchers
# Matcher that tests whether a set has the same number of elements as another set.
# The set's `#size` method is used for this check.
struct SizeOfMatcher(ExpectedType) < ValueMatcher(ExpectedType)
# 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
"is the same size as #{expected.label}"
end
# Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual : TestExpression(T)) forall T
expected.value.size == actual.value.size
end
# Message displayed when the matcher isn't satisifed.
#
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not the same size as #{expected.label}"
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)
"#{actual.label} is the same size as #{expected.label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{
expected: expected.value.size.inspect,
actual: actual.value.size.inspect,
}
end
# Additional information about the match failure when negated.
# The return value is a NamedTuple with Strings for each value.
private def negated_values(actual)
{
expected: "Not #{expected.value.size.inspect}",
actual: actual.value.size.inspect,
}
end
end
end

View file

@ -0,0 +1,129 @@
require "../test_value"
require "./failed_match_data"
require "./matcher"
require "./successful_match_data"
module Spectator::Matchers
# Provides common methods for matchers.
#
# The `#match` and `#negated_match` methods have an implementation
# that is suitable for most matchers.
# Matchers based on this class need to define `#match?` and `#failure_message`.
# If the matcher can be negated,
# the `#failure_message_when_negated` method needs to be overriden.
# Additionally, the `#does_not_match?` method can be specified
# if there's custom behavior for negated matches.
# If the matcher operates on or has extra data that is useful for debug,
# then the `#values` and `#negated_values` methods can be overriden.
# Finally, define a `#description` message that can be used for the one-liner "it" syntax.
abstract struct StandardMatcher < Matcher
# Actually performs the test against the expression (value or block).
#
# This method calls the abstract `#match?` method.
# If it returns true, then a `SuccessfulMatchData` instance is returned.
# Otherwise, a `FailedMatchData` instance is returned.
# Additionally, `#failure_message` and `#values` are called for a failed match.
def match(actual : TestExpression(T)) : MatchData forall T
if match?(actual)
SuccessfulMatchData.new
else
FailedMatchData.new(failure_message(actual), **values(actual))
end
end
# Performs the test against the expression (value or block), but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
#
# This method calls the abstract `#does_not_match?` method.
# If it returns true, then a `SuccessfulMatchData` instance is returned.
# Otherwise, a `FailedMatchData` instance is returned.
# Additionally, `#failure_message_when_negated` and `#negated_values` are called for a failed match.
def negated_match(actual : TestExpression(T)) : MatchData forall T
if does_not_match?(actual)
SuccessfulMatchData.new
else
FailedMatchData.new(failure_message_when_negated(actual), **negated_values(actual))
end
end
# Message displayed when the matcher isn't satisifed.
#
# This is only called when `#match?` returns false.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private abstract def failure_message(actual : TestExpression(T)) : String forall T
# 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.
#
# A default implementation of this method is provided,
# which causes compilation to fail.
# If the matcher supports negation, it must override this method.
#
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message_when_negated(actual : TestExpression(T)) : String forall T
{% raise "Negation with #{@type.name} is not supported." %}
end
# Checks whether the matcher is satisifed with the expression given to it.
private abstract def match?(actual : TestExpression(T)) : Bool forall T
# If the expectation is negated, then this method is called instead of `#match?`.
#
# The default implementation of this method is to invert the result of `#match?`.
# If the matcher requires custom handling of negated matches,
# then this method should be overriden.
# Remember to override `#failure_message_when_negated` as well.
private def does_not_match?(actual : TestExpression(T)) : Bool forall T
!match?(actual)
end
# Additional information about the match failure.
#
# By default, just the actual value is produced.
# The return value must be a NamedTuple with Strings for each value.
# The tuple can be of any size,
# but the keys must be known at compile-time (as Symbols),
# and the values must be strings.
# Generally, the string values are produced by calling `#inspect` on the relevant object.
# It should look like this:
# ```
# {
# expected: "foo",
# actual: "bar",
# }
# ```
#
# The values should typically only contain the test expression values, not the labels.
# Labeled should be returned by `#failure_message`.
private def values(actual : TestExpression(T)) forall T
{actual: actual.value.inspect}
end
# Additional information about the match failure when negated.
#
# By default, just the actual value is produced (same as `#values`).
# The return value must be a NamedTuple with Strings for each value.
# The tuple can be of any size,
# but the keys must be known at compile-time (as Symbols),
# and the values must be strings.
# Generally, the string values are produced by calling `#inspect` on the relevant object.
# It should look like this:
# ```
# {
# expected: "Not foo",
# actual: "bar",
# }
# ```
#
# The values should typically only contain the test expression values, not the labels.
# Labeled should be returned by `#failure_message_when_negated`.
private def negated_values(actual : TestExpression(T)) forall T
values(actual)
end
end
end

View file

@ -1,89 +1,101 @@
require "./value_matcher" # Checks whether the last element of the value is the expected value.
# This method expects that the actual value is a set (enumerable).require "./value_matcher"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests whether a value, such as a `String` or `Array`, starts with a value. # Matcher that tests whether a value, such as a `String` or `Array`, starts with a value.
# The `starts_with?` method is used if it's defined on the actual type. # The `starts_with?` method is used if it's defined on the actual type.
# Otherwise, it is treated as an `Enumerable` and the `first` value is compared against. # Otherwise, it is treated as an `Enumerable` and the `first` value is compared against.
struct StartWithMatcher(ExpectedType) < ValueMatcher(ExpectedType) struct StartWithMatcher(ExpectedType) < Matcher
# Determines whether the matcher is satisfied with the value given to it. # Expected value and label.
private def match_starts_with?(actual) private getter expected
actual.starts_with?(expected)
# Creates the matcher with an expected value.
def initialize(@expected : TestValue(ExpectedType))
end end
# Determines whether the matcher is satisfied with the value given to it. # Short text about the matcher's purpose.
private def match_first?(actual) # This explains what condition satisfies the matcher.
expected === actual # The description is used when the one-liner syntax is used.
def description
"starts with #{expected.label}"
end end
# Determines whether the matcher is satisfied with the partial given to it. # Actually performs the test against the expression.
# `MatchData` is returned that contains information about the match. def match(actual : TestExpression(T)) : MatchData forall T
def match(partial) if (value = actual.value).responds_to?(:starts_with?)
values = ExpectedActual.new(partial, self) match_starts_with(value, actual.label)
actual = values.actual
if actual.responds_to?(:starts_with?)
StartsWithMatchData.new(match_starts_with?(actual), values)
else else
first = actual.first match_first(value, actual.label)
FirstMatchData.new(match_first?(first), values, first)
end end
end end
# Match data specific to this matcher. # Performs the test against the expression, but inverted.
# This type is used when the actual value responds to `starts_with?`. # A successful match with `#match` should normally fail for this method, and vice-versa.
private struct StartsWithMatchData(ExpectedType, ActualType) < MatchData def negated_match(actual : TestExpression(T)) : MatchData forall T
# Creates the match data. if (value = actual.value).responds_to?(:starts_with?)
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) negated_match_starts_with(value, actual.label)
super(matched) else
end negated_match_first(value, actual.label)
# Information about the match.
def named_tuple
{
expected: NegatableMatchDataValue.new(@values.expected),
actual: @values.actual,
}
end
# Describes the condition that satisfies the matcher.
# This is informational and displayed to the end-user.
def message
"#{@values.actual_label} starts with #{@values.expected_label} (using #starts_with?)"
end
# Describes the condition that won't satsify the matcher.
# This is informational and displayed to the end-user.
def negated_message
"#{@values.actual_label} does not start with #{@values.expected_label} (using #starts_with?)"
end end
end end
# Match data specific to this matcher. # Checks whether the actual value starts with the expected value.
# This type is used when the actual value does not respond to `ends_with?`. # This method expects (and uses) the `#starts_with?` method on the value.
private struct FirstMatchData(ExpectedType, ActualType, FirstType) < MatchData private def match_starts_with(actual_value, actual_label)
# Creates the match data. if actual_value.starts_with?(expected.value)
def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType), @first : FirstType) SuccessfulMatchData.new
super(matched) else
FailedMatchData.new("#{actual_label} does not start with #{expected.label} (using #starts_with?)",
expected: expected.value.inspect,
actual: actual_value.inspect
)
end
end end
# Information about the match. # Checks whether the first element of the value is the expected value.
def named_tuple # This method expects that the actual value is a set (enumerable).
{ private def match_first(actual_value, actual_label)
expected: @values.expected, list = actual_value.to_a
actual: @first, first = list.first
list: @values.actual,
} if expected.value === first
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual_label} does not start with #{expected.label} (using expected === first)",
expected: expected.value.inspect,
actual: first.inspect,
list: list.inspect
)
end
end end
# Describes the condition that satisfies the matcher. # Checks whether the actual value does not start with the expected value.
# This is informational and displayed to the end-user. # This method expects (and uses) the `#starts_with?` method on the value.
def message private def negated_match_starts_with(actual_value, actual_label)
"#{@values.actual_label} starts with #{@values.expected_label} (using expected === actual.first)" if actual_value.starts_with?(expected.value)
FailedMatchData.new("#{actual_label} starts with #{expected.label} (using #starts_with?)",
expected: expected.value.inspect,
actual: actual_value.inspect
)
else
SuccessfulMatchData.new
end
end end
# Describes the condition that won't satsify the matcher. # Checks whether the first element of the value is not the expected value.
# This is informational and displayed to the end-user. # This method expects that the actual value is a set (enumerable).
def negated_message private def negated_match_first(actual_value, actual_label)
"#{@values.actual_label} does not start with #{@values.expected_label} (using expected === actual.first)" list = actual_value.to_a
first = list.first
if expected.value === first
FailedMatchData.new("#{actual_label} starts with #{expected.label} (using expected === first)",
expected: expected.value.inspect,
actual: first.inspect,
list: list.inspect
)
else
SuccessfulMatchData.new
end end
end end
end end

View file

@ -0,0 +1,11 @@
require "./match_data"
module Spectator::Matchers
# Information about a successful match.
struct SuccessfulMatchData < MatchData
# Indicates that the match succeeded.
def matched?
true
end
end
end

View file

@ -1,4 +1,4 @@
require "./value_matcher" require "./standard_matcher"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests whether a value is truthy or falsey. # Matcher that tests whether a value is truthy or falsey.
@ -8,29 +8,18 @@ module Spectator::Matchers
# #
# Additionally, different matchers can be created # Additionally, different matchers can be created
# by using the `#<`, `#<=`, `#>`, `#>=`, `#==`, and `#!=` operators. # by using the `#<`, `#<=`, `#>`, `#>=`, `#==`, and `#!=` operators.
struct TruthyMatcher < Matcher struct TruthyMatcher < StandardMatcher
# Creates the truthy matcher. # Creates the truthy matcher.
# The *truthy* argument should be true to match "truthy" values, # The *truthy* argument should be true to match "truthy" values,
# and false to match "falsey" values. # and false to match "falsey" values.
def initialize(@truthy : Bool) def initialize(@truthy : Bool = true)
end end
# Textual representation of what the matcher expects. # Short text about the matcher's purpose.
def label # This explains what condition satisfies the matcher.
@truthy ? "truthy" : "falsey" # The description is used when the one-liner syntax is used.
end def description
"is #{label}"
# Determines whether the matcher is satisfied with the value given to it.
private def match?(actual)
# Cast value to truthy value and compare.
@truthy == !!actual
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)
actual = partial.actual
MatchData.new(match?(actual), @truthy, actual, partial.label)
end end
# Creates a matcher that checks if a value is less than an expected value. # Creates a matcher that checks if a value is less than an expected value.
@ -38,7 +27,8 @@ module Spectator::Matchers
# ``` # ```
# expect(0).to be < 1 # expect(0).to be < 1
# ``` # ```
def <(expected : ExpectedType) forall ExpectedType def <(value : ExpectedType) forall ExpectedType
expected = TestValue.new(value)
LessThanMatcher.new(expected) LessThanMatcher.new(expected)
end end
@ -47,7 +37,8 @@ module Spectator::Matchers
# ``` # ```
# expect(0).to be <= 1 # expect(0).to be <= 1
# ``` # ```
def <=(expected : ExpectedType) forall ExpectedType def <=(value : ExpectedType) forall ExpectedType
expected = TestValue.new(value)
LessThanEqualMatcher.new(expected) LessThanEqualMatcher.new(expected)
end end
@ -56,7 +47,8 @@ module Spectator::Matchers
# ``` # ```
# expect(2).to be > 1 # expect(2).to be > 1
# ``` # ```
def >(expected : ExpectedType) forall ExpectedType def >(value : ExpectedType) forall ExpectedType
expected = TestValue.new(value)
GreaterThanMatcher.new(expected) GreaterThanMatcher.new(expected)
end end
@ -65,7 +57,8 @@ module Spectator::Matchers
# ``` # ```
# expect(2).to be >= 1 # expect(2).to be >= 1
# ``` # ```
def >=(expected : ExpectedType) forall ExpectedType def >=(value : ExpectedType) forall ExpectedType
expected = TestValue.new(value)
GreaterThanEqualMatcher.new(expected) GreaterThanEqualMatcher.new(expected)
end end
@ -74,7 +67,8 @@ module Spectator::Matchers
# ``` # ```
# expect(0).to be == 0 # expect(0).to be == 0
# ``` # ```
def ==(expected : ExpectedType) forall ExpectedType def ==(value : ExpectedType) forall ExpectedType
expected = TestValue.new(value)
EqualityMatcher.new(expected) EqualityMatcher.new(expected)
end end
@ -83,44 +77,65 @@ module Spectator::Matchers
# ``` # ```
# expect(0).to be != 1 # expect(0).to be != 1
# ``` # ```
def !=(expected : ExpectedType) forall ExpectedType def !=(value : ExpectedType) forall ExpectedType
expected = TestValue.new(value)
InequalityMatcher.new(expected) InequalityMatcher.new(expected)
end end
# Match data specific to this matcher. # Checks whether the matcher is satisifed with the expression given to it.
private struct MatchData(ActualType) < MatchData private def match?(actual : TestExpression(T)) forall T
# Creates the match data. @truthy == !!actual.value
def initialize(matched, @truthy : Bool, @actual : ActualType, @actual_label : String)
super(matched)
end end
# Information about the match. # Message displayed when the matcher isn't satisifed.
def named_tuple #
truthy = "Not false or nil" # This is only called when `#match?` returns false.
falsey = "false or nil" #
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is #{negated_label}"
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)
"#{actual.label} is #{label}"
end
# Additional information about the match failure.
# The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: AlternativeMatchDataValue.new(@truthy ? truthy : falsey, @truthy ? falsey : truthy), expected: @truthy ? "Not false or nil" : "false or nil",
actual: @actual, actual: actual.value.inspect,
truthy: !!@actual, truthy: !!actual.value.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@actual_label} is #{expected_label}" {
expected: @truthy ? "false or nil" : "Not false or nil",
actual: actual.value.inspect,
truthy: !!actual.value.inspect,
}
end end
# Describes the condition that won't satsify the matcher. # Generated, user-friendly, string for the expected value.
# This is informational and displayed to the end-user. private def label
def negated_message
"#{@actual_label} is not #{expected_label}"
end
# Textual representation of what the matcher expects.
private def expected_label
@truthy ? "truthy" : "falsey" @truthy ? "truthy" : "falsey"
end end
# Generated, user-friendly, string for the unexpected value.
private def negated_label
@truthy ? "falsey" : "truthy"
end end
end end
end end

View file

@ -3,51 +3,56 @@ require "./matcher"
module Spectator::Matchers module Spectator::Matchers
# Matcher that tests a value is of a specified type. # Matcher that tests a value is of a specified type.
# The values are compared with the `Object#is_a?` method. # The values are compared with the `Object#is_a?` method.
struct TypeMatcher(Expected) < Matcher struct TypeMatcher(Expected) < StandardMatcher
# Textual representation of what the matcher expects. # Short text about the matcher's purpose.
# The `Expected` type param will be used to populate the label. # This explains what condition satisfies the matcher.
def label # The description is used when the one-liner syntax is used.
Expected.to_s def description
"is as #{Expected}"
end end
# Determines whether the matcher is satisfied with the value given to it. # Checks whether the matcher is satisifed with the expression given to it.
private def match?(actual) private def match?(actual : TestExpression(T)) forall T
actual.is_a?(Expected) actual.value.is_a?(Expected)
end end
# Determines whether the matcher is satisfied with the partial given to it. # Message displayed when the matcher isn't satisifed.
# `MatchData` is returned that contains information about the match. #
def match(partial) # This is only called when `#match?` returns false.
actual = partial.actual #
MatchData(Expected, typeof(actual)).new(match?(actual), partial.label) # The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message(actual)
"#{actual.label} is not a #{Expected}"
end end
# Match data specific to this matcher. # Message displayed when the matcher isn't satisifed and is negated.
private struct MatchData(ExpectedType, ActualType) < MatchData # This is essentially what would satisfy the matcher if it wasn't negated.
# Creates the match data. #
def initialize(matched, @actual_label : String) # This is only called when `#does_not_match?` returns false.
super(matched) #
# The message should typically only contain the test expression labels.
# Actual values should be returned by `#values`.
private def failure_message_when_negated(actual)
"#{actual.label} is a #{Expected}"
end end
# Information about the match. # Additional information about the match failure.
def named_tuple # The return value is a NamedTuple with Strings for each value.
private def values(actual)
{ {
expected: NegatableMatchDataValue.new(ExpectedType), expected: Expected.to_s,
actual: ActualType, actual: actual.value.class.inspect,
} }
end end
# Describes the condition that satisfies the matcher. # Additional information about the match failure when negated.
# This is informational and displayed to the end-user. # The return value is a NamedTuple with Strings for each value.
def message private def negated_values(actual)
"#{@actual_label} is a #{ExpectedType}" {
end expected: "Not #{Expected}",
actual: actual.value.class.inspect,
# 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 a #{ExpectedType}"
end
end end
end end
end end

View file

@ -0,0 +1,75 @@
require "./value_matcher"
module Spectator::Matchers
# Matcher for checking that the contents of one array (or similar type)
# has the exact same contents as another, but in any order.
struct UnorderedArrayMatcher(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.
# This explains what condition satisfies the matcher.
# The description is used when the one-liner syntax is used.
def description
"contains #{expected.label} in any order"
end
# Actually performs the test against the expression.
def match(actual : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
missing, extra = array_diff(expected_elements, actual_elements)
if missing.empty? && extra.empty?
SuccessfulMatchData.new
else
FailedMatchData.new("#{actual_label} does not contain #{expected.label} (unordered)",
expected: expected_elements.inspect,
actual: actual_elements.inspect,
missing: missing.inspect,
extra: extra.inspect,
)
end
end
# Performs the test against the expression, but inverted.
# A successful match with `#match` should normally fail for this method, and vice-versa.
def negated_match(actual : TestExpression(T)) : MatchData forall T
actual_elements = actual.value.to_a
expected_elements = expected.value.to_a
missing, extra = array_diff(expected_elements, actual_elements)
if missing.empty? && extra.empty?
FailedMatchData.new("#{actual_label} contains #{expected.label} (unordered)",
expected: "Not #{expected_elements.inspect}",
actual: actual_elements.inspect,
)
else
SuccessfulMatchData.new
end
end
# Finds the difference of two unordered arrays.
# Returns a tuple of arrays - missing from *actual* and extra in *actual*.
private def array_diff(expected, actual)
extra = actual.dup
missing = [] of ExpectedType
# OPTIMIZE: Not very efficient at finding the difference.
expected.each do |item|
index = extra.index(item)
if index
extra.delete_at(index)
else
missing << item
end
end
{missing, extra}
end
end
end

View file

@ -1,30 +1,67 @@
require "./matcher" require "./standard_matcher"
module Spectator::Matchers module Spectator::Matchers
# Category of matcher that uses a value. # Category of matcher that uses a value.
# Matchers of this type expect that a SUT applies to the value in some way. # Matchers of this type expect that a SUT applies to the value in some way.
# Sub-types must implement `Matcher#match?`, `Matcher#message`, and `Matcher#negated_message`. #
abstract struct ValueMatcher(ExpectedType) < Matcher # Matchers based on this class need to define `#match?` and `#failure_message`.
# Textual representation of what the matcher expects. # If the matcher can be negated,
# This shouldn't be used in the conditional logic, # the `#failure_message_when_negated` method needs to be overriden.
# but for verbose output to help the end-user. # Additionally, the `#does_not_match?` method can be specified
getter label # if there's custom behavior for negated matches.
# If the matcher operates on or has extra data that is useful for debug,
# then the `#values` and `#negated_values` methods can be overriden.
# Finally, define a `#description` message that can be used for the one-liner "it" syntax.
#
# The failure messages should typically only contain the test expression labels.
# Actual values should be returned by `#values` and `#negated_values`.
abstract struct ValueMatcher(ExpectedType) < StandardMatcher
# Expected value. # Expected value.
# Sub-types may use this value to test the expectation and generate message strings. # Sub-types may use this value to test the expectation and generate message strings.
getter expected private getter expected
# Creates the value matcher. # Creates the value matcher.
# The label should be a string representation of the expectation.
# The expected value is stored for later use. # The expected value is stored for later use.
def initialize(@expected : ExpectedType, @label : String) def initialize(@expected : TestValue(ExpectedType))
end end
# Creates the value matcher. # Additional information about the match failure.
# The label is generated by calling `#to_s` on the expected value. #
# The expected value is stored for later use. # By default, just the actual and expected values are produced.
def initialize(expected : ExpectedType) # The return value must be a NamedTuple with Strings for each value.
initialize(expected, expected.to_s) # The tuple can be of any size,
# but the keys must be known at compile-time (as Symbols),
# and the values must be strings.
# Generally, the string values are produced by calling `#inspect` on the relevant object.
# It should look like this:
# ```
# {
# expected: "foo",
# actual: "bar",
# }
# ```
private def values(actual : TestExpression(T)) forall T
super.merge(expected: expected.value.inspect)
end
# Additional information about the match failure when negated.
#
# By default, just the actual and expected values are produced (same as `#values`).
# However, the expected value is prefixed with the word "Not".
# The return value must be a NamedTuple with Strings for each value.
# The tuple can be of any size,
# but the keys must be known at compile-time (as Symbols),
# and the values must be strings.
# Generally, the string values are produced by calling `#inspect` on the relevant object.
# It should look like this:
# ```
# {
# expected: "Not foo",
# actual: "bar",
# }
# ```
private def negated_values(actual : TestExpression(T)) forall T
super.merge(expected: "Not #{expected.value.inspect}")
end end
end end
end end

View file

@ -21,13 +21,17 @@ class Object
# However, since this isn't a macro and we can't "look behind" this method call # However, since this isn't a macro and we can't "look behind" this method call
# to see what it was invoked on, the argument is an empty string. # to see what it was invoked on, the argument is an empty string.
# Additionally, the source file and line can't be obtained. # Additionally, the source file and line can't be obtained.
::Spectator::Expectations::ValueExpectationPartial.new(self, __FILE__, __LINE__).to(matcher) actual = ::Spectator::TestValue.new(self)
source = ::Spectator::Source.new(__FILE__, __LINE__)
::Spectator::Expectations::ExpectationPartial.new(actual, source).to(matcher)
end end
# Works the same as `#should` except the condition is inverted. # Works the same as `#should` except the condition is inverted.
# When `#should` succeeds, this method will fail, and vice-versa. # When `#should` succeeds, this method will fail, and vice-versa.
def should_not(matcher : ::Spectator::Matchers::Matcher) def should_not(matcher : ::Spectator::Matchers::Matcher)
::Spectator::Expectations::ValueExpectationPartial.new(self, __FILE__, __LINE__).to_not(matcher) actual = ::Spectator::TestValue.new(self)
source = ::Spectator::Source.new(__FILE__, __LINE__)
::Spectator::Expectations::ExpectationPartial.new(actual, source).to_not(matcher)
end end
end end
@ -35,12 +39,16 @@ struct Proc(*T, R)
# Extension method to create an expectation for a block of code (proc). # Extension method to create an expectation for a block of code (proc).
# Depending on the matcher, the proc may be executed multiple times. # Depending on the matcher, the proc may be executed multiple times.
def should(matcher : ::Spectator::Matchers::Matcher) def should(matcher : ::Spectator::Matchers::Matcher)
::Spectator::Expectations::BlockExpectationPartial.new(self, __FILE__, __LINE__).to(matcher) actual = ::Spectator::TestBlock.new(self)
source = ::Spectator::Source.new(__FILE__, __LINE__)
::Spectator::Expectations::ExpectationPartial.new(actual, source).to(matcher)
end end
# Works the same as `#should` except the condition is inverted. # Works the same as `#should` except the condition is inverted.
# When `#should` succeeds, this method will fail, and vice-versa. # When `#should` succeeds, this method will fail, and vice-versa.
def should_not(matcher : ::Spectator::Matchers::Matcher) def should_not(matcher : ::Spectator::Matchers::Matcher)
::Spectator::Expectations::BlockExpectationPartial.new(self, __FILE__, __LINE__).to_not(matcher) actual = ::Spectator::TestBlock.new(self)
source = ::Spectator::Source.new(__FILE__, __LINE__)
::Spectator::Expectations::BlockExpectationPartial.new(actual, source).to_not(matcher)
end end
end end

View file

@ -0,0 +1,48 @@
require "./test_expression"
module Spectator
# Captures an block from a test and its label.
struct TestBlock(ReturnType) < TestExpression(ReturnType)
# Calls the block and retrieves the value.
def value : ReturnType
@proc.call
end
# Creates the block expression with a custom label.
# Typically the label is the code in the block/proc.
def initialize(@proc : -> ReturnType, label : String)
super(label)
end
def self.create(proc : -> T, label : String) forall T
{% if T.id == "ReturnType".id %}
wrapper = ->{ proc.call; nil }
TestBlock(Nil).new(wrapper, label)
{% else %}
TestBlock(T).new(proc, label)
{% end %}
end
# Creates the block expression with a generic label.
# This is used for the "should" syntax and when the label doesn't matter.
def initialize(@proc : -> ReturnType)
super("<Proc>")
end
def self.create(proc : -> T) forall T
{% if T.id == "ReturnType".id %}
wrapper = ->{ proc.call; nil }
TestBlock(Nil).new(wrapper)
{% else %}
TestBlock(T).new(proc)
{% end %}
end
# Reports complete information about the expression.
def inspect(io)
io << label
io << " -> "
io << value
end
end
end

View file

@ -0,0 +1,25 @@
module Spectator
# Base type for capturing an expression from a test.
abstract struct TestExpression(T)
# User-friendly string displayed for the actual expression being tested.
# For instance, in the expectation:
# ```
# expect(foo).to eq(bar)
# ```
# This property will be "foo".
# It will be the literal string "foo",
# and not the actual value of the foo.
getter label : String
# Creates the common base of the expression.
def initialize(@label)
end
abstract def value : T
# String representation of the expression.
def to_s(io)
io << label
end
end
end

View file

@ -0,0 +1,29 @@
require "./test_expression"
module Spectator
# Captures a value from a test and its label.
struct TestValue(T) < TestExpression(T)
# Actual value.
getter value : T
# Creates the expression value with a custom label.
def initialize(@value : T, label : String)
super(label)
end
# Creates the expression with a stringified value.
# This is used for the "should" syntax and when the label doesn't matter.
def initialize(@value : T)
super(@value.to_s)
end
# Reports complete information about the expression.
def inspect(io)
io << label
io << '='
io << @value
end
end
alias LabeledValue = TestValue(String)
end