7b0b9c7365
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
76 lines
3.1 KiB
C#
76 lines
3.1 KiB
C#
using Bunit;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;
|
|
|
|
/// <summary>
|
|
/// Regression tests for CentralUI-011. <c>DiffDialog.OpenAsync</c> returns the
|
|
/// <c>TaskCompletionSource</c>'s task, completed only by <c>Close()</c>. If the
|
|
/// user navigated away while the dialog was open, <c>DisposeAsync</c> 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
|
|
/// <c>DisposeAsync</c>.
|
|
/// </summary>
|
|
public class DiffDialogTests : BunitContext
|
|
{
|
|
/// <summary>
|
|
/// 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 <c>await</c>s them (e.g. <c>DisposeAsync</c> →
|
|
/// <c>TryUnlockBodyAsync</c>) 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.
|
|
/// </summary>
|
|
private void SetupBodyLockInterop()
|
|
{
|
|
JSInterop.Mode = JSRuntimeMode.Loose;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DisposeAsync_WhileOpen_CompletesPendingTask()
|
|
{
|
|
SetupBodyLockInterop();
|
|
var cut = Render<DiffDialog>();
|
|
|
|
// 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<bool> 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<DiffDialog>();
|
|
|
|
// Block-bodied lambda so InvokeAsync sees a void delegate — it must NOT
|
|
// await the dialog's own (deliberately long-lived) task.
|
|
Task<bool> 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);
|
|
}
|
|
}
|