using Bunit; using ScadaLink.CentralUI.Components.Shared; namespace ScadaLink.CentralUI.Tests.Shared; /// /// Regression tests for CentralUI-011. DiffDialog.OpenAsync returns the /// TaskCompletionSource's task, completed only by Close(). If the /// user navigated away while the dialog was open, DisposeAsync ran but /// never completed the TCS — the awaiting caller was suspended forever and any /// cleanup after the await was skipped. The fix completes the TCS in /// DisposeAsync. /// public class DiffDialogTests : BunitContext { /// /// DiffDialog applies/removes a body scroll-lock class and focuses the modal /// via JS interop on open/close. Loose mode auto-completes those void calls /// so a path that awaits them (e.g. DisposeAsync → /// TryUnlockBodyAsync) resumes instead of hanging on a never-completed /// planned invocation, and no strict-mode unplanned-invocation exception /// surfaces through the narrowed CentralUI-023 catch blocks. /// private void SetupBodyLockInterop() { JSInterop.Mode = JSRuntimeMode.Loose; } [Fact] public async Task DisposeAsync_WhileOpen_CompletesPendingTask() { SetupBodyLockInterop(); var cut = Render(); // Open the dialog; the returned task represents the caller's await. // Block-bodied lambda so InvokeAsync sees a void delegate — it must NOT // await the dialog's own (deliberately long-lived) task. Task pending = null!; await cut.InvokeAsync( () => { pending = cut.Instance.ShowAsync("Compare", "before", "after"); }); Assert.False(pending.IsCompleted, "Dialog task should be pending while open."); // Simulate navigating away while the dialog is still open. await cut.InvokeAsync(async () => await cut.Instance.DisposeAsync()); // The awaiter must complete deterministically rather than hang forever. var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2))); Assert.Same(pending, completed); Assert.True(pending.IsCompletedSuccessfully); var result = await pending; Assert.False(result, "Dismiss-on-dispose should resolve to false (not confirmed)."); } [Fact] public async Task Close_CompletesPendingTaskWithTrue() { SetupBodyLockInterop(); var cut = Render(); // Block-bodied lambda so InvokeAsync sees a void delegate — it must NOT // await the dialog's own (deliberately long-lived) task. Task pending = null!; await cut.InvokeAsync( () => { pending = cut.Instance.ShowAsync("Compare", "before", "after"); }); // Closing via the Close button completes the task with true. await cut.InvokeAsync(() => cut.Find("button.btn-secondary").Click()); var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2))); Assert.Same(pending, completed); var result = await pending; Assert.True(result); } }