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);
}
}