diff --git a/ILSpy.Tests/Views/DebugStepsTests.cs b/ILSpy.Tests/Views/DebugStepsTests.cs index 8e0dc076c..7f7ddff13 100644 --- a/ILSpy.Tests/Views/DebugStepsTests.cs +++ b/ILSpy.Tests/Views/DebugStepsTests.cs @@ -177,13 +177,13 @@ public class DebugStepsTests await tab.RestartDecompileWithStepLimit(replayStep.BeginStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("C# replay before a selected AST mutation step must still emit code"); - tab.DebugStepHighlight.Should().NotBeNull("C# replay before a selected AST mutation step must locate the changed node"); + AssertPreciseHighlight(tab, "C# replay before a selected AST mutation step must locate the changed node"); debugStepsVm.Steps.Should().BeSameAs(collectedSteps, "a step-limited C# replay must not replace the full step tree shown by the pane"); await tab.RestartDecompileWithStepLimit(replayStep.EndStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("C# replay after a selected AST mutation step must still emit code"); - tab.DebugStepHighlight.Should().NotBeNull("C# replay after a selected AST mutation step must locate the changed node"); + AssertPreciseHighlight(tab, "C# replay after a selected AST mutation step must locate the changed node"); debugStepsVm.Steps.Should().BeSameAs(collectedSteps, "a step-limited C# replay must preserve the current full-run step tree and selection context"); @@ -229,13 +229,13 @@ public class DebugStepsTests await tab.RestartDecompileWithStepLimit(replayStep!.BeginStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("ILAst replay before a selected step must still emit IL"); - tab.DebugStepHighlight.Should().NotBeNull("ILAst replay before a selected step must locate the changed instruction"); + AssertPreciseHighlight(tab, "ILAst replay before a selected step must locate the changed instruction"); debugStepsVm.Steps.Should().BeSameAs(collectedSteps, "a step-limited ILAst replay must not replace the full step tree shown by the pane"); await tab.RestartDecompileWithStepLimit(replayStep.EndStep, isDebug: false, replayStep.BeginStep); tab.Text.Should().NotBeNullOrWhiteSpace("ILAst replay after a selected step must still emit IL"); - tab.DebugStepHighlight.Should().NotBeNull("ILAst replay after a selected step must locate the changed instruction"); + AssertPreciseHighlight(tab, "ILAst replay after a selected step must locate the changed instruction"); // The first leaf step that acts on a concrete instruction; a step whose Position is null // (e.g. an empty transform group) has nothing to highlight and is not what a user replays. @@ -391,6 +391,26 @@ public class DebugStepsTests return Task.CompletedTask; } + // A replay highlight must land on the changed node, not merely be non-null: in bounds, not a + // flood of the whole document, and (unless it is a zero-length removal caret) on rendered code + // rather than whitespace. This is what keeps the ancestor fallback from silently widening every + // highlight to the enclosing method undetected. + static void AssertPreciseHighlight(DecompilerTabPageModel tab, string because) + { + tab.DebugStepHighlight.Should().NotBeNull(because); + var range = tab.DebugStepHighlight!.Value; + var text = tab.Text!; + (range.Start >= 0 && range.Start + range.Length <= text.Length).Should() + .BeTrue("the debug-step highlight must lie within the emitted document"); + range.Length.Should().BeLessThan(text.Length, + "the highlight must mark a specific node, not flood the whole document"); + if (range.Length > 0) + { + text.Substring(range.Start, range.Length).Trim().Should() + .NotBeEmpty("a non-caret highlight must cover rendered code, not just whitespace"); + } + } + [AvaloniaTest] public Task MarkNodeStart_Excludes_Leading_Indentation() {