From 7168b262184de882989d4eec5de4fc210c005bed Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 8 May 2019 16:39:00 -0600 Subject: [PATCH 1/4] Remove regex matcher - use case matcher --- spec/matchers/case_matcher_spec.cr | 38 +++--- spec/matchers/regex_matcher_spec.cr | 147 ------------------------ src/spectator/dsl/matcher_dsl.cr | 11 +- src/spectator/matchers/case_matcher.cr | 4 +- src/spectator/matchers/regex_matcher.cr | 47 -------- 5 files changed, 31 insertions(+), 216 deletions(-) delete mode 100644 spec/matchers/regex_matcher_spec.cr delete mode 100644 src/spectator/matchers/regex_matcher.cr 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/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..0ae9856 100644 --- a/src/spectator/dsl/matcher_dsl.cr +++ b/src/spectator/dsl/matcher_dsl.cr @@ -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/regex_matcher.cr deleted file mode 100644 index bd679a3..0000000 --- a/src/spectator/matchers/regex_matcher.cr +++ /dev/null @@ -1,47 +0,0 @@ -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) - # Determines whether the matcher is satisfied with the value given to it. - private def match?(actual) - !!(actual =~ 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) - values = ExpectedActual.new(partial, self) - MatchData.new(match?(values.actual), values) - 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 - { - 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} (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} does not match #{@values.expected_label} (using =~)" - end - end - end -end From f53bc26c28268d62a98ec7da6b1e2db90fac86c8 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 8 May 2019 16:59:36 -0600 Subject: [PATCH 2/4] Add ReferenceMatcher type --- spec/matchers/reference_matcher_spec.cr | 152 ++++++++++++++++++++ src/spectator/matchers/reference_matcher.cr | 47 ++++++ 2 files changed, 199 insertions(+) create mode 100644 spec/matchers/reference_matcher_spec.cr create mode 100644 src/spectator/matchers/reference_matcher.cr 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/src/spectator/matchers/reference_matcher.cr b/src/spectator/matchers/reference_matcher.cr new file mode 100644 index 0000000..1463a2a --- /dev/null +++ b/src/spectator/matchers/reference_matcher.cr @@ -0,0 +1,47 @@ +require "./value_matcher" + +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) + 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) + 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 + { + 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 + end + end +end From 2378594c5dd030f2ad5f764b8d71d448f15b6ef9 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 8 May 2019 16:59:59 -0600 Subject: [PATCH 3/4] Use reference matching for be() --- src/spectator/dsl/matcher_dsl.cr | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/spectator/dsl/matcher_dsl.cr b/src/spectator/dsl/matcher_dsl.cr index 0ae9856..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. From 7e205210303009e9f35131fc8f6dbdb61aa5fd67 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Wed, 8 May 2019 17:02:30 -0600 Subject: [PATCH 4/4] Bump to 0.6.0 Breaking change with be() and match() --- shard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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.