diff --git a/README.md b/README.md index 6e98ccd..a828f2a 100644 --- a/README.md +++ b/README.md @@ -288,25 +288,25 @@ Items not marked as completed may have partial implementations. - [X] `contain` - [X] `have` - [X] `contain_exactly` - - [ ] `contain_exactly.in_any_order` + - [X] `contain_exactly.in_any_order` - [X] `match_array` - - [ ] `match_array.in_any_order` + - [X] `match_array.in_any_order` - [X] `start_with` - [X] `end_with` - [X] `be_empty` - [X] `have_key` - [X] `have_value` - - [ ] `all` + - [X] `all` - [ ] `all_satisfy` - [X] Truthy matchers - `be`, `be_true`, `be_truthy`, `be_false`, `be_falsey`, `be_nil` - [X] Error matchers - `raise_error` - [ ] Yield matchers - `yield_control[.times]`, `yield_with_args[.times]`, `yield_with_no_args[.times]`, `yield_successive_args` - [ ] Output matchers - `output[.to_stdout|.to_stderr]` + - [X] Predicate matchers - `be_x`, `have_x` - [ ] Misc. matchers - - [ ] `exist` - [X] `match` - [ ] `satisfy` - - [ ] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]` + - [X] `change[.by|.from[.to]|.to|.by_at_least|.by_at_most]` - [X] `have_attributes` - [ ] Compound - `and`, `or` - [ ] Runner diff --git a/shard.yml b/shard.yml index 861b00b..363562c 100644 --- a/shard.yml +++ b/shard.yml @@ -1,12 +1,12 @@ name: spectator -version: 0.7.2 +version: 0.8.0 description: | A feature-rich spec testing framework for Crystal with similarities to RSpec. authors: - Michael Miller -crystal: 0.28.0 +crystal: 0.30.1 license: MIT diff --git a/spec/expectation_failed_spec.cr b/spec/expectation_failed_spec.cr index 152967e..54c5d1d 100644 --- a/spec/expectation_failed_spec.cr +++ b/spec/expectation_failed_spec.cr @@ -13,7 +13,7 @@ describe Spectator::ExpectationFailed do it "is the same as the expectation's #actual_message" do expectation = new_unsatisfied_expectation error = Spectator::ExpectationFailed.new(expectation) - error.message.should eq(expectation.actual_message) + error.message.should eq(expectation.failure_message) end end end diff --git a/spec/expectations/block_expectation_partial_spec.cr b/spec/expectations/block_expectation_partial_spec.cr deleted file mode 100644 index ea54f3a..0000000 --- a/spec/expectations/block_expectation_partial_spec.cr +++ /dev/null @@ -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 diff --git a/spec/expectations/expectation_spec.cr b/spec/expectations/expectation_spec.cr index 176fd72..06540fc 100644 --- a/spec/expectations/expectation_spec.cr +++ b/spec/expectations/expectation_spec.cr @@ -5,18 +5,22 @@ describe Spectator::Expectations::Expectation do context "with a successful match" do it "is true" do 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. - expectation = Spectator::Expectations::Expectation.new(match_data, false) + expectation = Spectator::Expectations::Expectation.new(match_data, partial.source) expectation.satisfied?.should be_true end context "when negated" do it "is false" 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) + matcher = new_matcher(value) + partial = new_partial(value) + 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 end end @@ -24,126 +28,24 @@ describe Spectator::Expectations::Expectation do context "with an unsuccessful match" 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. - expectation = Spectator::Expectations::Expectation.new(match_data, false) + expectation = Spectator::Expectations::Expectation.new(match_data, partial.source) expectation.satisfied?.should be_false end context "when negated" do it "is true" 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) + matcher = new_matcher(42) + partial = new_partial(777) + 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 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 diff --git a/spec/expectations/value_expectation_partial_spec.cr b/spec/expectations/value_expectation_partial_spec.cr deleted file mode 100644 index b7422fb..0000000 --- a/spec/expectations/value_expectation_partial_spec.cr +++ /dev/null @@ -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 diff --git a/spec/helpers/expectations_helper.cr b/spec/helpers/expectations_helper.cr index ff7c339..0096f84 100644 --- a/spec/helpers/expectations_helper.cr +++ b/spec/helpers/expectations_helper.cr @@ -1,30 +1,38 @@ # Utility methods for creating expectations, partials, and matchers. 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 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 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 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 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 def new_expectation(expected : ExpectedType = 123, actual : ActualType = 123) forall ExpectedType, ActualType partial = new_partial(actual, "foo") matcher = new_matcher(expected, "bar") - match_data = matcher.match(partial) - Spectator::Expectations::Expectation.new(match_data, false) + match_data = matcher.match(partial.actual) + Spectator::Expectations::Expectation.new(match_data, partial.source) end def new_satisfied_expectation(value : T = 123) forall T diff --git a/spec/matchers/array_matcher_spec.cr b/spec/matchers/array_matcher_spec.cr deleted file mode 100644 index 49a97ef..0000000 --- a/spec/matchers/array_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/attributes_matcher_spec.cr b/spec/matchers/attributes_matcher_spec.cr deleted file mode 100644 index 36bedc5..0000000 --- a/spec/matchers/attributes_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/case_matcher_spec.cr b/spec/matchers/case_matcher_spec.cr deleted file mode 100644 index 573a30b..0000000 --- a/spec/matchers/case_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/collection_matcher_spec.cr b/spec/matchers/collection_matcher_spec.cr deleted file mode 100644 index f848792..0000000 --- a/spec/matchers/collection_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/contain_matcher_spec.cr b/spec/matchers/contain_matcher_spec.cr deleted file mode 100644 index a10af48..0000000 --- a/spec/matchers/contain_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/empty_matcher_spec.cr b/spec/matchers/empty_matcher_spec.cr deleted file mode 100644 index eea43bc..0000000 --- a/spec/matchers/empty_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/end_with_matcher_spec.cr b/spec/matchers/end_with_matcher_spec.cr deleted file mode 100644 index d9894d5..0000000 --- a/spec/matchers/end_with_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/equality_matcher_spec.cr b/spec/matchers/equality_matcher_spec.cr deleted file mode 100644 index b997a0f..0000000 --- a/spec/matchers/equality_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/exception_matcher_spec.cr b/spec/matchers/exception_matcher_spec.cr deleted file mode 100644 index b5c31b4..0000000 --- a/spec/matchers/exception_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/greater_than_equal_matcher_spec.cr b/spec/matchers/greater_than_equal_matcher_spec.cr deleted file mode 100644 index c76641d..0000000 --- a/spec/matchers/greater_than_equal_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/greater_than_matcher_spec.cr b/spec/matchers/greater_than_matcher_spec.cr deleted file mode 100644 index 1375cff..0000000 --- a/spec/matchers/greater_than_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/have_key_matcher_spec.cr b/spec/matchers/have_key_matcher_spec.cr deleted file mode 100644 index cbcf6f6..0000000 --- a/spec/matchers/have_key_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/have_matcher_spec.cr b/spec/matchers/have_matcher_spec.cr deleted file mode 100644 index f4e917f..0000000 --- a/spec/matchers/have_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/have_value_matcher_spec.cr b/spec/matchers/have_value_matcher_spec.cr deleted file mode 100644 index 78ba281..0000000 --- a/spec/matchers/have_value_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/inequality_matcher_spec.cr b/spec/matchers/inequality_matcher_spec.cr deleted file mode 100644 index 1dcdcfc..0000000 --- a/spec/matchers/inequality_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/less_than_equal_matcher_spec.cr b/spec/matchers/less_than_equal_matcher_spec.cr deleted file mode 100644 index 75d648f..0000000 --- a/spec/matchers/less_than_equal_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/less_than_matcher_spec.cr b/spec/matchers/less_than_matcher_spec.cr deleted file mode 100644 index df5acaf..0000000 --- a/spec/matchers/less_than_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/nil_matcher_spec.cr b/spec/matchers/nil_matcher_spec.cr deleted file mode 100644 index 3250d02..0000000 --- a/spec/matchers/nil_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/predicate_matcher_spec.cr b/spec/matchers/predicate_matcher_spec.cr deleted file mode 100644 index 5a9b2c2..0000000 --- a/spec/matchers/predicate_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/range_matcher_spec.cr b/spec/matchers/range_matcher_spec.cr deleted file mode 100644 index 5f473a5..0000000 --- a/spec/matchers/range_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/reference_matcher_spec.cr b/spec/matchers/reference_matcher_spec.cr deleted file mode 100644 index b029090..0000000 --- a/spec/matchers/reference_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/respond_matcher_spec.cr b/spec/matchers/respond_matcher_spec.cr deleted file mode 100644 index 3685464..0000000 --- a/spec/matchers/respond_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/start_with_matcher_spec.cr b/spec/matchers/start_with_matcher_spec.cr deleted file mode 100644 index de626a5..0000000 --- a/spec/matchers/start_with_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/truthy_matcher_spec.cr b/spec/matchers/truthy_matcher_spec.cr deleted file mode 100644 index 7ef3970..0000000 --- a/spec/matchers/truthy_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/spec/matchers/type_matcher_spec.cr b/spec/matchers/type_matcher_spec.cr deleted file mode 100644 index 81a430b..0000000 --- a/spec/matchers/type_matcher_spec.cr +++ /dev/null @@ -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 diff --git a/src/spectator/dsl/example_dsl.cr b/src/spectator/dsl/example_dsl.cr index f518b53..5fea5a7 100644 --- a/src/spectator/dsl/example_dsl.cr +++ b/src/spectator/dsl/example_dsl.cr @@ -1,3 +1,7 @@ +require "../expectations/expectation_partial" +require "../source" +require "../test_block" +require "../test_value" require "./matcher_dsl" module Spectator::DSL @@ -19,7 +23,9 @@ module Spectator::DSL # 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. 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 # Starts an expectation on a block of code. @@ -49,9 +55,6 @@ module Spectator::DSL {% raise "Argument or block must be provided to expect" %} {% end %} - # Create a proc to capture the block. - %proc = ->({{block.args.splat}}) {{block}} - # 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: @@ -67,13 +70,19 @@ module Spectator::DSL # 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 = block.body.id.split('.')[1..-1].join('.') %} - %partial = %proc.partial(subject) - ::Spectator::Expectations::BlockExpectationPartial.new(%partial, {{"#" + method_name}}, {{_source_file}}, {{_source_line}}) - {% else %} + %proc = ->{ subject.{{method_name.id}} } + %test_block = ::Spectator::TestBlock.create(%proc, {{"#" + method_name}}) + {% elsif block.args.empty? %} # In this case, it looks like the short-hand method syntax wasn't used. - # Just drop in the proc as-is. - ::Spectator::Expectations::BlockExpectationPartial.new(%proc, "`" + {{block.body.stringify}} + "`", {{_source_file}}, {{_source_line}}) + # Capture the block as a proc and pass along. + %proc = ->{{block}} + %test_block = ::Spectator::TestBlock.create(%proc, {{"`" + block.body.stringify + "`"}}) + {% else %} + {% raise "Unexpected block arguments in expect call" %} {% end %} + + %source = ::Spectator::Source.new({{_source_file}}, {{_source_line}}) + ::Spectator::Expectations::ExpectationPartial.new(%test_block, %source) end # Starts an expectation. diff --git a/src/spectator/dsl/matcher_dsl.cr b/src/spectator/dsl/matcher_dsl.cr index 7d14334..bcfbec1 100644 --- a/src/spectator/dsl/matcher_dsl.cr +++ b/src/spectator/dsl/matcher_dsl.cr @@ -1,4 +1,6 @@ require "../matchers" +require "../test_block" +require "../test_value" module Spectator::DSL # Methods for defining matchers for expectations. @@ -12,7 +14,8 @@ module Spectator::DSL # expect(1 + 2).to eq(3) # ``` 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 # Indicates that some value should not equal another. @@ -24,7 +27,8 @@ module Spectator::DSL # expect(1 + 2).to ne(5) # ``` 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 # Indicates that some value when compared to another satisfies an operator. @@ -44,7 +48,7 @@ module Spectator::DSL # expect("foo").to be_truthy # ``` macro be - ::Spectator::Matchers::TruthyMatcher.new(true) + ::Spectator::Matchers::TruthyMatcher.new end # Indicates that some object should be the same as another. @@ -58,7 +62,8 @@ module Spectator::DSL # expect(obj.dup).to_not be(obj) # ``` 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 # Indicates that some value should be of a specified type. @@ -115,7 +120,8 @@ module Spectator::DSL # expect(3 - 1).to be_lt(3) # ``` 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 # 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) # ``` 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 # Indicates that some value should be greater than another. @@ -139,7 +146,8 @@ module Spectator::DSL # expect(3 + 1).to be_gt(3) # ``` 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 # 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) # ``` 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 # Indicates that some value should match another. @@ -168,7 +177,8 @@ module Spectator::DSL # expect({:foo, 5}).to match({Symbol, Int32}) # ``` 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 # Indicates that some value should be true. @@ -202,7 +212,7 @@ module Spectator::DSL # expect(true).to be_truthy # ``` macro be_truthy - ::Spectator::Matchers::TruthyMatcher.new(true) + ::Spectator::Matchers::TruthyMatcher.new end # Indicates that some value should be falsey. @@ -258,7 +268,8 @@ module Spectator::DSL # NOTE: Do not attempt to mix the two use cases. # It likely won't work and will result in a compilation error. 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 # 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) # ``` macro be_between(min, max) - :Spectator::Matchers::RangeMatcher.new( - Range.new({{min}}, {{max}}), - [{{min.stringify}}, {{max.stringify}}].join(" to ") - ) + %range = Range.new({{min}}, {{max}})) + %label = [{{min.stringify}}, {{max.stringify}}].join(" to ") + %test_value = ::Spectator::TestValue.new(%range, %label) + :Spectator::Matchers::RangeMatcher.new(%test_value) end # 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/) # ``` 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 # 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/) # ``` 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 # 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) # ``` 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 # 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) # ``` 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 # 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") # ``` 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 # ditto @@ -442,7 +458,8 @@ module Spectator::DSL # expect({"lucky" => 7}).to have_value(7) # ``` 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 # ditto @@ -457,7 +474,8 @@ module Spectator::DSL # expect([1, 2, 3]).to contain_exactly(1, 2, 3) # ``` 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 # 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]) # ``` 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 # 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) # ``` 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 # Indicates that some block should raise an error. @@ -561,7 +685,7 @@ module Spectator::DSL end # Used to create predicate matchers. - # Any missing method that starts with 'be_' will be handled. + # Any missing method that starts with 'be_' or 'have_' will be handled. # All other method names will be ignored and raise a compile-time error. # # This can be used to simply check a predicate method that ends in '?'. @@ -570,14 +694,38 @@ module Spectator::DSL # expect("foobar").to be_ascii_only # # Is equivalent to: # expect("foobar".ascii_only?).to be_true + # + # expect("foobar").to_not have_back_references + # # Is equivalent to: + # expect("foobar".has_back_references?).to_not be_true # ``` macro method_missing(call) {% if call.name.starts_with?("be_") %} - {% method_name = call.name[3..-1] %} # Remove be_ prefix. - ::Spectator::Matchers::PredicateMatcher(NamedTuple({{method_name}}: Nil)).new + # Remove `be_` prefix. + {% method_name = call.name[3..-1] %} + {% matcher = "PredicateMatcher" %} + {% elsif call.name.starts_with?("have_") %} + # Remove `have_` prefix. + {% method_name = call.name[5..-1] %} + {% matcher = "HavePredicateMatcher" %} {% else %} - {% raise "Undefined local variable or method '#{call}'" %} + {% raise "Undefined local variable or method '#{call}'" %} {% end %} + + descriptor = { {{method_name}}: Tuple.new({{call.args.splat}}) } + label = String::Builder.new({{method_name.stringify}}) + {% unless call.args.empty? %} + label << '(' + {% for arg, index in call.args %} + label << {{arg}} + {% if index < call.args.size - 1 %} + label << ", " + {% end %} + {% end %} + label << ')' + {% end %} + test_value = ::Spectator::TestValue.new(descriptor, label.to_s) + ::Spectator::Matchers::{{matcher.id}}.new(test_value) end end end diff --git a/src/spectator/dsl/structure_dsl.cr b/src/spectator/dsl/structure_dsl.cr index 48d6134..8010d30 100644 --- a/src/spectator/dsl/structure_dsl.cr +++ b/src/spectator/dsl/structure_dsl.cr @@ -789,7 +789,53 @@ module Spectator::DSL # 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 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 block should return the value. # @@ -838,32 +884,43 @@ module Spectator::DSL # end # ``` macro let(name, &block) - # Create a block that returns the value. - let!(%value) {{block}} + {% if block.is_a?(Nop) %} + # Assignment variant. + @%value = {{name.value}} - # Wrapper to hold the value. - # This will be nil if the value hasn't been referenced yet. - # After being referenced, the cached value will be stored in a wrapper. - @%wrapper : ::Spectator::Internals::ValueWrapper? + def {{name.target}} + @%value + end + {% else %} + # Block variant. - # Method for returning the value. - def {{name.id}} - # Check if the value is cached. - # The wrapper will be nil if it isn't. - if (wrapper = @%wrapper) - # It is cached, return that value. - # Unwrap it from the wrapper variable. - # Here we use typeof to get around the issue - # that the macro has no idea what type the value is. - wrapper.unsafe_as(::Spectator::Internals::TypedValueWrapper(typeof(%value))).value - else - # The value isn't cached, - # Construct it and store it in the wrapper. - %value.tap do |value| - @%wrapper = ::Spectator::Internals::TypedValueWrapper(typeof(%value)).new(value) + # Create a block that returns the value. + let!(%value) {{block}} + + # Wrapper to hold the value. + # This will be nil if the value hasn't been referenced yet. + # After being referenced, the cached value will be stored in a wrapper. + @%wrapper : ::Spectator::Internals::ValueWrapper? + + # Method for returning the value. + def {{name.id}} + # Check if the value is cached. + # The wrapper will be nil if it isn't. + if (wrapper = @%wrapper) + # It is cached, return that value. + # Unwrap it from the wrapper variable. + # Here we use typeof to get around the issue + # that the macro has no idea what type the value is. + wrapper.unsafe_as(::Spectator::Internals::TypedValueWrapper(typeof(%value))).value + else + # The value isn't cached, + # Construct it and store it in the wrapper. + %value.tap do |value| + @%wrapper = ::Spectator::Internals::TypedValueWrapper(typeof(%value)).new(value) + end end end - end + {% end %} end # The noisier sibling to `#let`. diff --git a/src/spectator/expectation_failed.cr b/src/spectator/expectation_failed.cr index 35d7d5f..05246da 100644 --- a/src/spectator/expectation_failed.cr +++ b/src/spectator/expectation_failed.cr @@ -9,7 +9,7 @@ module Spectator # Creates the exception. # The exception string is generated from the expecation message. def initialize(@expectation) - super(@expectation.actual_message) + super(@expectation.failure_message) end end end diff --git a/src/spectator/expectations/block_expectation_partial.cr b/src/spectator/expectations/block_expectation_partial.cr deleted file mode 100644 index 3adbec7..0000000 --- a/src/spectator/expectations/block_expectation_partial.cr +++ /dev/null @@ -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("", source_file, source_line) - end - end -end diff --git a/src/spectator/expectations/expectation.cr b/src/spectator/expectations/expectation.cr index 536aff0..300f134 100644 --- a/src/spectator/expectations/expectation.cr +++ b/src/spectator/expectations/expectation.cr @@ -1,57 +1,67 @@ +require "../matchers/failed_match_data" +require "../matchers/match_data" +require "../source" + module Spectator::Expectations - # Ties together a partial, matcher, and their outcome. - class Expectation - # Populates the base portiion of the expectation with values. - # The *negated* flag should be true if the expectation is inverted. - # The *match_data* is the value returned by `Spectator::Matcher#match` - # when the expectation was evaluated. - # The *negated* flag and `MatchData#matched?` flag - # are mutually-exclusive in this context. - def initialize(@match_data : Spectator::Matchers::MatchData, @negated : Bool) + # Result of evaluating a matcher on an expectation partial. + struct Expectation + # Location where this expectation was defined. + getter source : Source + + # Creates the expectation. + def initialize(@match_data : Matchers::MatchData, @source : Source) end - # Indicates whether the expectation was satisifed. - # This is true if: - # - The matcher was satisified and the expectation is not negated. - # - The matcher wasn't satisfied and the expectation is negated. + # Indicates whether the matcher was satisified. def satisfied? - @match_data.matched? ^ @negated + @match_data.matched? end - # Information about the match. - # Returned value and type will something that has key-value pairs (like a `NamedTuple`). + # Indicates that the expectation was not satisified. + 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 - @match_data.values.tap do |labeled_values| - 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 + values?.not_nil! end # Creates the JSON representation of the expectation. def to_json(json : ::JSON::Builder) json.object do + json.field("source") { @source.to_json(json) } json.field("satisfied", satisfied?) - json.field("expected", expected_message) - json.field("actual", actual_message) - json.field("values") do - json.object do - values.each do |labeled_value| - json.field(labeled_value.label, labeled_value.value.to_s) - end + if (failed = @match_data.as?(Matchers::FailedMatchData)) + 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.object do + failed.values.each do |pair| + json.field(pair.first, pair.last) end end end diff --git a/src/spectator/expectations/expectation_partial.cr b/src/spectator/expectations/expectation_partial.cr index 0238a8e..1cc8831 100644 --- a/src/spectator/expectations/expectation_partial.cr +++ b/src/spectator/expectations/expectation_partial.cr @@ -1,39 +1,34 @@ +require "../matchers/match_data" +require "../source" +require "../test_expression" + module Spectator::Expectations - # Base class for all expectation partials. - # An "expectation partial" stores part of an expectation (obviously). - # The part of the expectation this class covers is the actual value. + # Stores part of an expectation (obviously). + # The part of the expectation this type covers is the actual value and source. # This can also cover a block's behavior. - # Sub-types of this class are returned by the `DSL::ExampleDSL.expect` call. - abstract struct ExpectationPartial - # 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 + struct ExpectationPartial(T) + # The actual value being tested. + # This also contains its label. + getter actual : TestExpression(T) - # Source file the expectation originated from. - getter source_file : String + # Location where this expectation was defined. + getter source : Source - # Line number in the source file the expectation originated from. - getter source_line : Int32 - - # Creates the base of the partial. - private def initialize(@label, @source_file, @source_line) + # Creates the partial. + def initialize(@actual : TestExpression(T), @source : Source) end # Asserts that some criteria defined by the matcher is satisfied. def to(matcher) : Nil - report(eval(matcher)) + match_data = matcher.match(@actual) + report(match_data) end # Asserts that some criteria defined by the matcher is not satisfied. # This is effectively the opposite of `#to`. def to_not(matcher) : Nil - report(eval(matcher, true)) + match_data = matcher.negated_match(@actual) + report(match_data) end # ditto @@ -42,14 +37,9 @@ module Spectator::Expectations to_not(matcher) 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. - private def report(expectation : Expectation) + private def report(match_data : Matchers::MatchData) + expectation = Expectation.new(match_data, @source) Internals::Harness.current.report_expectation(expectation) end end diff --git a/src/spectator/expectations/value_expectation_partial.cr b/src/spectator/expectations/value_expectation_partial.cr deleted file mode 100644 index 791bce5..0000000 --- a/src/spectator/expectations/value_expectation_partial.cr +++ /dev/null @@ -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 diff --git a/src/spectator/formatting/failure_block.cr b/src/spectator/formatting/failure_block.cr index 4b7276e..b542775 100644 --- a/src/spectator/formatting/failure_block.cr +++ b/src/spectator/formatting/failure_block.cr @@ -53,7 +53,7 @@ module Spectator::Formatting # Produces a list of unsatisfied expectations and their values. private def unsatisfied_expectations(indent) @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.increase do matcher_values(indent, expectation) diff --git a/src/spectator/formatting/failure_junit_test_case.cr b/src/spectator/formatting/failure_junit_test_case.cr index d2f9d2d..964e673 100644 --- a/src/spectator/formatting/failure_junit_test_case.cr +++ b/src/spectator/formatting/failure_junit_test_case.cr @@ -19,7 +19,7 @@ module Spectator::Formatting private def content(xml) super @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) end end @@ -28,8 +28,8 @@ module Spectator::Formatting # Adds the expectation values to the failure block. private def expectation_values(labeled_values, xml) labeled_values.each do |entry| - label = entry.label - value = entry.value + label = entry.first + value = entry.last xml.text("#{label}: #{value}\n") end end diff --git a/src/spectator/formatting/match_data_value_pair.cr b/src/spectator/formatting/match_data_value_pair.cr index 95fa57f..dd7f255 100644 --- a/src/spectator/formatting/match_data_value_pair.cr +++ b/src/spectator/formatting/match_data_value_pair.cr @@ -1,8 +1,8 @@ module Spectator::Formatting # A single labeled value from the `Spectator::Matchers::MatchData#value` method. - private struct MatchDataValuePair(T) + private struct MatchDataValuePair # Creates the pair formatter. - def initialize(@key : Symbol, @value : T, @padding : Int32) + def initialize(@key : Symbol, @value : String, @padding : Int32) end # Appends the pair to the output. @@ -10,7 +10,7 @@ module Spectator::Formatting @padding.times { io << ' ' } io << @key io << ": " - @value.inspect(io) + io << @value end end end diff --git a/src/spectator/formatting/match_data_values.cr b/src/spectator/formatting/match_data_values.cr index 51b5f1e..bf194a9 100644 --- a/src/spectator/formatting/match_data_values.cr +++ b/src/spectator/formatting/match_data_values.cr @@ -2,22 +2,22 @@ module Spectator::Formatting # Produces a `MatchDataValuePair` for each key-value pair # from `Spectator::Matchers::MatchData#values`. private struct MatchDataValues - include Enumerable(MatchDataValuePair) + include Enumerable(Tuple(Symbol, String)) @max_key_length : Int32 # Creates the values mapper. - def initialize(@values : Array(Spectator::Matchers::MatchDataLabeledValue)) - @max_key_length = @values.map(&.label.to_s.size).max + def initialize(@values : Array(Tuple(Symbol, String))) + @max_key_length = @values.map(&.first.to_s.size).max end # Yields pairs that can be printed to output. def each @values.each do |labeled_value| - key = labeled_value.label + key = labeled_value.first key_length = key.to_s.size 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 diff --git a/src/spectator/matchers/all_matcher.cr b/src/spectator/matchers/all_matcher.cr new file mode 100644 index 0000000..d43bda8 --- /dev/null +++ b/src/spectator/matchers/all_matcher.cr @@ -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 diff --git a/src/spectator/matchers/alternative_match_data_value.cr b/src/spectator/matchers/alternative_match_data_value.cr deleted file mode 100644 index b600312..0000000 --- a/src/spectator/matchers/alternative_match_data_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/array_matcher.cr b/src/spectator/matchers/array_matcher.cr index 9a7bc2e..f3c3940 100644 --- a/src/spectator/matchers/array_matcher.cr +++ b/src/spectator/matchers/array_matcher.cr @@ -1,108 +1,113 @@ -require "./value_matcher" +require "./failed_match_data" +require "./matcher" +require "./successful_match_data" +require "./unordered_array_matcher" module Spectator::Matchers # Matcher for checking that the contents of one array (or similar type) # has the exact same contents as another and in the same order. - struct ArrayMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # 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.to_a - values = ExpectedActual.new(expected, label, actual, partial.label) - if values.expected.size == values.actual.size + struct ArrayMatcher(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 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 - values.expected.zip(values.actual) do |expected, element| - return ContentMatchData.new(index, values) unless expected == element + expected_elements.zip(actual_elements) do |expected_element, actual_element| + return index unless expected_element == actual_element index += 1 end - IdenticalMatchData.new(values) + true else - SizeMatchData.new(values) + false end end - # Common functionality for all match data for this matcher. - private abstract struct CommonMatchData(ExpectedType, ActualType) < MatchData - # Creates the match data. - def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) - super(matched) - end - - # Basic 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} contains exactly #{@values.expected_label}" - end + # Produces match data for a failure when the array sizes differ. + private def failed_size_mismatch(expected_elements, actual_elements, actual_label) + FailedMatchData.new("#{actual_label} does not contain exactly #{expected.label} (size mismatch)", + expected: expected_elements.inspect, + actual: actual_elements.inspect, + "expected size": expected_elements.size.to_s, + "actual size": actual_elements.size.to_s + ) 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 + # Produces match data for a failure when the array content is mismatched. + 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: expected_elements[index].inspect, + actual: actual_elements[index].inspect, + index: index.to_s + ) 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 + # Produces match data for a failure when the arrays are identical, but they shouldn't be (negation). + private def failed_content_identical(expected_elements, actual_elements, actual_label) + FailedMatchData.new("#{actual_label} contains exactly #{expected.label}", + expected: "Not #{expected_elements.inspect}", + actual: actual_elements.inspect + ) end end end diff --git a/src/spectator/matchers/attributes_matcher.cr b/src/spectator/matchers/attributes_matcher.cr index 12e7d49..a447d4a 100644 --- a/src/spectator/matchers/attributes_matcher.cr +++ b/src/spectator/matchers/attributes_matcher.cr @@ -1,72 +1,83 @@ -require "./value_matcher" +require "../test_value" +require "./failed_match_data" +require "./matcher" +require "./successful_match_data" module Spectator::Matchers # Matcher that tests that multiple attributes match specified conditions. # The attributes are tested with the === operator. # The `ExpectedType` type param should be a `NamedTuple`. - struct AttributesMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - # Test each attribute and immediately return false if a comparison fails. - {% for attribute in ExpectedType.keys %} - return false unless expected[{{attribute.symbolize}}] === actual[{{attribute.symbolize}}] - {% end %} + # Each key in the tuple is the attribute/method name, + # and the corresponding value is the expected value to match against. + struct AttributesMatcher(ExpectedType) < Matcher + # Expected value and label. + private getter expected - # All checks passed if this point is reached. - true + # Creates the matcher with an expected value. + def initialize(@expected : TestValue(ExpectedType)) 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_values = snapshot_values(partial.actual) - values = ExpectedActual.new(expected, label, actual_values, partial.label) - MatchData.new(match?(actual_values), values) + # 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 attributes #{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 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 # Captures all of the actual values. - # A `NamedTuple` is returned, - # with each key being the attribute. - private def snapshot_values(actual) + # A `NamedTuple` is returned, with each key being the attribute. + private def snapshot_values(object) {% begin %} { {% for attribute in ExpectedType.keys %} - {{attribute}}: actual.{{attribute}}, + {{attribute}}: object.{{attribute}}, {% end %} } {% end %} end - # 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 + # Checks if all attributes from the snapshot of them are satisified. + private def match?(snapshot) + # Test that every attribute has the expected value. + {% for attribute in ExpectedType.keys %} + return false unless expected.value[{{attribute.symbolize}}] === snapshot[{{attribute.symbolize}}] + {% end %} - # Information about the match. - def named_tuple - {% begin %} - { - {% for attribute in ExpectedType.keys %} - {{"expected " + attribute.stringify}}: NegatableMatchDataValue.new(@values.expected[{{attribute.symbolize}}]), - {{"actual " + attribute.stringify}}: @values.actual[{{attribute.symbolize}}], - {% end %} - } + # At this point, none of the checks failed, so the match was successful. + true + end + + # Produces the tuple for the failed match data from a snapshot of the attributes. + private def values(snapshot) + {% begin %} + { + {% for attribute in ExpectedType.keys %} + {{"expected " + attribute.stringify}}: expected.value[{{attribute.symbolize}}].inspect, + {{"actual " + attribute.stringify}}: snapshot[{{attribute.symbolize}}].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 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 diff --git a/src/spectator/matchers/case_matcher.cr b/src/spectator/matchers/case_matcher.cr index d321468..3ba55f3 100644 --- a/src/spectator/matchers/case_matcher.cr +++ b/src/spectator/matchers/case_matcher.cr @@ -4,44 +4,37 @@ module Spectator::Matchers # Common matcher that tests whether two values semantically equal each other. # The values are compared with the === operator. struct CaseMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - expected === actual + # 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 + "matches #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + expected.value === actual.value end - # 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 + # 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 match #{expected.label}" + end - # 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} 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 + # 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} matched #{expected.label}" end end end diff --git a/src/spectator/matchers/change_exact_matcher.cr b/src/spectator/matchers/change_exact_matcher.cr new file mode 100644 index 0000000..48dcf9f --- /dev/null +++ b/src/spectator/matchers/change_exact_matcher.cr @@ -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 diff --git a/src/spectator/matchers/change_from_matcher.cr b/src/spectator/matchers/change_from_matcher.cr new file mode 100644 index 0000000..82a9267 --- /dev/null +++ b/src/spectator/matchers/change_from_matcher.cr @@ -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 diff --git a/src/spectator/matchers/change_matcher.cr b/src/spectator/matchers/change_matcher.cr new file mode 100644 index 0000000..ad8ca89 --- /dev/null +++ b/src/spectator/matchers/change_matcher.cr @@ -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 diff --git a/src/spectator/matchers/change_relative_matcher.cr b/src/spectator/matchers/change_relative_matcher.cr new file mode 100644 index 0000000..adb4cdc --- /dev/null +++ b/src/spectator/matchers/change_relative_matcher.cr @@ -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 diff --git a/src/spectator/matchers/change_to_matcher.cr b/src/spectator/matchers/change_to_matcher.cr new file mode 100644 index 0000000..65f1ce5 --- /dev/null +++ b/src/spectator/matchers/change_to_matcher.cr @@ -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 diff --git a/src/spectator/matchers/collection_matcher.cr b/src/spectator/matchers/collection_matcher.cr index f57d46c..bfa88f2 100644 --- a/src/spectator/matchers/collection_matcher.cr +++ b/src/spectator/matchers/collection_matcher.cr @@ -1,19 +1,41 @@ +require "../test_value" +require "./range_matcher" require "./value_matcher" module Spectator::Matchers # Matcher for checking that a value is in a collection of other values. struct CollectionMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - expected.includes?(actual) + # 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 in #{expected.label}" 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 - matched = match?(actual) - MatchData.new(matched, ExpectedActual.new(partial, self)) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + expected.value.includes?(actual.value) + 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 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 # 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. # 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. # @@ -29,39 +51,12 @@ module Spectator::Matchers # and have upper and lower bounds equal to *center* plus and minus diff. # The range will be inclusive. def of(center) - diff = @expected + diff = @expected.value lower = center - diff upper = center + diff range = Range.new(lower, upper) - RangeMatcher.new(range, "#{center} +/- #{label}") - end - - # 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 + test_value = TestValue.new(range, "#{center} +/- #{expected.label}") + RangeMatcher.new(test_value) end end end diff --git a/src/spectator/matchers/contain_matcher.cr b/src/spectator/matchers/contain_matcher.cr index 739d1e9..53389fc 100644 --- a/src/spectator/matchers/contain_matcher.cr +++ b/src/spectator/matchers/contain_matcher.cr @@ -4,46 +4,48 @@ module Spectator::Matchers # 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. struct ContainMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - expected.all? do |item| - actual.includes?(item) + # 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}" + 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 - # Determines whether the matcher is satisfied with the partial given to it. - # `MatchData` is returned that contains information about the match. - def match(partial) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # 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 match #{expected.label}" end - # 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 + # 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} contains #{expected.label}" + end - # Information about the match. - def named_tuple - { - subset: NegatableMatchDataValue.new(@values.expected), - superset: @values.actual, - } - 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 + # Additional information about the match failure. + # The return value is a NamedTuple with Strings for each value. + private def values(actual) + { + subset: expected.value.inspect, + superset: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/dummy_match_data.cr b/src/spectator/matchers/dummy_match_data.cr deleted file mode 100644 index 4a6a693..0000000 --- a/src/spectator/matchers/dummy_match_data.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/empty_matcher.cr b/src/spectator/matchers/empty_matcher.cr index b477ec4..a8ccfe9 100644 --- a/src/spectator/matchers/empty_matcher.cr +++ b/src/spectator/matchers/empty_matcher.cr @@ -3,46 +3,38 @@ require "./matcher" module Spectator::Matchers # Matcher that tests whether a collection is empty. # The values are checked with the `empty?` method. - struct EmptyMatcher < Matcher - # Textual representation of what the matcher expects. - def label - "empty?" + struct EmptyMatcher < StandardMatcher + # 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 empty" 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 - matched = actual.empty? - MatchData.new(matched, actual, partial.label) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value.empty? end - # Match data specific to this matcher. - private struct MatchData(T) < MatchData - # Creates the match data. - def initialize(matched, @actual : T, @actual_label : String) - super(matched) - 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 empty" + end - # Information about the match. - def named_tuple - { - expected: NegatableMatchDataValue.new([] of Nil), - actual: @actual, - } - end - - # Describes the condition that satisfies the matcher. - # 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 + # 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 empty" end end end diff --git a/src/spectator/matchers/end_with_matcher.cr b/src/spectator/matchers/end_with_matcher.cr index 92655d5..ed76381 100644 --- a/src/spectator/matchers/end_with_matcher.cr +++ b/src/spectator/matchers/end_with_matcher.cr @@ -1,89 +1,102 @@ -require "./value_matcher" +require "./failed_match_data" +require "./matcher" +require "./successful_match_data" module Spectator::Matchers # 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. # Otherwise, it is treated as an `Indexable` and the `last` value is compared against. - struct EndWithMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match_ends_with?(actual) - actual.ends_with?(expected) + struct EndWithMatcher(ExpectedType) < Matcher + # Expected value and label. + private getter expected + + # Creates the matcher with an expected value. + def initialize(@expected : TestValue(ExpectedType)) end - # Determines whether the matcher is satisfied with the value given to it. - private def match_last?(actual) - expected === actual + # 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 + "ends with #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - actual = values.actual - if actual.responds_to?(:ends_with?) - EndsWithMatchData.new(match_ends_with?(actual), values) + # Actually performs the test against the expression. + def match(actual : TestExpression(T)) : MatchData forall T + if (value = actual.value).responds_to?(:ends_with?) + match_ends_with(value, actual.label) else - last = actual.last - LastMatchData.new(match_last?(last), values, last) + match_last(value, actual.label) end end - # Match data specific to this matcher. - # This type is used when the actual value responds to `ends_with?`. - private struct EndsWithMatchData(ExpectedType, ActualType) < MatchData - # Creates the match data. - def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) - super(matched) - end - - # 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?)" + # 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 + if actual.value.responds_to?(:ends_with?) + negated_match_ends_with(actual) + else + negated_match_last(actual) end end - # Match data specific to this matcher. - # This type is used when the actual value does not respond to `ends_with?`. - private struct LastMatchData(ExpectedType, ActualType, LastType) < MatchData - # Creates the match data. - def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType), @last : LastType) - super(matched) + # Checks whether the actual value ends with the expected value. + # This method expects (and uses) the `#ends_with?` method on the value. + private def match_ends_with(actual_value, actual_label) + if actual_value.ends_with?(expected.value) + SuccessfulMatchData.new + else + FailedMatchData.new("#{actual_label} does not end with #{expected.label} (using #ends_with?)", + expected: expected.value.inspect, + actual: actual_value.inspect + ) end + end - # Information about the match. - def named_tuple - { - expected: @values.expected, - actual: @last, - list: @values.actual, - } + # Checks whether the last element of the value is the expected value. + # This method expects that the actual value is a set (enumerable). + private def match_last(actual_value, actual_label) + list = actual_value.to_a + last = list.last + + 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 - # 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 expected === actual.last)" + # Checks whether the actual value does not end with the expected value. + # This method expects (and uses) the `#ends_with?` method on the value. + private def negated_match_ends_with(actual) + 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 - # 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 expected === actual.last)" + # Checks whether the last element of the value is not the expected value. + # This method expects that the actual value is a set (enumerable). + private def negated_match_last(actual) + 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 diff --git a/src/spectator/matchers/equality_matcher.cr b/src/spectator/matchers/equality_matcher.cr index 0e97e60..5d58454 100644 --- a/src/spectator/matchers/equality_matcher.cr +++ b/src/spectator/matchers/equality_matcher.cr @@ -4,44 +4,37 @@ module Spectator::Matchers # Common matcher that tests whether two values equal each other. # The values are compared with the == operator. struct EqualityMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual == expected + # 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 + "equals #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + expected.value == actual.value end - # 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 + # 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 equal #{expected.label}" + end - # 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} 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 + # 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} equals #{expected.label}" end end end diff --git a/src/spectator/matchers/exception_matcher.cr b/src/spectator/matchers/exception_matcher.cr index 066922c..981845c 100644 --- a/src/spectator/matchers/exception_matcher.cr +++ b/src/spectator/matchers/exception_matcher.cr @@ -1,11 +1,93 @@ -require "./value_matcher" +require "../test_value" +require "./failed_match_data" +require "./matcher" +require "./successful_match_data" module Spectator::Matchers # Matcher that tests whether an exception is raised. - struct ExceptionMatcher(ExceptionType, ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(exception) - exception.is_a?(ExceptionType) && (expected.nil? || expected === exception.message) + struct ExceptionMatcher(ExceptionType, ExpectedType) < Matcher + # Expected value and label. + private getter expected + + # 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 # Runs a block of code and returns the exception it threw. @@ -20,104 +102,21 @@ module Spectator::Matchers exception 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. def self.create(exception_type : T.class, label : String) forall T ExceptionMatcher(T, Nil).new end # Creates a new exception matcher with a message check. - def self.create(expected, label : String) - ExceptionMatcher(Exception, typeof(expected)).new(expected, label) + def self.create(value, label : String) + expected = TestValue.new(value, label) + ExceptionMatcher(Exception, typeof(value)).new(expected) end # Creates a new exception matcher with a type and message check. - def self.create(exception_type : T.class, expected, label : String) forall T - ExceptionMatcher(T, typeof(expected)).new(expected, label) - end - - # 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 + def self.create(exception_type : T.class, value, label : String) forall T + expected = TestValue.new(value, label) + ExceptionMatcher(T, typeof(value)).new(expected) end end end diff --git a/src/spectator/matchers/expected_actual.cr b/src/spectator/matchers/expected_actual.cr deleted file mode 100644 index d4efe63..0000000 --- a/src/spectator/matchers/expected_actual.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/failed_match_data.cr b/src/spectator/matchers/failed_match_data.cr new file mode 100644 index 0000000..f1925ce --- /dev/null +++ b/src/spectator/matchers/failed_match_data.cr @@ -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 diff --git a/src/spectator/matchers/generic_match_data_value.cr b/src/spectator/matchers/generic_match_data_value.cr deleted file mode 100644 index 9069489..0000000 --- a/src/spectator/matchers/generic_match_data_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/greater_than_equal_matcher.cr b/src/spectator/matchers/greater_than_equal_matcher.cr index 4ac07aa..5f4e20b 100644 --- a/src/spectator/matchers/greater_than_equal_matcher.cr +++ b/src/spectator/matchers/greater_than_equal_matcher.cr @@ -4,44 +4,55 @@ module Spectator::Matchers # Matcher that tests whether one value is greater than or equal to another. # The values are compared with the >= operator. struct GreaterThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual >= expected + # 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 + "greater than or equal to #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value >= expected.value end - # 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 + # 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 less than #{expected.label}" + end - # Information about the match. - def named_tuple - { - expected: NegatablePrefixedMatchDataValue.new(">=", "<", @values.expected), - actual: @values.actual, - } - 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 greater than or equal to #{expected.label}" + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@values.actual_label} is greater than or equal to #{@values.expected_label} (using >=)" - 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.inspect, + } + 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 less than #{@values.expected_label} (using >=)" - 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: "< #{expected.value.inspect}", + actual: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/greater_than_matcher.cr b/src/spectator/matchers/greater_than_matcher.cr index a96947e..c51303b 100644 --- a/src/spectator/matchers/greater_than_matcher.cr +++ b/src/spectator/matchers/greater_than_matcher.cr @@ -4,44 +4,55 @@ module Spectator::Matchers # Matcher that tests whether one value is greater than another. # The values are compared with the > operator. struct GreaterThanMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual > expected + # 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 + "greater than #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value > expected.value end - # 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 + # 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 less than or equal to #{expected.label}" + end - # Information about the match. - def named_tuple - { - expected: NegatablePrefixedMatchDataValue.new(">", "<=", @values.expected), - actual: @values.actual, - } - 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 greater than #{expected.label}" + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@values.actual_label} is greater than #{@values.expected_label} (using >)" - 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.inspect, + } + 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 less than or equal to #{@values.expected_label} (using >)" - 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: "<= #{expected.value.inspect}", + actual: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/have_key_matcher.cr b/src/spectator/matchers/have_key_matcher.cr index c27743a..e3f2560 100644 --- a/src/spectator/matchers/have_key_matcher.cr +++ b/src/spectator/matchers/have_key_matcher.cr @@ -4,45 +4,48 @@ module Spectator::Matchers # Matcher that tests whether a `Hash` (or similar type) has a given key. # The set is checked with the `has_key?` method. struct HaveKeyMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual.has_key?(expected) + # 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 key #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value.has_key?(expected.value) end - # 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 + # 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 key #{expected.label}" + end - # Information about the match. - def named_tuple - actual = @values.actual - { - key: NegatableMatchDataValue.new(@values.expected), - actual: actual.responds_to?(:keys) ? actual.keys : actual, - } - 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 key #{expected.label}" + 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 + # 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: expected.value.inspect, + actual: set.inspect, + } end end end diff --git a/src/spectator/matchers/have_matcher.cr b/src/spectator/matchers/have_matcher.cr index 7fb2a99..5dd0151 100644 --- a/src/spectator/matchers/have_matcher.cr +++ b/src/spectator/matchers/have_matcher.cr @@ -5,68 +5,69 @@ module Spectator::Matchers # For a `String`, the `includes?` method is used. # Otherwise, it expects an `Enumerable` and iterates over each item until === is true. struct HaveMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - # True is returned if the match was successful, false otherwise. - private def match?(actual) - if actual.is_a?(String) - match_string?(actual) + # 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 + "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 - match_enumerable?(actual) + match_enumerable?(value) end end # Checks if a `String` matches the expected values. # The `includes?` method is used for this check. - private def match_string?(actual) - expected.all? do |item| - actual.includes?(item) + private def match_string?(value) + expected.value.all? do |item| + value.includes?(item) end end # Checks if an `Enumerable` matches the expected values. # The `===` operator is used on every item. - private def match_enumerable?(actual) - array = actual.to_a - expected.all? do |item| - array.any? do |elem| - item === elem + private def match_enumerable?(value) + array = value.to_a + expected.value.all? do |item| + array.any? do |element| + item === element end 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # 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 include #{expected.label}" end - # 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 + # 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} includes #{expected.label}" + end - # Information about the match. - def named_tuple - { - subset: NegatableMatchDataValue.new(@values.expected), - superset: @values.actual, - } - 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 + # Additional information about the match failure. + # The return value is a NamedTuple with Strings for each value. + private def values(actual) + { + subset: expected.value.inspect, + superset: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/have_predicate_matcher.cr b/src/spectator/matchers/have_predicate_matcher.cr new file mode 100644 index 0000000..dedc70e --- /dev/null +++ b/src/spectator/matchers/have_predicate_matcher.cr @@ -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 diff --git a/src/spectator/matchers/have_value_matcher.cr b/src/spectator/matchers/have_value_matcher.cr index a59d1f4..9c722f5 100644 --- a/src/spectator/matchers/have_value_matcher.cr +++ b/src/spectator/matchers/have_value_matcher.cr @@ -4,45 +4,48 @@ module Spectator::Matchers # Matcher that tests whether a `Hash` (or similar type) has a given value. # The set is checked with the `has_value?` method. struct HaveValueMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual.has_value?(expected) + # 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 value #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value.has_value?(expected.value) end - # 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 + # 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 value #{expected.label}" + end - # Information about the match. - def named_tuple - actual = @values.actual - { - value: NegatableMatchDataValue.new(@values.expected), - actual: actual.responds_to?(:values) ? actual.values : actual, - } - 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 value #{expected.label}" + 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 + # 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: expected.value.inspect, + actual: set.inspect, + } end end end diff --git a/src/spectator/matchers/inequality_matcher.cr b/src/spectator/matchers/inequality_matcher.cr index 71010fb..7f175f1 100644 --- a/src/spectator/matchers/inequality_matcher.cr +++ b/src/spectator/matchers/inequality_matcher.cr @@ -4,44 +4,55 @@ module Spectator::Matchers # Matcher that tests whether two values do not equal each other. # The values are compared with the != operator. struct InequalityMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual != expected + # 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 not equal to #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + expected.value != actual.value end - # 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 + # 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 equal to #{expected.label}" + end - # Information about the match. - def named_tuple - { - expected: NegatablePrefixedMatchDataValue.new("Not", "", @values.expected), - actual: @values.actual, - } - 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 not equal to #{expected.label}" + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@values.actual_label} is not #{@values.expected_label} (using !=)" - end + # Additional information about the match failure. + # The return value is a NamedTuple with Strings for each value. + private def values(actual) + { + expected: "Not #{expected.value.inspect}", + actual: actual.value.inspect, + } + 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 #{@values.expected_label} (using !=)" - 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: expected.value.inspect, + actual: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/less_than_equal_matcher.cr b/src/spectator/matchers/less_than_equal_matcher.cr index ff3fc10..900c7a4 100644 --- a/src/spectator/matchers/less_than_equal_matcher.cr +++ b/src/spectator/matchers/less_than_equal_matcher.cr @@ -4,44 +4,55 @@ module Spectator::Matchers # Matcher that tests whether one value is less than or equal to another. # The values are compared with the <= operator. struct LessThanEqualMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual <= expected + # 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 + "less than or equal to #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value <= expected.value end - # 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 + # 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 greater than #{expected.label}" + end - # Information about the match. - def named_tuple - { - expected: NegatablePrefixedMatchDataValue.new("<=", ">", @values.expected), - actual: @values.actual, - } - 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 less than or equal to #{expected.label}" + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@values.actual_label} is less than or equal to #{@values.expected_label} (using <=)" - 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.inspect, + } + 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 greater than #{@values.expected_label} (using <=)" - 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: "> #{expected.value.inspect}", + actual: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/less_than_matcher.cr b/src/spectator/matchers/less_than_matcher.cr index 3cb12e6..5f2ec2c 100644 --- a/src/spectator/matchers/less_than_matcher.cr +++ b/src/spectator/matchers/less_than_matcher.cr @@ -4,44 +4,55 @@ module Spectator::Matchers # Matcher that tests whether one value is less than another. # The values are compared with the < operator. struct LessThanMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual < expected + # 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 + "less than #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value < expected.value end - # 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 + # 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 greater than or equal to #{expected.label}" + end - # Information about the match. - def named_tuple - { - expected: NegatablePrefixedMatchDataValue.new("<", ">=", @values.expected), - actual: @values.actual, - } - 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 less than #{expected.label}" + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@values.actual_label} is less than #{@values.expected_label} (using <)" - 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.inspect, + } + 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 greater than or equal to #{@values.expected_label} (using <)" - 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: ">= #{expected.value.inspect}", + actual: actual.value.inspect, + } end end end diff --git a/src/spectator/matchers/match_data.cr b/src/spectator/matchers/match_data.cr index 4595e33..47b444c 100644 --- a/src/spectator/matchers/match_data.cr +++ b/src/spectator/matchers/match_data.cr @@ -1,46 +1,7 @@ -require "./match_data_labeled_value" -require "./match_data_value" -require "./generic_match_data_value" - module Spectator::Matchers - # Information regarding a expectation parial and matcher. - # `Matcher#match` will return a sub-type of this. + # Information about the outcome of a match. abstract struct MatchData - # Indicates whether the matcher was satisified with the expectation partial. - getter? 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 + # Indicates whether the match as successful or not. + abstract def matched? : Bool end end diff --git a/src/spectator/matchers/match_data_labeled_value.cr b/src/spectator/matchers/match_data_labeled_value.cr deleted file mode 100644 index 2a39095..0000000 --- a/src/spectator/matchers/match_data_labeled_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/match_data_value.cr b/src/spectator/matchers/match_data_value.cr deleted file mode 100644 index 21629fd..0000000 --- a/src/spectator/matchers/match_data_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/matcher.cr b/src/spectator/matchers/matcher.cr index 8586918..f5d0d4c 100644 --- a/src/spectator/matchers/matcher.cr +++ b/src/spectator/matchers/matcher.cr @@ -5,13 +5,21 @@ module Spectator::Matchers # A matcher looks at something produced by the SUT # and evaluates whether it is correct or not. abstract struct Matcher - # Textual representation of what the matcher expects. - # This shouldn't be used in the conditional logic, - # but for verbose output to help the end-user. - abstract def label : String + # 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. + # ``` + # 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. - # True is returned if the match was successful, false otherwise. - abstract def match(partial) : MatchData + # Actually performs the test against the expression (value or block). + abstract def match(actual : TestExpression(T)) : MatchData forall T + + # 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 diff --git a/src/spectator/matchers/negatable_match_data_value.cr b/src/spectator/matchers/negatable_match_data_value.cr deleted file mode 100644 index 62abf49..0000000 --- a/src/spectator/matchers/negatable_match_data_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/negatable_prefixed_match_data_value.cr b/src/spectator/matchers/negatable_prefixed_match_data_value.cr deleted file mode 100644 index 9cc6138..0000000 --- a/src/spectator/matchers/negatable_prefixed_match_data_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/nil_matcher.cr b/src/spectator/matchers/nil_matcher.cr index fd5021b..343fe79 100644 --- a/src/spectator/matchers/nil_matcher.cr +++ b/src/spectator/matchers/nil_matcher.cr @@ -3,46 +3,38 @@ require "./matcher" module Spectator::Matchers # Common matcher that tests whether a value is nil. # The `Object#nil?` method is used for this. - struct NilMatcher < Matcher - # Textual representation of what the matcher expects. - def label - "nil?" + struct NilMatcher < StandardMatcher + # 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 nil" 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 - matched = actual.nil? - MatchData.new(matched, actual, partial.label) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value.nil? end - # Match data specific to this matcher. - private struct MatchData(T) < MatchData - # Creates the match data. - def initialize(matched, @actual : T, @actual_label : String) - super(matched) - 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 nil" + end - # Information about the match. - def named_tuple - { - expected: NegatableMatchDataValue.new(nil), - actual: @actual, - } - end - - # Describes the condition that satisfies the matcher. - # 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 + # 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 nil" end end end diff --git a/src/spectator/matchers/predicate_matcher.cr b/src/spectator/matchers/predicate_matcher.cr index 41f9f44..46d3d94 100644 --- a/src/spectator/matchers/predicate_matcher.cr +++ b/src/spectator/matchers/predicate_matcher.cr @@ -4,65 +4,96 @@ module Spectator::Matchers # Matcher that tests one or more predicates (methods ending in '?'). # The `ExpectedType` type param should be a `NamedTuple`. # Each key in the tuple is a predicate (without the '?') to test. + # Each value is a a `Tuple` of arguments to pass to the predicate method. struct PredicateMatcher(ExpectedType) < Matcher - # Textual representation of what the matcher expects. - # Constructs the label from the type parameters. - def label - {{ExpectedType.keys.splat.stringify}} + # Expected value and label. + private getter expected + + # Creates the matcher with a expected values. + def initialize(@expected : TestValue(ExpectedType)) end - # Determines whether the matcher is satisfied with the value given to it. - private def match?(values) + # 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 #{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. {% for attribute in ExpectedType.keys %} - return false unless values[{{attribute.symbolize}}] + return false unless snapshot[{{attribute.symbolize}}] {% end %} # All checks passed if this point is reached. true end - # Determines whether the matcher is satisfied with the partial given to it. - # `MatchData` is returned that contains information about the match. - def match(partial) : MatchData - values = snapshot_values(partial.actual) - MatchData.new(match?(values), values, partial.label) - end - - # Captures all of the actual values. - # A `NamedTuple` is returned, - # with each key being the attribute. - private def snapshot_values(actual) + # 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}}: actual.{{attribute}}?, + {{attribute}}: snapshot[{{attribute.symbolize}}].inspect, {% 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 diff --git a/src/spectator/matchers/prefixed_match_data_value.cr b/src/spectator/matchers/prefixed_match_data_value.cr deleted file mode 100644 index feea8cc..0000000 --- a/src/spectator/matchers/prefixed_match_data_value.cr +++ /dev/null @@ -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 diff --git a/src/spectator/matchers/range_matcher.cr b/src/spectator/matchers/range_matcher.cr index ad05da3..6eba568 100644 --- a/src/spectator/matchers/range_matcher.cr +++ b/src/spectator/matchers/range_matcher.cr @@ -4,75 +4,86 @@ module Spectator::Matchers # Matcher that tests whether a value is in a given range. # The `Range#includes?` method is used for this check. struct RangeMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - expected.includes?(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 - matched = match?(actual) - expected_value = @expected - MatchData.new(matched, ExpectedActual.new(expected_value, label, actual, partial.label)) + # 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 in #{expected.label}" end # Returns a new matcher, with the same bounds, but uses an inclusive range. def inclusive - range = Range.new(@expected.begin, @expected.end, exclusive: false) - RangeMatcher.new(range, label) + new_range = Range.new(range.begin, range.end, exclusive: false) + expected = TestValue.new(new_range, label) + RangeMatcher.new(expected) end # Returns a new matcher, with the same bounds, but uses an exclusive range. def exclusive - range = Range.new(@expected.begin, @expected.end, exclusive: true) - RangeMatcher.new(range, label) + new_range = Range.new(range.begin, range.end, exclusive: true) + expected = TestValue.new(new_range, label) + RangeMatcher.new(expected) end - # Match data specific to this matcher. - # This is used when the expected type is a `Range`. - private struct MatchData(B, E, ActualType) < MatchData - # Creates the match data. - def initialize(matched, @values : ExpectedActual(Range(B, E), ActualType)) - super(matched) - end + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + expected.value.includes?(actual.value) + end - # Information about the match. - def named_tuple - { - lower: NegatablePrefixedMatchDataValue.new(">=", "<", range.begin), - upper: NegatablePrefixedMatchDataValue.new(exclusive? ? "<" : "<=", exclusive? ? ">=" : ">", range.end), - actual: @values.actual, - } - 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 in #{expected.label} (#{exclusivity})" + 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} (#{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 - # 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} (#{exclusivity})" - end + # Additional information about the match failure. + # The return value is a NamedTuple with Strings for each value. + private def values(actual) + { + lower: ">= #{range.begin.inspect}", + upper: "#{exclusive? ? "<" : "<="} #{range.end.inspect}", + actual: actual.value.inspect, + } + end - # Gets the expected range. - private def range - @values.expected - 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) + { + lower: "< #{range.begin.inspect}", + upper: "#{exclusive? ? ">=" : ">"} #{range.end.inspect}", + actual: actual.value.inspect, + } + end - # Indicates whether the range is inclusive or exclusive. - private def exclusive? - range.exclusive? - end + # Gets the expected range. + private def range + expected.value + end - # Produces a string "inclusive" or "exclusive" based on the range. - private def exclusivity - exclusive? ? "exclusive" : "inclusive" - end + # Indicates whether the range is inclusive or exclusive. + private def exclusive? + range.exclusive? + end + + # Produces a string "inclusive" or "exclusive" based on the range. + private def exclusivity + exclusive? ? "exclusive" : "inclusive" end end end diff --git a/src/spectator/matchers/reference_matcher.cr b/src/spectator/matchers/reference_matcher.cr index 1463a2a..633e619 100644 --- a/src/spectator/matchers/reference_matcher.cr +++ b/src/spectator/matchers/reference_matcher.cr @@ -4,44 +4,37 @@ module Spectator::Matchers # Matcher that tests whether two references are the same. # The values are compared with the `Reference#same?` method. struct ReferenceMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual.same?(expected) + # 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 #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + expected.value.same?(actual.value) end - # 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 + # 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 - # 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} 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 + # 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 end end diff --git a/src/spectator/matchers/respond_matcher.cr b/src/spectator/matchers/respond_matcher.cr index 4c0e4a0..d842167 100644 --- a/src/spectator/matchers/respond_matcher.cr +++ b/src/spectator/matchers/respond_matcher.cr @@ -6,68 +6,68 @@ module Spectator::Matchers # The `ExpectedType` type param should be a `NamedTuple`, # with each key being the method to check and the value is ignored. struct RespondMatcher(ExpectedType) < Matcher - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - # The snapshot did the hard work. - # Here just check if all values are true. - actual.values.all? + # 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 + "responds to #{label}" 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) - values = snapshot_values(partial.actual) - MatchData.new(match?(values), values, partial.label, label) + # 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 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 # Captures all of the actual values. - # A `NamedTuple` is returned, - # with each key being the attribute. - private def snapshot_values(actual) + # A `NamedTuple` is returned, with each key being the attribute. + private def snapshot_values(object) {% begin %} { - {% for method in ExpectedType.keys %} - {{method.stringify}}: actual.responds_to?({{method.symbolize}}), + {% for attribute in ExpectedType.keys %} + {{attribute}}: object.responds_to?({{attribute.symbolize}}), {% end %} } {% end %} end - # Textual representation of what the matcher expects. - def label + # Checks if all results from the snapshot are satisified. + 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. {{ExpectedType.keys.map { |e| "##{e}".id }.splat.stringify}} 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 diff --git a/src/spectator/matchers/size_matcher.cr b/src/spectator/matchers/size_matcher.cr new file mode 100644 index 0000000..1ab6e3d --- /dev/null +++ b/src/spectator/matchers/size_matcher.cr @@ -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 diff --git a/src/spectator/matchers/size_of_matcher.cr b/src/spectator/matchers/size_of_matcher.cr new file mode 100644 index 0000000..3e9f1cf --- /dev/null +++ b/src/spectator/matchers/size_of_matcher.cr @@ -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 diff --git a/src/spectator/matchers/standard_matcher.cr b/src/spectator/matchers/standard_matcher.cr new file mode 100644 index 0000000..414e491 --- /dev/null +++ b/src/spectator/matchers/standard_matcher.cr @@ -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 diff --git a/src/spectator/matchers/start_with_matcher.cr b/src/spectator/matchers/start_with_matcher.cr index 6ece475..067f52a 100644 --- a/src/spectator/matchers/start_with_matcher.cr +++ b/src/spectator/matchers/start_with_matcher.cr @@ -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 # 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. # Otherwise, it is treated as an `Enumerable` and the `first` value is compared against. - struct StartWithMatcher(ExpectedType) < ValueMatcher(ExpectedType) - # Determines whether the matcher is satisfied with the value given to it. - private def match_starts_with?(actual) - actual.starts_with?(expected) + struct StartWithMatcher(ExpectedType) < Matcher + # Expected value and label. + private getter expected + + # Creates the matcher with an expected value. + def initialize(@expected : TestValue(ExpectedType)) end - # Determines whether the matcher is satisfied with the value given to it. - private def match_first?(actual) - expected === actual + # 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 + "starts with #{expected.label}" 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) - values = ExpectedActual.new(partial, self) - actual = values.actual - if actual.responds_to?(:starts_with?) - StartsWithMatchData.new(match_starts_with?(actual), values) + # Actually performs the test against the expression. + def match(actual : TestExpression(T)) : MatchData forall T + if (value = actual.value).responds_to?(:starts_with?) + match_starts_with(value, actual.label) else - first = actual.first - FirstMatchData.new(match_first?(first), values, first) + match_first(value, actual.label) end end - # Match data specific to this matcher. - # This type is used when the actual value responds to `starts_with?`. - private struct StartsWithMatchData(ExpectedType, ActualType) < MatchData - # Creates the match data. - def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType)) - super(matched) - end - - # 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?)" + # 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 + if (value = actual.value).responds_to?(:starts_with?) + negated_match_starts_with(value, actual.label) + else + negated_match_first(value, actual.label) end end - # Match data specific to this matcher. - # This type is used when the actual value does not respond to `ends_with?`. - private struct FirstMatchData(ExpectedType, ActualType, FirstType) < MatchData - # Creates the match data. - def initialize(matched, @values : ExpectedActual(ExpectedType, ActualType), @first : FirstType) - super(matched) + # Checks whether the actual value starts with the expected value. + # This method expects (and uses) the `#starts_with?` method on the value. + private def match_starts_with(actual_value, actual_label) + if actual_value.starts_with?(expected.value) + SuccessfulMatchData.new + else + FailedMatchData.new("#{actual_label} does not start with #{expected.label} (using #starts_with?)", + expected: expected.value.inspect, + actual: actual_value.inspect + ) end + end - # Information about the match. - def named_tuple - { - expected: @values.expected, - actual: @first, - list: @values.actual, - } + # Checks whether the first element of the value is the expected value. + # This method expects that the actual value is a set (enumerable). + private def match_first(actual_value, actual_label) + list = actual_value.to_a + first = list.first + + 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 - # 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 expected === actual.first)" + # Checks whether the actual value does not start with the expected value. + # This method expects (and uses) the `#starts_with?` method on the value. + private def negated_match_starts_with(actual_value, actual_label) + 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 - # 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 expected === actual.first)" + # Checks whether the first element of the value is not the expected value. + # This method expects that the actual value is a set (enumerable). + private def negated_match_first(actual_value, actual_label) + 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 diff --git a/src/spectator/matchers/successful_match_data.cr b/src/spectator/matchers/successful_match_data.cr new file mode 100644 index 0000000..25a94ce --- /dev/null +++ b/src/spectator/matchers/successful_match_data.cr @@ -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 diff --git a/src/spectator/matchers/truthy_matcher.cr b/src/spectator/matchers/truthy_matcher.cr index 7eac541..34aea3b 100644 --- a/src/spectator/matchers/truthy_matcher.cr +++ b/src/spectator/matchers/truthy_matcher.cr @@ -1,4 +1,4 @@ -require "./value_matcher" +require "./standard_matcher" module Spectator::Matchers # Matcher that tests whether a value is truthy or falsey. @@ -8,29 +8,18 @@ module Spectator::Matchers # # Additionally, different matchers can be created # by using the `#<`, `#<=`, `#>`, `#>=`, `#==`, and `#!=` operators. - struct TruthyMatcher < Matcher + struct TruthyMatcher < StandardMatcher # Creates the truthy matcher. # The *truthy* argument should be true to match "truthy" values, # and false to match "falsey" values. - def initialize(@truthy : Bool) + def initialize(@truthy : Bool = true) end - # Textual representation of what the matcher expects. - def label - @truthy ? "truthy" : "falsey" - end - - # 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) + # 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 #{label}" end # 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 # ``` - def <(expected : ExpectedType) forall ExpectedType + def <(value : ExpectedType) forall ExpectedType + expected = TestValue.new(value) LessThanMatcher.new(expected) end @@ -47,7 +37,8 @@ module Spectator::Matchers # ``` # expect(0).to be <= 1 # ``` - def <=(expected : ExpectedType) forall ExpectedType + def <=(value : ExpectedType) forall ExpectedType + expected = TestValue.new(value) LessThanEqualMatcher.new(expected) end @@ -56,7 +47,8 @@ module Spectator::Matchers # ``` # expect(2).to be > 1 # ``` - def >(expected : ExpectedType) forall ExpectedType + def >(value : ExpectedType) forall ExpectedType + expected = TestValue.new(value) GreaterThanMatcher.new(expected) end @@ -65,7 +57,8 @@ module Spectator::Matchers # ``` # expect(2).to be >= 1 # ``` - def >=(expected : ExpectedType) forall ExpectedType + def >=(value : ExpectedType) forall ExpectedType + expected = TestValue.new(value) GreaterThanEqualMatcher.new(expected) end @@ -74,7 +67,8 @@ module Spectator::Matchers # ``` # expect(0).to be == 0 # ``` - def ==(expected : ExpectedType) forall ExpectedType + def ==(value : ExpectedType) forall ExpectedType + expected = TestValue.new(value) EqualityMatcher.new(expected) end @@ -83,44 +77,65 @@ module Spectator::Matchers # ``` # expect(0).to be != 1 # ``` - def !=(expected : ExpectedType) forall ExpectedType + def !=(value : ExpectedType) forall ExpectedType + expected = TestValue.new(value) InequalityMatcher.new(expected) end - # Match data specific to this matcher. - private struct MatchData(ActualType) < MatchData - # Creates the match data. - def initialize(matched, @truthy : Bool, @actual : ActualType, @actual_label : String) - super(matched) - end + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + @truthy == !!actual.value + end - # Information about the match. - def named_tuple - truthy = "Not false or nil" - falsey = "false or nil" - { - expected: AlternativeMatchDataValue.new(@truthy ? truthy : falsey, @truthy ? falsey : truthy), - actual: @actual, - truthy: !!@actual, - } - 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 #{negated_label}" + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@actual_label} is #{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 #{label}" + end - # Describes the condition that won't satsify the matcher. - # This is informational and displayed to the end-user. - def negated_message - "#{@actual_label} is not #{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: @truthy ? "Not false or nil" : "false or nil", + actual: actual.value.inspect, + truthy: !!actual.value.inspect, + } + end - # Textual representation of what the matcher expects. - private def expected_label - @truthy ? "truthy" : "falsey" - 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: @truthy ? "false or nil" : "Not false or nil", + actual: actual.value.inspect, + truthy: !!actual.value.inspect, + } + end + + # Generated, user-friendly, string for the expected value. + private def label + @truthy ? "truthy" : "falsey" + end + + # Generated, user-friendly, string for the unexpected value. + private def negated_label + @truthy ? "falsey" : "truthy" end end end diff --git a/src/spectator/matchers/type_matcher.cr b/src/spectator/matchers/type_matcher.cr index 3283992..2b44296 100644 --- a/src/spectator/matchers/type_matcher.cr +++ b/src/spectator/matchers/type_matcher.cr @@ -3,51 +3,56 @@ require "./matcher" module Spectator::Matchers # Matcher that tests a value is of a specified type. # The values are compared with the `Object#is_a?` method. - struct TypeMatcher(Expected) < Matcher - # Textual representation of what the matcher expects. - # The `Expected` type param will be used to populate the label. - def label - Expected.to_s + struct TypeMatcher(Expected) < StandardMatcher + # 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 as #{Expected}" end - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - actual.is_a?(Expected) + # Checks whether the matcher is satisifed with the expression given to it. + private def match?(actual : TestExpression(T)) forall T + actual.value.is_a?(Expected) 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(Expected, typeof(actual)).new(match?(actual), partial.label) + # 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 a #{Expected}" end - # Match data specific to this matcher. - private struct MatchData(ExpectedType, ActualType) < MatchData - # Creates the match data. - def initialize(matched, @actual_label : String) - super(matched) - 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 a #{Expected}" + end - # Information about the match. - def named_tuple - { - expected: NegatableMatchDataValue.new(ExpectedType), - actual: ActualType, - } - end + # Additional information about the match failure. + # The return value is a NamedTuple with Strings for each value. + private def values(actual) + { + expected: Expected.to_s, + actual: actual.value.class.inspect, + } + end - # Describes the condition that satisfies the matcher. - # This is informational and displayed to the end-user. - def message - "#{@actual_label} is a #{ExpectedType}" - 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 a #{ExpectedType}" - 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}", + actual: actual.value.class.inspect, + } end end end diff --git a/src/spectator/matchers/unordered_array_matcher.cr b/src/spectator/matchers/unordered_array_matcher.cr new file mode 100644 index 0000000..823ee16 --- /dev/null +++ b/src/spectator/matchers/unordered_array_matcher.cr @@ -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 diff --git a/src/spectator/matchers/value_matcher.cr b/src/spectator/matchers/value_matcher.cr index 9ffa6d8..5049320 100644 --- a/src/spectator/matchers/value_matcher.cr +++ b/src/spectator/matchers/value_matcher.cr @@ -1,30 +1,67 @@ -require "./matcher" +require "./standard_matcher" module Spectator::Matchers # Category of matcher that uses a value. # 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 - # Textual representation of what the matcher expects. - # This shouldn't be used in the conditional logic, - # but for verbose output to help the end-user. - getter label - + # + # 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. + # + # 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. # Sub-types may use this value to test the expectation and generate message strings. - getter expected + private getter expected # Creates the value matcher. - # The label should be a string representation of the expectation. # The expected value is stored for later use. - def initialize(@expected : ExpectedType, @label : String) + def initialize(@expected : TestValue(ExpectedType)) end - # Creates the value matcher. - # The label is generated by calling `#to_s` on the expected value. - # The expected value is stored for later use. - def initialize(expected : ExpectedType) - initialize(expected, expected.to_s) + # Additional information about the match failure. + # + # By default, just the actual and expected values are 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", + # } + # ``` + 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 diff --git a/src/spectator/should.cr b/src/spectator/should.cr index 2cfcf2d..de75c85 100644 --- a/src/spectator/should.cr +++ b/src/spectator/should.cr @@ -21,13 +21,17 @@ class Object # 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. # 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 # Works the same as `#should` except the condition is inverted. # When `#should` succeeds, this method will fail, and vice-versa. 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 @@ -35,12 +39,16 @@ struct Proc(*T, R) # Extension method to create an expectation for a block of code (proc). # Depending on the matcher, the proc may be executed multiple times. 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 # Works the same as `#should` except the condition is inverted. # When `#should` succeeds, this method will fail, and vice-versa. 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 diff --git a/src/spectator/test_block.cr b/src/spectator/test_block.cr new file mode 100644 index 0000000..a6af675 --- /dev/null +++ b/src/spectator/test_block.cr @@ -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("") + 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 diff --git a/src/spectator/test_expression.cr b/src/spectator/test_expression.cr new file mode 100644 index 0000000..d5e3cdd --- /dev/null +++ b/src/spectator/test_expression.cr @@ -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 diff --git a/src/spectator/test_value.cr b/src/spectator/test_value.cr new file mode 100644 index 0000000..b621562 --- /dev/null +++ b/src/spectator/test_value.cr @@ -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