Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,28 +250,32 @@ ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem Row(SharpTreeNode node) =>
.OfType<ICSharpCode.ILSpy.Controls.TreeView.SharpTreeViewItem>()
.First(r => RowNodeEquals(r, node));

var menu = grid.ContextMenu!;

async Task RightClick(SharpTreeNode node)
{
var row = Row(node);
var clickX = System.Math.Min(row.Bounds.Width, grid.Bounds.Width) / 2;
var pt = row.TranslatePoint(new Point(clickX, row.Bounds.Height / 2), window);
HeadlessWindowExtensions.MouseDown(window, pt!.Value, MouseButton.Right);
HeadlessWindowExtensions.MouseUp(window, pt.Value, MouseButton.Right);
for (int i = 0; i < 4; i++)
{
Dispatcher.UIThread.RunJobs();
await Task.Delay(20);
}
// The highlight is scoped to the popup - set while the menu is being requested, dropped
// again when it closes - so the popup is the point at which the gesture is finished and
// the row's classes are worth reading.
await Waiters.WaitForAsync(() => menu.IsOpen, description: "the right-clicked row's context menu to open");
}

async Task Dismiss()
{
window.KeyPress(Key.Escape, RawInputModifiers.None, PhysicalKey.Escape, keySymbol: null);
await Waiters.WaitForAsync(() => !menu.IsOpen, description: "the context menu to close");
// IsOpen flips the moment the popup is torn down, but the frame that still shows its
// light-dismiss overlay is what answers hit tests until the scene is rendered again.
// Right-clicking into that frame delivers press and release to the vanishing overlay
// instead of the row: no ContextRequested is raised at all, so nothing becomes the
// context target and the next assertion sees a row with no classes on it.
for (int i = 0; i < 4; i++)
{
Dispatcher.UIThread.RunJobs();
await Task.Delay(20);
}
Waiters.PumpUI();
}

await RightClick(nodeB);
Expand Down
19 changes: 18 additions & 1 deletion ILSpy.Tests/Waiters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using System.Threading.Tasks;

using Avalonia;
using Avalonia.Headless;
using Avalonia.Threading;
using Avalonia.VisualTree;

Expand Down Expand Up @@ -51,13 +52,29 @@ public static async Task WaitForAsync(
{
if (predicate())
return;
Dispatcher.UIThread.RunJobs();
PumpUI();
await Task.Delay(PollInterval);
}
throw new TimeoutException(
$"Timed out after {(timeout ?? DefaultTimeout).TotalSeconds:0.#}s waiting for: {description}");
}

/// <summary>
/// Advances the UI by one step: runs the queued dispatcher jobs and renders a frame.
/// </summary>
/// <remarks>
/// Both halves matter. Hit testing for synthesized input is answered from the rendered scene,
/// not from the visual tree, so a loop that only runs dispatcher jobs leaves input routing a
/// frame behind: a click can still be delivered to a control the last frame shows but the tree
/// no longer has - a closed popup's light-dismiss overlay, for one - and never reach what is
/// underneath. Avalonia's own headless input helpers pump both for exactly this reason.
/// </remarks>
public static void PumpUI()
{
Dispatcher.UIThread.RunJobs();
AvaloniaHeadlessPlatform.ForceRenderTimerTick();
}

public static async Task WaitForAssembliesAsync(
this AssemblyTreeModel atm,
int minimumCount = 1,
Expand Down
Loading