diff --git a/shard.yml b/shard.yml index 71ef960..55ce215 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: spectator -version: 0.5.3 +version: 0.6.0 description: | A feature-rich spec testing framework for Crystal with similarities to RSpec. diff --git a/spec/matchers/case_matcher_spec.cr b/spec/matchers/case_matcher_spec.cr index cf4d442..573a30b 100644 --- a/spec/matchers/case_matcher_spec.cr +++ b/spec/matchers/case_matcher_spec.cr @@ -88,6 +88,28 @@ describe Spectator::Matchers::CaseMatcher do 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 @@ -116,14 +138,6 @@ describe Spectator::Matchers::CaseMatcher do end describe "#message" do - it "mentions ===" do - value = 42 - partial = new_partial(value) - matcher = Spectator::Matchers::CaseMatcher.new(value) - match_data = matcher.match(partial) - match_data.message.should contain("===") - end - it "contains the actual label" do value = 42 label = "everything" @@ -155,14 +169,6 @@ describe Spectator::Matchers::CaseMatcher do end describe "#negated_message" do - it "mentions ===" do - value = 42 - partial = new_partial(value) - matcher = Spectator::Matchers::CaseMatcher.new(value) - match_data = matcher.match(partial) - match_data.negated_message.should contain("===") - end - it "contains the actual label" do value = 42 label = "everything" diff --git a/spec/matchers/reference_matcher_spec.cr b/spec/matchers/reference_matcher_spec.cr new file mode 100644 index 0000000..b029090 --- /dev/null +++ b/spec/matchers/reference_matcher_spec.cr @@ -0,0 +1,152 @@ +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/regex_matcher_spec.cr b/spec/matchers/regex_matcher_spec.cr deleted file mode 100644 index 295b9a7..0000000 --- a/spec/matchers/regex_matcher_spec.cr +++ /dev/null @@ -1,147 +0,0 @@ -require "../spec_helper" - -describe Spectator::Matchers::RegexMatcher do - describe "#match" do - it "compares using #=~" do - spy = SpySUT.new - partial = new_partial(spy) - matcher = Spectator::Matchers::RegexMatcher.new(/foobar/) - matcher.match(partial) - spy.match_call_count.should be > 0 - end - - context "returned MatchData" do - describe "#matched?" do - context "with a matching pattern" do - it "is true" do - value = "foobar" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.matched?.should be_true - end - end - - context "with a non-matching pattern" do - it "is false" do - value = "foo" - pattern = /bar/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - 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 - value = "foo" - pattern = /bar/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data_value_sans_prefix(match_data.values, :expected)[:value].should eq(pattern) - end - end - - context "actual" do - it "is the actual value" do - value = "foo" - pattern = /bar/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - 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 =~" do - value = "foobar" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.message.should contain("=~") - end - - it "contains the actual label" do - value = "foobar" - label = "different" - pattern = /foo/ - partial = new_partial(value, label) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.message.should contain(label) - end - - it "contains the expected label" do - value = "foobar" - label = "different" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern, 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" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.message.should contain(pattern.to_s) - end - end - end - - describe "#negated_message" do - it "mentions =~" do - value = "foobar" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.negated_message.should contain("=~") - end - - it "contains the actual label" do - value = "foobar" - label = "different" - pattern = /foo/ - partial = new_partial(value, label) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.negated_message.should contain(label) - end - - it "contains the expected label" do - value = "foobar" - label = "different" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern, 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" - pattern = /foo/ - partial = new_partial(value) - matcher = Spectator::Matchers::RegexMatcher.new(pattern) - match_data = matcher.match(partial) - match_data.negated_message.should contain(pattern.to_s) - end - end - end - end - end -end diff --git a/src/spectator/dsl/matcher_dsl.cr b/src/spectator/dsl/matcher_dsl.cr index 58e38af..def9910 100644 --- a/src/spectator/dsl/matcher_dsl.cr +++ b/src/spectator/dsl/matcher_dsl.cr @@ -47,18 +47,18 @@ module Spectator::DSL ::Spectator::Matchers::TruthyMatcher.new(true) end - # Indicates that some value should semantically equal another. - # The === operator is used for this check. - # This has identical behavior as a "when" condition in a case block. + # Indicates that some object should be the same as another. + # This checks if two references are the same. + # The `Reference#same?` method is used for this check. # # Examples: # ``` - # expect(1 + 2).to be(3) - # expect(5).to be(Int32) # Using `#be_a` instead is recommened here. - # expect(tuple).to be({1, 2}) + # obj = "foobar" + # expect(obj).to be(obj) + # expect(obj.dup).to_not be(obj) # ``` macro be(expected) - ::Spectator::Matchers::CaseMatcher.new({{expected}}, {{expected.stringify}}) + ::Spectator::Matchers::ReferenceMatcher.new({{expected}}, {{expected.stringify}}) end # Indicates that some value should be of a specified type. @@ -155,17 +155,20 @@ module Spectator::DSL end # Indicates that some value should match another. - # The =~ operator is used for this check. - # Typically a regular expression is used, - # but any type that has the =~ operator will work. + # The === (case equality) operator is used for this check. + # Typically a regular expression is used. + # This has identical behavior as a "when" condition in a case block. # # Examples: # ``` # expect("foo").to match(/foo|bar/) # expect("BAR").to match(/foo|bar/i) + # expect(1 + 2).to match(3) + # expect(5).to match(Int32) # Using `#be_a` instead is recommened here. + # expect({:foo, 5}).to match({Symbol, Int32}) # ``` macro match(expected) - ::Spectator::Matchers::RegexMatcher.new({{expected}}, {{expected.stringify}}) + ::Spectator::Matchers::CaseMatcher.new({{expected}}, {{expected.stringify}}) end # Indicates that some value should be true. diff --git a/src/spectator/matchers/case_matcher.cr b/src/spectator/matchers/case_matcher.cr index 8dfc881..d321468 100644 --- a/src/spectator/matchers/case_matcher.cr +++ b/src/spectator/matchers/case_matcher.cr @@ -34,13 +34,13 @@ module Spectator::Matchers # Describes the condition that satisfies the matcher. # This is informational and displayed to the end-user. def message - "#{@values.actual_label} equals #{@values.expected_label} (using ===)" + "#{@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 equal #{@values.expected_label} (using ===)" + "#{@values.actual_label} does not match #{@values.expected_label}" end end end diff --git a/src/spectator/matchers/regex_matcher.cr b/src/spectator/matchers/reference_matcher.cr similarity index 76% rename from src/spectator/matchers/regex_matcher.cr rename to src/spectator/matchers/reference_matcher.cr index bd679a3..1463a2a 100644 --- a/src/spectator/matchers/regex_matcher.cr +++ b/src/spectator/matchers/reference_matcher.cr @@ -1,12 +1,12 @@ require "./value_matcher" module Spectator::Matchers - # Matcher that tests whether a value matches a regular expression. - # The value is compared with the =~ operator. - struct RegexMatcher(ExpectedType) < ValueMatcher(ExpectedType) + # 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 =~ expected) + actual.same?(expected) end # Determines whether the matcher is satisfied with the partial given to it. @@ -34,13 +34,13 @@ module Spectator::Matchers # 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} (using =~)" + "#{@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} does not match #{@values.expected_label} (using =~)" + "#{@values.actual_label} is not #{@values.expected_label}" end end end