Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 81d3a58

Browse files
committed
Fix diff for compound when transforming actual
Previously, we were passing the untransformed actual to the differ. Now, we take it from the matchers. fixes #1317
1 parent 0f43390 commit 81d3a58

File tree

7 files changed

+132
-32
lines changed

7 files changed

+132
-32
lines changed

Changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Enhancements:
66
* Update `eq` and `eql` matchers to better highlight difference in string encoding.
77
(Alan Foster, #1425)
88

9+
Bug Fixes:
10+
11+
* Fix the diff for redefined `actual` and reassigned `@actual` in compound
12+
expectations failure messages. (Phil Pirozhkov, #1440)
13+
914
### 3.12.3 / 2023-04-20
1015
[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.12.2...v3.12.3)
1116

lib/rspec/expectations/fail_with.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def fail_with(message, expected=nil, actual=nil)
3030
"appropriate failure_message[_when_negated] method to return a string?"
3131
end
3232

33-
message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
33+
message = ::RSpec::Matchers::MultiMatcherDiff.from(expected, actual).message_with_diff(message, differ)
3434

3535
RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
3636
end

lib/rspec/matchers.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
dsl
1111
matcher_delegator
1212
aliased_matcher
13-
expecteds_for_multiple_diffs
13+
multi_matcher_diff
1414
].each { |file| RSpec::Support.require_rspec_matchers(file) }
1515

1616
# RSpec's top level namespace. All of rspec-expectations is contained

lib/rspec/matchers/built_in/compound.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ def diffable?
5151
end
5252

5353
# @api private
54-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
54+
# @return [RSpec::Matchers::MultiMatcherDiff]
5555
def expected
5656
return nil unless evaluator
57-
::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
57+
::RSpec::Matchers::MultiMatcherDiff.for_many_matchers(diffable_matcher_list)
5858
end
5959

6060
protected

lib/rspec/matchers/expecteds_for_multiple_diffs.rb renamed to lib/rspec/matchers/multi_matcher_diff.rb

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module RSpec
22
module Matchers
33
# @api private
4-
# Handles list of expected values when there is a need to render
5-
# multiple diffs. Also can handle one value.
6-
class ExpectedsForMultipleDiffs
4+
# Handles list of expected and actual value pairs when there is a need
5+
# to render multiple diffs. Also can handle one pair.
6+
class MultiMatcherDiff
77
# @private
88
# Default diff label when there is only one matcher in diff
99
# output
@@ -19,33 +19,33 @@ def initialize(expected_list)
1919

2020
# @api private
2121
# Wraps provided expected value in instance of
22-
# ExpectedForMultipleDiffs. If provided value is already an
23-
# ExpectedForMultipleDiffs then it just returns it.
22+
# MultiMatcherDiff. If provided value is already an
23+
# MultiMatcherDiff then it just returns it.
2424
# @param [Any] expected value to be wrapped
25-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
26-
def self.from(expected)
25+
# @param [Any] actual value
26+
# @return [RSpec::Matchers::MultiMatcherDiff]
27+
def self.from(expected, actual)
2728
return expected if self === expected
28-
new([[expected, DEFAULT_DIFF_LABEL]])
29+
new([[expected, DEFAULT_DIFF_LABEL, actual]])
2930
end
3031

3132
# @api private
3233
# Wraps provided matcher list in instance of
33-
# ExpectedForMultipleDiffs.
34+
# MultiMatcherDiff.
3435
# @param [Array<Any>] matchers list of matchers to wrap
35-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
36+
# @return [RSpec::Matchers::MultiMatcherDiff]
3637
def self.for_many_matchers(matchers)
37-
new(matchers.map { |m| [m.expected, diff_label_for(m)] })
38+
new(matchers.map { |m| [m.expected, diff_label_for(m), m.actual] })
3839
end
3940

4041
# @api private
4142
# Returns message with diff(s) appended for provided differ
4243
# factory and actual value if there are any
4344
# @param [String] message original failure message
4445
# @param [Proc] differ
45-
# @param [Any] actual value
4646
# @return [String]
47-
def message_with_diff(message, differ, actual)
48-
diff = diffs(differ, actual)
47+
def message_with_diff(message, differ)
48+
diff = diffs(differ)
4949
message = "#{message}\n#{diff}" unless diff.empty?
5050
message
5151
end
@@ -65,8 +65,8 @@ def truncated(description)
6565
end
6666
end
6767

68-
def diffs(differ, actual)
69-
@expected_list.map do |(expected, diff_label)|
68+
def diffs(differ)
69+
@expected_list.map do |(expected, diff_label, actual)|
7070
diff = differ.diff(actual, expected)
7171
next if diff.strip.empty?
7272
if diff == "\e[0m\n\e[0m"

spec/rspec/matchers/built_in/compound_spec.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,98 @@ def expect_block
543543
expect(error.message).to include(expected_failure)
544544
end
545545
end
546+
547+
context 'when matcher transforms the actual' do
548+
context 'when the matcher redefines `actual`' do
549+
matcher :eq_downcase do |expected|
550+
match do |actual|
551+
@matcher_internal_actual = actual.downcase
552+
values_match? expected, @matcher_internal_actual
553+
end
554+
555+
def actual
556+
@matcher_internal_actual
557+
end
558+
559+
diffable
560+
end
561+
562+
it 'shows the redefined value in diff' do
563+
expected_failure =
564+
dedent(<<-EOS)
565+
| expected "HELLO\\nWORLD" to eq downcase "bonjour\\nmonde"
566+
|
567+
|...and:
568+
|
569+
| expected "HELLO\\nWORLD" to eq downcase "hola\\nmon"
570+
|Diff for (eq downcase "bonjour\\nmonde"):
571+
|@@ -1,3 +1,3 @@
572+
|-bonjour
573+
|-monde
574+
|+hello
575+
|+world
576+
|
577+
|Diff for (eq downcase "hola\\nmon"):
578+
|@@ -1,3 +1,3 @@
579+
|-hola
580+
|-mon
581+
|+hello
582+
|+world
583+
EOS
584+
585+
expect {
586+
expect("HELLO\nWORLD")
587+
.to eq_downcase("bonjour\nmonde")
588+
.and eq_downcase("hola\nmon")
589+
}.to fail do |error|
590+
expect(error.message).to include(expected_failure)
591+
end
592+
end
593+
end
594+
595+
context 'when the matcher reassigns `@actual`' do
596+
matcher :eq_downcase do |expected|
597+
match do |actual|
598+
@actual = actual.downcase
599+
values_match? expected, @actual
600+
end
601+
602+
diffable
603+
end
604+
605+
it 'shows the reassigned value in diff' do
606+
expected_failure =
607+
dedent(<<-EOS)
608+
| expected "hello\\nworld" to eq downcase "bonjour\\nmonde"
609+
|
610+
|...and:
611+
|
612+
| expected "hello\\nworld" to eq downcase "hola\\nmon"
613+
|Diff for (eq downcase "bonjour\\nmonde"):
614+
|@@ -1,3 +1,3 @@
615+
|-bonjour
616+
|-monde
617+
|+hello
618+
|+world
619+
|
620+
|Diff for (eq downcase "hola\\nmon"):
621+
|@@ -1,3 +1,3 @@
622+
|-hola
623+
|-mon
624+
|+hello
625+
|+world
626+
EOS
627+
628+
expect {
629+
expect("HELLO\nWORLD")
630+
.to eq_downcase("bonjour\nmonde")
631+
.and eq_downcase("hola\nmon")
632+
}.to fail do |error|
633+
expect(error.message).to include(expected_failure)
634+
end
635+
end
636+
end
637+
end
546638
end
547639

548640
context "when both matchers are not diffable" do

spec/rspec/matchers/expecteds_for_multiple_diffs_spec.rb renamed to spec/rspec/matchers/multi_matcher_diff_spec.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module RSpec
22
module Matchers
3-
RSpec.describe ExpectedsForMultipleDiffs do
3+
RSpec.describe MultiMatcherDiff do
44

55
before do
6-
stub_const("::RSpec::Matchers::ExpectedsForMultipleDiffs::DESCRIPTION_MAX_LENGTH", 30)
6+
stub_const("::RSpec::Matchers::MultiMatcherDiff::DESCRIPTION_MAX_LENGTH", 30)
77
end
88

99
class FakeDiffer
@@ -17,11 +17,12 @@ def self.diff(actual, expected)
1717
let(:message) { "a message" }
1818
let(:actual) { "actual value" }
1919

20-
let(:wrapped_value) { described_class.from("expected value") }
20+
let(:wrapped_value) { described_class.from("expected value", actual) }
2121

2222
def create_matcher(stubs)
2323
instance_double(BuiltIn::BaseMatcher, stubs.merge(
2424
:matches? => true,
25+
:actual => actual,
2526
:failure_message => ""
2627
))
2728
end
@@ -35,12 +36,12 @@ def create_matcher(stubs)
3536
let(:matcher_with_long_description) { create_matcher(:description => long_description, :expected => "expected value") }
3637

3738
describe ".from" do
38-
it "wraps provided value in ExpectedsForMultipleDiffs" do
39+
it "wraps provided value in MultiMatcherDiff" do
3940
expect(wrapped_value).to be_a(described_class)
4041
end
4142

4243
it "returns original value if it was already wrapped" do
43-
expect(described_class.from(wrapped_value)).to be(wrapped_value)
44+
expect(described_class.from(wrapped_value, actual)).to be(wrapped_value)
4445
end
4546
end
4647

@@ -49,7 +50,7 @@ def create_matcher(stubs)
4950

5051
it "has a diff for all matchers with their description" do
5152
expect(wrapped_value.message_with_diff(
52-
message, differ, actual
53+
message, differ
5354
)).to eq(dedent <<-EOS)
5455
|a message
5556
|Diff for (matcher 1 description):["actual value", "expected 1"]
@@ -63,7 +64,7 @@ def create_matcher(stubs)
6364
it "returns a message warning if the diff is empty" do
6465
allow(FakeDiffer).to receive(:diff) { "\e[0m\n\e[0m" }
6566
expect(wrapped_value.message_with_diff(
66-
message, differ, actual
67+
message, differ
6768
)).to eq(dedent <<-EOS)
6869
|a message
6970
|Diff:
@@ -74,26 +75,28 @@ def create_matcher(stubs)
7475
it "returns just provided message if diff is just whitespace" do
7576
allow(FakeDiffer).to receive(:diff) { " \n \t" }
7677
expect(wrapped_value.message_with_diff(
77-
message, differ, actual
78+
message, differ
7879
)).to eq(dedent <<-EOS)
7980
|a message
8081
EOS
8182
end
8283

8384
it "returns regular message with diff when single expected" do
8485
expect(wrapped_value.message_with_diff(
85-
message, differ, actual
86+
message, differ
8687
)).to eq(dedent <<-EOS)
8788
|a message
8889
|Diff:["actual value", "expected value"]
8990
EOS
9091
end
9192

9293
it "returns message with diff and matcher description when single expected with matcher" do
93-
wrapped_value = described_class.for_many_matchers([include("expected value")])
94+
matcher = include("expected value")
95+
matcher.matches?(actual)
96+
wrapped_value = described_class.for_many_matchers([matcher])
9497

9598
expect(wrapped_value.message_with_diff(
96-
message, differ, actual
99+
message, differ
97100
)).to eq(dedent <<-EOS)
98101
|a message
99102
|Diff for (include "expected value"):["actual value", ["expected value"]]
@@ -104,7 +107,7 @@ def create_matcher(stubs)
104107
wrapped_value = described_class.for_many_matchers([matcher_with_long_description])
105108

106109
expect(wrapped_value.message_with_diff(
107-
message, differ, actual
110+
message, differ
108111
)).to eq(dedent <<-EOS)
109112
|a message
110113
|Diff for (#{truncated_description}):["actual value", "expected value"]

0 commit comments

Comments
 (0)