diff --git a/lib/super_diff/rspec.rb b/lib/super_diff/rspec.rb index 14e6f52f..8a2c9dd6 100644 --- a/lib/super_diff/rspec.rb +++ b/lib/super_diff/rspec.rb @@ -113,7 +113,7 @@ def self.rspec_version ObjectInspection::InspectionTreeBuilders::ObjectHavingAttributes, # ObjectInspection::InspectionTreeBuilders::Primitive, ObjectInspection::InspectionTreeBuilders::ValueWithin, - ObjectInspection::InspectionTreeBuilders::RSpecMatcher + ObjectInspection::InspectionTreeBuilders::GenericDescribableMatcher ) end end diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb index d0012258..d14650cf 100644 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb +++ b/lib/super_diff/rspec/object_inspection/inspection_tree_builders.rb @@ -14,6 +14,10 @@ module InspectionTreeBuilders :Double, "super_diff/rspec/object_inspection/inspection_tree_builders/double" ) + autoload( + :GenericDescribableMatcher, + "super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher" + ) autoload( :HashIncluding, "super_diff/rspec/object_inspection/inspection_tree_builders/hash_including" @@ -34,10 +38,6 @@ module InspectionTreeBuilders :Primitive, "super_diff/rspec/object_inspection/inspection_tree_builders/primitive" ) - autoload( - :RSpecMatcher, - "super_diff/rspec/object_inspection/inspection_tree_builders/rspec_matcher" - ) autoload( :ValueWithin, "super_diff/rspec/object_inspection/inspection_tree_builders/value_within" diff --git a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/rspec_matcher.rb b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb similarity index 62% rename from lib/super_diff/rspec/object_inspection/inspection_tree_builders/rspec_matcher.rb rename to lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb index afbdbe22..1a983ca9 100644 --- a/lib/super_diff/rspec/object_inspection/inspection_tree_builders/rspec_matcher.rb +++ b/lib/super_diff/rspec/object_inspection/inspection_tree_builders/generic_describable_matcher.rb @@ -2,10 +2,9 @@ module SuperDiff module RSpec module ObjectInspection module InspectionTreeBuilders - class RSpecMatcher < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base + class GenericDescribableMatcher < SuperDiff::ObjectInspection::InspectionTreeBuilders::Base def self.applies_to?(value) - value.is_a?(::RSpec::Matchers::BuiltIn::BaseMatcher) || - value.is_a?(::RSpec::Matchers::DSL::Matcher) + ::RSpec::Matchers.is_a_describable_matcher?(value) end def call diff --git a/spec/integration/rspec/generic_describable_matchers_spec.rb b/spec/integration/rspec/generic_describable_matchers_spec.rb new file mode 100644 index 00000000..f50b5e11 --- /dev/null +++ b/spec/integration/rspec/generic_describable_matchers_spec.rb @@ -0,0 +1,177 @@ +require "spec_helper" + +RSpec.describe "Integration with describable matchers not handled specially", + type: :integration do + context "when the expected value contains a built-in matcher (not an aliased matcher)" do + it "produces the correct failure message when used in the positive" do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~TEST.strip + actual = { + number: "not a number" + } + expected = hash_including( + number: be_a(Numeric) + ) + expect(actual).to match(expected) + TEST + program = make_plain_test_program(snippet, color_enabled: color_enabled) + + expected_output = + build_expected_output( + color_enabled: color_enabled, + snippet: "expect(actual).to match(expected)", + expectation: + proc do + line do + plain "Expected " + actual %|{ number: "not a number" }| + plain " to match " + expected "#)>" + plain "." + end + end, + diff: + proc do + plain_line " {" + expected_line "- number: #" + actual_line %|+ number: "not a number"| + plain_line " }" + end + ) + + expect(program).to produce_output_when_run(expected_output).in_color( + color_enabled + ) + end + end + + it "produces the correct failure message when used in the negative" do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~TEST.strip + actual = { + number: 4 + } + expected = hash_including( + number: be_a(Numeric) + ) + expect(actual).not_to match(expected) + TEST + program = make_plain_test_program(snippet, color_enabled: color_enabled) + + expected_output = + build_expected_output( + color_enabled: color_enabled, + snippet: "expect(actual).not_to match(expected)", + expectation: + proc do + line do + plain "Expected " + actual "{ number: 4 }" + plain " not to match " + expected "#)>" + plain "." + end + end + ) + + expect(program).to produce_output_when_run(expected_output).in_color( + color_enabled + ) + end + end + end + + context "when the expected value contains a custom matcher defined via RSpec::Matchers::DSL" do + it "produces the correct failure message" do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~TEST.strip + actual = { + number: "something else" + } + expected = hash_including( + number: failing_custom_matcher_from_dsl(42) + ) + expect(actual).to match(expected) + TEST + program = make_plain_test_program(snippet, color_enabled: color_enabled) + + expected_output = + build_expected_output( + color_enabled: color_enabled, + snippet: "expect(actual).to match(expected)", + newline_before_expectation: true, + expectation: + proc do + line do + plain "Expected " + actual %|{ number: "something else" }| + end + + line do + plain "to match " + expected "#)>" + end + end, + diff: + proc do + plain_line " {" + expected_line "- number: #" + actual_line %|+ number: "something else"| + plain_line " }" + end + ) + + expect(program).to produce_output_when_run(expected_output).in_color( + color_enabled + ) + end + end + end + + context "when the expected value contains a custom matcher defined from scratch" do + it "produces the correct failure message" do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~TEST.strip + actual = { + number: "something else" + } + expected = hash_including( + number: failing_custom_matcher_from_scratch(42) + ) + expect(actual).to match(expected) + TEST + program = make_plain_test_program(snippet, color_enabled: color_enabled) + + expected_output = + build_expected_output( + color_enabled: color_enabled, + snippet: "expect(actual).to match(expected)", + newline_before_expectation: true, + expectation: + proc do + line do + plain "Expected " + actual %|{ number: "something else" }| + end + + line do + plain "to match " + expected "#)>" + end + end, + diff: + proc do + plain_line " {" + expected_line "- number: #" + actual_line %|+ number: "something else"| + plain_line " }" + end + ) + + expect(program).to produce_output_when_run(expected_output).in_color( + color_enabled + ) + end + end + end +end diff --git a/spec/integration/rspec/unhandled_matcher_spec.rb b/spec/integration/rspec/unhandled_matcher_spec.rb deleted file mode 100644 index 999305ec..00000000 --- a/spec/integration/rspec/unhandled_matcher_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -require "spec_helper" - -RSpec.describe "Integration with built-in RSpec matchers", type: :integration do - context "when the expected value contains a built-in matcher" do - context "that fails" do - it "produces the correct failure message when used in the negative" do - as_both_colored_and_uncolored do |color_enabled| - snippet = <<~TEST.strip - actual = { - number: 4 - } - expected = hash_including( - number: be_a(Numeric) - ) - expect(actual).not_to match(expected) - TEST - program = - make_plain_test_program(snippet, color_enabled: color_enabled) - - expected_output = - build_expected_output( - color_enabled: color_enabled, - snippet: "expect(actual).not_to match(expected)", - expectation: - proc do - line do - plain "Expected " - actual "{ number: 4 }" - plain " not to match " - expected "#)>" - plain "." - end - end - ) - - expect(program).to produce_output_when_run(expected_output).in_color( - color_enabled - ) - end - end - - it "produces the correct failure message when used in the positive" do - as_both_colored_and_uncolored do |color_enabled| - snippet = <<~TEST.strip - actual = { - number: "not a number" - } - expected = hash_including( - number: be_a(Numeric) - ) - expect(actual).to match(expected) - TEST - program = - make_plain_test_program(snippet, color_enabled: color_enabled) - - expected_output = - build_expected_output( - color_enabled: color_enabled, - snippet: "expect(actual).to match(expected)", - expectation: - proc do - line do - plain "Expected " - actual %|{ number: "not a number" }| - plain " to match " - expected "#)>" - plain "." - end - end, - diff: - proc do - plain_line " {" - expected_line "- number: #" - actual_line %|+ number: "not a number"| - plain_line " }" - end - ) - - expect(program).to produce_output_when_run(expected_output).in_color( - color_enabled - ) - end - end - end - end -end diff --git a/spec/support/integration/matchers.rb b/spec/support/integration/matchers.rb index 8c2c8da0..870273bb 100644 --- a/spec/support/integration/matchers.rb +++ b/spec/support/integration/matchers.rb @@ -32,6 +32,40 @@ def pass_with_singleline_failure_message PassWithSinglelineFailureMessageMatcher.new end + RSpec::Matchers.define :failing_custom_matcher_from_dsl do |value| + match { false } + + description do + "custom matcher defined via the DSL with value #{value.inspect}" + end + end + + def failing_custom_matcher_from_scratch(value) + FailingCustomMatcherFromScratch.new(value) + end + + class FailingCustomMatcherFromScratch + def initialize(value) + @value = value + end + + def matches?(_) + false + end + + def description + "custom matcher defined from scratch with value #{value.inspect}" + end + + def failure_message + "Expected custom matcher not to fail, but did" + end + + private + + attr_reader :value + end + class FailWithIndentedMultilineFailureMessageMatcher def matches?(_) false