From 24368a07b5b368c7baacef5e6d21a8207c844a51 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Tue, 3 Nov 2026 15:11:06 +0100 Subject: [PATCH] test: run async UI test bodies to completion Avalonia's Dispatch(Func) returns at the first real await, so the assertions after it never ran and async ViewModel tests passed vacuously. UiSession.Run now drives the body to completion while pumping the dispatcher. The test profile has no checkpoints, since the fake chains use tiny heights that the real mainnet checkpoints would try to anchor. --- .../PalladiumWallet.Tests/App/HeadlessSetup.cs | 18 +++++++++++++++++- tests/PalladiumWallet.Tests/App/VmTestBase.cs | 5 ++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/PalladiumWallet.Tests/App/HeadlessSetup.cs b/tests/PalladiumWallet.Tests/App/HeadlessSetup.cs index 628979f..02349bc 100644 --- a/tests/PalladiumWallet.Tests/App/HeadlessSetup.cs +++ b/tests/PalladiumWallet.Tests/App/HeadlessSetup.cs @@ -22,7 +22,23 @@ internal static class UiSession private static readonly Lazy Session = new(() => HeadlessUnitTestSession.StartNew(typeof(TestAppBuilder))); - public static Task Run(Func body) => Session.Value.Dispatch(body, CancellationToken.None); + // Avalonia's Dispatch(Func) returns as soon as the body reaches its first real await: + // everything after it (including the assertions) would silently never run and the test would + // pass vacuously. So the async body is driven to completion here, pumping the dispatcher on + // the UI thread (the continuations of its awaits are posted there), with a hard time limit. + public static Task Run(Func body) => Session.Value.Dispatch(() => + { + var task = body(); + var deadline = DateTime.UtcNow + TimeSpan.FromMinutes(2); + while (!task.IsCompleted) + { + if (DateTime.UtcNow > deadline) + throw new TimeoutException("Async UI test body did not complete within 2 minutes."); + Avalonia.Threading.Dispatcher.UIThread.RunJobs(); + Thread.Sleep(1); + } + task.GetAwaiter().GetResult(); + }, CancellationToken.None); public static Task Run(Action body) => Session.Value.Dispatch(() => { body(); return Task.CompletedTask; }, CancellationToken.None); diff --git a/tests/PalladiumWallet.Tests/App/VmTestBase.cs b/tests/PalladiumWallet.Tests/App/VmTestBase.cs index 9c131c2..a78a30d 100644 --- a/tests/PalladiumWallet.Tests/App/VmTestBase.cs +++ b/tests/PalladiumWallet.Tests/App/VmTestBase.cs @@ -34,7 +34,10 @@ public abstract class VmTestBase : IDisposable MainWindowViewModel.UpdateCheck = _ => Task.FromResult(null); // No bootstrap servers: the only reachable server is the in-process fake, so the // VM's connect/reconnect loops can never touch a real indexing server. - MainWindowViewModel.ProfileOverride = ChainProfiles.Mainnet with { BootstrapServers = [] }; + // No checkpoints either: the fake chains use tiny heights, and with the real mainnet checkpoints + // every proof would have to be anchored up to height 20,000 (ADR 0040), which the fake + // server does not serve. Tests that care about anchoring set their own profile. + MainWindowViewModel.ProfileOverride = ChainProfiles.Mainnet with { BootstrapServers = [], Checkpoints = [] }; new AppConfig { LastServerHost = _server.Host, LastServerPort = _server.Port, LastServerUseSsl = false } .Save(AppPaths.ConfigPath()); }