Browse Source

Re-resolve the right-click target while waiting for its hit test

Right_Clicking_A_Second_Row_Moves_The_Context_Highlight_To_It still timed
out on the Windows Debug CI job, now in the hit-test wait added for the
second right-click: for the full 60s no hit at the precomputed point matched
the captured row container. A light-dismiss overlay that survives one frame
cannot explain that many rendered frames; a container that is no longer the
one on screen can. The test only waits for three assemblies, so the rest of
the list keeps loading on the slow agent while the test runs, and every
insertion reshuffles the rows - re-realising containers and moving them -
after the row and point were captured.

The wait now resolves the row container and the click point on every poll
and matches the hit by node instead of by container identity, and a timeout
reports the point and what was hit instead so a further failure is
diagnosable from the log.

Assisted-by: Claude:claude-fable-5:Claude Code
pull/4082/head
Siegfried Pammer 2 weeks ago
parent
commit
1bc8ebb8bd
  1. 45
      ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs

45
ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs

@ -252,20 +252,41 @@ public class DecompileInNewViewTests @@ -252,20 +252,41 @@ public class DecompileInNewViewTests
var menu = grid.ContextMenu!;
async Task RightClick(SharpTreeNode node)
Point? ClickPoint(SharpTreeNode node)
{
var row = Row(node);
var row = grid.GetVisualDescendants()
.OfType<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem>()
.FirstOrDefault(r => RowNodeEquals(r, node));
if (row == null)
return null;
var clickX = System.Math.Min(row.Bounds.Width, grid.Bounds.Width) / 2;
var pt = row.TranslatePoint(new Point(clickX, row.Bounds.Height / 2), window);
// A popup's light-dismiss overlay keeps answering hit tests for as long as the frame
// that still shows it, and it swallows a press without raising ContextRequested - so
// nothing becomes the context target. Hit testing the point is the same question the
// context-request handler asks, so waiting for it to reach the row is exactly the
// precondition for this click, however many frames the overlay takes to disappear.
await Waiters.WaitForAsync(
() => window.InputHitTest(pt!.Value) is Visual hit
&& ReferenceEquals(hit.FindAncestorOfType<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem>(includeSelf: true), row),
description: "the row to answer hit tests at the point about to be right-clicked");
return row.TranslatePoint(new Point(clickX, row.Bounds.Height / 2), window);
}
async Task RightClick(SharpTreeNode node)
{
// A press only becomes a context request for the row when the hit test at the press
// point answers with that row - the same question OnTreeContextRequested asks - and it
// takes an unknown number of frames for that to hold: a closed popup's light-dismiss
// overlay keeps answering hit tests until the scene is rendered again, and assemblies
// still loading in the background reshuffle the rows, which re-realises containers and
// shifts them. So the row container and the point are resolved afresh on every poll and
// the hit is matched by node, not by container identity.
Point? pt = null;
Visual? lastHit = null;
try
{
await Waiters.WaitForAsync(
() => (pt = ClickPoint(node)) is { } p
&& (lastHit = window.InputHitTest(p) as Visual) is { } hit
&& hit.FindAncestorOfType<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem>(includeSelf: true) is { } hitRow
&& RowNodeEquals(hitRow, node),
description: "the row to answer hit tests at the point about to be right-clicked");
}
catch (System.TimeoutException ex)
{
throw new System.TimeoutException($"{ex.Message} (point: {pt?.ToString() ?? "row not realised"}, hit: {lastHit?.GetType().Name ?? "nothing"})", ex);
}
HeadlessWindowExtensions.MouseDown(window, pt!.Value, MouseButton.Right);
HeadlessWindowExtensions.MouseUp(window, pt.Value, MouseButton.Right);
// The highlight is scoped to the popup - set while the menu is being requested, dropped

Loading…
Cancel
Save