From 06eb33b52e5294227571aa8ed9a67555c9ad3121 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sat, 22 Aug 2026 23:13:28 +1000 Subject: [PATCH] Show the snapshot an F# entry is anchored on QueueEntry.Expected decided "new snapshot" from OriginalExpression being null and never looked at OriginalValue, which is the anchor a producer sends when its language does not implement CallerArgumentExpression. F# does not, so every F# entry opened with an empty expected pane and every received line marked as added - while the applier was anchoring on that same value and the staged expected.txt was written from it. It is the snapshot the expression would have parsed to, not source text: the patcher compares it against what a literal parses to, and against NewContent for an already-applied patch. So it goes in the pane as it is, under the plain "expected" header, and only a patch with neither anchor still reads as a new snapshot. --- .../FsharpEntryTests.Screen.verified.txt | 24 +++++++++ .../FsharpEntryTests.cs | 54 +++++++++++++++++++ src/DiffEngineViewer/QueueEntry.cs | 11 +++- 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/DiffEngineViewer.Tests/FsharpEntryTests.Screen.verified.txt create mode 100644 src/DiffEngineViewer.Tests/FsharpEntryTests.cs diff --git a/src/DiffEngineViewer.Tests/FsharpEntryTests.Screen.verified.txt b/src/DiffEngineViewer.Tests/FsharpEntryTests.Screen.verified.txt new file mode 100644 index 00000000..5c5b3822 --- /dev/null +++ b/src/DiffEngineViewer.Tests/FsharpEntryTests.Screen.verified.txt @@ -0,0 +1,24 @@ ++----------------------------------------------------------------------------------------------+ +| SampleTests.fs:42 inline 1 of 1 | ++----------------------+-----------------------------------+-----------------------------------+ +| Pending (1) | received | expected | ++----------------------+-----------------------------------+-----------------------------------+ +| > SampleTests.fs:42 | 1 the quick | 1 the quick | +| | ~ 2 brown dog | ~ 2 brown fox | +| | 3 jumps over | 3 jumps over | +| | 4 the lazy | 4 the lazy | +| | 5 dog | 5 dog | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | ++----------------------+-----------------------------------+-----------------------------------+ +| [Accept] [Discard] [Accept all] lines 1-5 of 5 | ++----------------------------------------------------------------------------------------------+ \ No newline at end of file diff --git a/src/DiffEngineViewer.Tests/FsharpEntryTests.cs b/src/DiffEngineViewer.Tests/FsharpEntryTests.cs new file mode 100644 index 00000000..1c8d688f --- /dev/null +++ b/src/DiffEngineViewer.Tests/FsharpEntryTests.cs @@ -0,0 +1,54 @@ +/// +/// An entry from a producer whose language does not implement CallerArgumentExpression, which is +/// F#. There is no source text for the previous argument, so the patch carries its value instead - +/// the same snapshot the expression would have parsed to, and the same anchor one parse further +/// on. +/// +public class FsharpEntryTests +{ + [Test] + public async Task The_previous_snapshot_fills_the_expected_pane() + { + var state = Fixtures.Inline(Patch()); + + var entry = state.Queue[0]; + + await Assert.That(entry.RightHeader).IsEqualTo("expected"); + await Assert.That(entry.RightText).IsEqualTo(Fixtures.Expected); + await Assert.That(entry.Warning).IsNull(); + } + + /// + /// With neither anchor there is genuinely nothing to compare against, which is what the empty + /// side is for. + /// + [Test] + public async Task A_first_run_still_reads_as_a_new_snapshot() + { + var state = Fixtures.Inline( + new InlinePatch("SampleTests.fs", 42, null, Fixtures.Received) + { + TestName = null + }); + + var entry = state.Queue[0]; + + await Assert.That(entry.RightHeader).IsEqualTo("expected (new snapshot)"); + await Assert.That(entry.RightText).IsEmpty(); + } + + /// + /// And what that looks like: two full panes with one line differing, rather than five added + /// lines beside nothing. + /// + [Test] + public Task Screen() => + Verify(Fixtures.Render(Fixtures.Inline(Patch()))); + + static InlinePatch Patch() => + new("SampleTests.fs", 42, null, Fixtures.Received) + { + TestName = null, + OriginalValue = Fixtures.Expected + }; +} diff --git a/src/DiffEngineViewer/QueueEntry.cs b/src/DiffEngineViewer/QueueEntry.cs index fce0b082..e54c06e5 100644 --- a/src/DiffEngineViewer/QueueEntry.cs +++ b/src/DiffEngineViewer/QueueEntry.cs @@ -198,7 +198,16 @@ public static QueueEntry ForDelete( { if (patch.OriginalExpression is null) { - return ("expected (new snapshot)", "", null); + // A producer whose language has no CallerArgumentExpression - F#, which does not + // implement it - anchors on the argument's value instead. It is the same snapshot the + // expression would have parsed to, so it is the same pane; without this every F# + // entry read as a new snapshot against an empty side, with every received line new. + if (patch.OriginalValue is null) + { + return ("expected (new snapshot)", "", null); + } + + return ("expected", SourceLanguage.NormalizeNewlines(patch.OriginalValue), null); } // Read as the language of the file it came out of: an F# literal is not a C# one, and a