Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Shared/DialogHostShowAsyncTests.cs
T
Joseph Doherty 513fa1aa16 docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
2026-10-07 15:05:49 -04:00

190 lines
7.3 KiB
C#

using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared;
/// <summary>
/// bUnit tests for the third <c>DialogHost</c> dialog kind (T33a):
/// <see cref="IDialogService.ShowAsync{TResult}"/> renders arbitrary custom
/// body content, omits the standard Cancel/Confirm footer (the body supplies
/// its own buttons), and resolves a typed result through the
/// <see cref="DialogContext{TResult}"/> the body is handed. The tests render a
/// real <see cref="DialogService"/> behind a live <c>DialogHost</c> so the
/// interface contract is exercised end to end.
///
/// bUnit cannot run real JS interop, so the host's <c>sbDialog.*</c> focus-trap
/// / focus-restoration calls are exercised in <see cref="JSRuntimeMode.Loose"/>
/// mode where they become no-ops.
/// </summary>
public class DialogHostShowAsyncTests : BunitContext
{
private readonly DialogService _service = new();
/// <summary>Initializes the test class and registers the dialog services.</summary>
public DialogHostShowAsyncTests()
{
// Register the concrete service as the interface so DialogHost (which
// injects IDialogService and casts to DialogService) resolves the same
// instance the test drives.
Services.AddSingleton<IDialogService>(_service);
// Focus trap / restoration / body-lock interop are no-ops under test.
JSInterop.Mode = JSRuntimeMode.Loose;
}
/// <summary>Verifies that ShowAsync renders a custom body and resolves the typed result.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ShowAsync_RendersCustomBody_AndResolvesTypedResult()
{
var cut = Render<DialogHost>();
// Open a custom dialog whose body renders a single button that closes
// the dialog with a typed string result via the supplied context.
Task<string?> pending = null!;
await cut.InvokeAsync(() =>
{
pending = _service.ShowAsync<string>("Title", ctx => builder =>
{
builder.OpenElement(0, "button");
builder.AddAttribute(1, "data-test", "ok");
builder.AddAttribute(2, "onclick",
EventCallback.Factory.Create(this, () => ctx.Close("done")));
builder.CloseElement();
});
});
cut.Render();
// The custom body renders, and the standard footer is NOT present.
Assert.NotNull(cut.Find("button[data-test='ok']"));
Assert.DoesNotContain(cut.FindAll("button"),
b => b.TextContent.Trim() is "Cancel" or "Confirm");
Assert.False(pending.IsCompleted, "Dialog must stay open until the body acts.");
// Clicking the custom button resolves the awaited task with the value.
cut.Find("button[data-test='ok']").Click();
var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
Assert.Same(pending, completed);
Assert.Equal("done", await pending);
}
/// <summary>Verifies that cancelling resolves ShowAsync with null.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ShowAsync_CancelResolvesNull()
{
var cut = Render<DialogHost>();
Task<string?> pending = null!;
await cut.InvokeAsync(() =>
{
pending = _service.ShowAsync<string>("Title", ctx => builder =>
{
builder.OpenElement(0, "button");
builder.AddAttribute(1, "data-test", "cancel");
builder.AddAttribute(2, "onclick",
EventCallback.Factory.Create(this, () => ctx.Cancel()));
builder.CloseElement();
});
});
cut.Render();
cut.Find("button[data-test='cancel']").Click();
var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
Assert.Same(pending, completed);
Assert.Null(await pending);
}
/// <summary>Verifies that pressing Escape resolves ShowAsync with null.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ShowAsync_EscapeResolvesNull()
{
var cut = Render<DialogHost>();
Task<string?> pending = null!;
await cut.InvokeAsync(() =>
{
pending = _service.ShowAsync<string>("Title", _ => builder =>
{
builder.OpenElement(0, "span");
builder.AddContent(1, "no buttons");
builder.CloseElement();
});
});
cut.Render();
// The host's Escape handler cancels custom dialogs the same way.
cut.Find("div.modal").KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Key = "Escape" });
var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
Assert.Same(pending, completed);
Assert.Null(await pending);
}
/// <summary>Verifies that ShowAsync applies the size class to the modal dialog.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ShowAsync_AppliesSizeClassToModalDialog()
{
var cut = Render<DialogHost>();
await cut.InvokeAsync(() =>
{
_ = _service.ShowAsync<string>("Title", _ => builder =>
{
builder.AddContent(0, "body");
}, size: "modal-lg");
});
cut.Render();
var dialog = cut.Find("div.modal-dialog");
Assert.Contains("modal-lg", dialog.ClassList);
}
/// <summary>Verifies that the backdrop carries the token hook class.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Backdrop_HasTokenHookClass()
{
var cut = Render<DialogHost>();
await cut.InvokeAsync(() =>
{
_ = _service.ShowAsync<string>("Title", _ => builder =>
{
builder.AddContent(0, "body");
});
});
cut.Render();
// The backdrop carries the tokenized hook class so a later task can
// attach the actual var-driven background rule without touching markup.
var backdrop = cut.Find("div.modal-backdrop");
Assert.Contains("sb-modal-backdrop", backdrop.ClassList);
}
/// <summary>Verifies that Confirm still resolves true, with no regression.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Confirm_StillResolvesTrue_NoRegression()
{
var cut = Render<DialogHost>();
Task<bool> pending = null!;
await cut.InvokeAsync(() => { pending = _service.ConfirmAsync("Title", "Sure?"); });
cut.Render();
// The standard footer's Confirm button is present for Confirm-kind dialogs.
cut.FindAll("button").First(b => b.TextContent.Trim() == "Confirm").Click();
var completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(2)));
Assert.Same(pending, completed);
Assert.True(await pending);
}
}