diff --git a/CHANGELOG.md b/CHANGELOG.md index 413271a9..47c45d28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fix ActiveRecord's `attributes_for_super_diff` and tree builders related to Active Records to handle models that do not have a primary key. [#282](https://github.com/splitwise/super_diff/pull/282) by [@atranson-electra](https://github.com/atranson-electra) +- Fix failure case for chained matchers. [#288](https://github.com/splitwise/super_diff/pull/288) ### Other changes diff --git a/docs/contributors/architecture/how-super-diff-works.md b/docs/contributors/architecture/how-super-diff-works.md index 2b6935df..6f330416 100644 --- a/docs/contributors/architecture/how-super-diff-works.md +++ b/docs/contributors/architecture/how-super-diff-works.md @@ -39,7 +39,8 @@ or when a matcher whose `#does_not_match?` method returns `true` is passed to `expect(...).not_to` — RSpec will call the `RSpec::Expectations::ExpectationHelper#handle_failure` method, which will call `RSpec::Expectations.fail_with`. -This method will use `RSpec::Matchers::ExpectedsForMultipleDiffs` +This method will use `RSpec::Matchers::MultiMatcherDiff` +(or in RSpec < 3.13.0, `RSpec::Matchers::ExpectedsForMultipleDiffs`) and the differ object that `RSpec::Expectations.differ` returns to generate a diff, combining it with the failure message from the matcher, @@ -89,7 +90,7 @@ Here are all of the places that SuperDiff patches RSpec: as it interferes with the previous patches) - `RSpec::Support::ObjectFormatter` (to use SuperDiff's object inspection logic) -- `RSpec::Matchers::ExpectedsForMultipleDiffs` +- `RSpec::Matchers::ExpectedsForMultipleDiffs` and `RSpec::Matchers::MultiMatcherDiff` (to add a key above the diff, add spacing around the diff, and colorize the word "Diff:") diff --git a/lib/super_diff/rspec/monkey_patches.rb b/lib/super_diff/rspec/monkey_patches.rb index 6e063ffe..7210ea3c 100644 --- a/lib/super_diff/rspec/monkey_patches.rb +++ b/lib/super_diff/rspec/monkey_patches.rb @@ -352,6 +352,26 @@ def from(expected) new([[expected, text]]) end + def for_many_matchers(matchers) + # Look for expected_for_diff and actual_for_diff if possible + diff_tuples = matchers.map do |m| + expected = if m.respond_to?(:expected_for_diff) + m.expected_for_diff + else + m.expected + end + + actual = if m.respond_to?(:actual_for_diff) + m.actual_for_diff + else + m.actual + end + [expected, diff_label_for(m), actual] + end + + new(diff_tuples) + end + def colorizer RSpec::Core::Formatters::ConsoleCodes end @@ -423,6 +443,26 @@ def from(expected, actual) new([[expected, text, actual]]) end + def for_many_matchers(matchers) + # Look for expected_for_diff and actual_for_diff if possible + diff_tuples = matchers.map do |m| + expected = if m.respond_to?(:expected_for_diff) + m.expected_for_diff + else + m.expected + end + + actual = if m.respond_to?(:actual_for_diff) + m.actual_for_diff + else + m.actual + end + [expected, diff_label_for(m), actual] + end + + new(diff_tuples) + end + def colorizer RSpec::Core::Formatters::ConsoleCodes end diff --git a/spec/integration/rspec/raise_error_matcher_spec.rb b/spec/integration/rspec/raise_error_matcher_spec.rb index 5d5e545a..41854af9 100644 --- a/spec/integration/rspec/raise_error_matcher_spec.rb +++ b/spec/integration/rspec/raise_error_matcher_spec.rb @@ -1487,4 +1487,39 @@ end end end + + context 'when part of an expectation chain' do + context 'when the expected error and/or actual message is short' do + it 'produces the correct failure message' do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~TEST.strip + expect { raise StandardError.new('boo') }.to raise_error(RuntimeError, 'bar').and(change(Random, :rand)) + TEST + program = + make_plain_test_program(snippet, color_enabled: color_enabled) + + expected_output = + build_expected_output( + color_enabled: color_enabled, + snippet: + "expect { raise StandardError.new('boo') }.to raise_error(RuntimeError, 'bar').and(change(Random, :rand))", + expectation: + proc do + line do + plain 'Expected raised exception ' + actual %(#) + plain ' to match ' + expected 'a kind of RuntimeError with message "bar"' + plain '.' + end + end + ) + + expect(program).to produce_output_when_run( + expected_output + ).in_color(color_enabled) + end + end + end + end end