Name dialogs and contain native overlay keyboard focus
This commit is contained in:
+17
@@ -33,6 +33,21 @@ public class AuditDrilldownDrawerTests : BunitContext
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
}
|
||||
|
||||
/// <summary>The drawer has modal semantics and Escape dispatches close once.</summary>
|
||||
[Fact]
|
||||
public void OpenDrawer_HasNamedModalAndEscapeCloses()
|
||||
{
|
||||
var closes = 0;
|
||||
var cut = Render<AuditDrilldownDrawer>(p => p.Add(c => c.Event, MakeEvent())
|
||||
.Add(c => c.IsOpen, true).Add(c => c.OnClose, () => closes++));
|
||||
var dialog = cut.Find("[role=dialog]");
|
||||
Assert.Equal("true", dialog.GetAttribute("aria-modal"));
|
||||
var titleId = dialog.GetAttribute("aria-labelledby");
|
||||
Assert.Contains("Audit Event", cut.Find("#" + titleId).TextContent);
|
||||
dialog.KeyDown(new Microsoft.AspNetCore.Components.Web.KeyboardEventArgs { Key = "Escape" });
|
||||
Assert.Equal(1, closes);
|
||||
}
|
||||
|
||||
private static AuditEventView MakeEvent(
|
||||
AuditChannel channel = AuditChannel.ApiOutbound,
|
||||
AuditKind kind = AuditKind.ApiCall,
|
||||
@@ -372,6 +387,8 @@ public class AuditDrilldownDrawerTests : BunitContext
|
||||
{
|
||||
// Set up Strict mode interop so the call must match exactly.
|
||||
JSInterop.Mode = JSRuntimeMode.Strict;
|
||||
JSInterop.Setup<bool?>("sbDialog.activate", _ => true).SetResult(true);
|
||||
JSInterop.SetupVoid("sbDialog.deactivate", _ => true).SetVoidResult();
|
||||
var clipboardCall = JSInterop.SetupVoid(
|
||||
"navigator.clipboard.writeText",
|
||||
invocation => invocation.Arguments.Count == 1
|
||||
|
||||
@@ -33,6 +33,46 @@ public class DialogHostShowAsyncTests : BunitContext
|
||||
JSInterop.Mode = JSRuntimeMode.Loose;
|
||||
}
|
||||
|
||||
/// <summary>Unsupported overlapping presentation cancels safely; disposal resolves pending callers.</summary>
|
||||
[Fact]
|
||||
public async Task RejectedOverlayAndDisposal_ResolvePendingResults()
|
||||
{
|
||||
JSInterop.Setup<bool?>("sbDialog.activate", _ => true).SetResult(false);
|
||||
var cut = Render<DialogHost>();
|
||||
Task<bool> pending = null!;
|
||||
await cut.InvokeAsync(() => { pending = _service.ConfirmAsync("Rejected", "Local only"); });
|
||||
cut.WaitForAssertion(() => Assert.Null(_service.Current));
|
||||
Assert.False(await pending);
|
||||
JSInterop.Setup<bool?>("sbDialog.activate", _ => true).SetResult(true);
|
||||
await cut.InvokeAsync(() => { pending = _service.ConfirmAsync("Open", "Local only"); });
|
||||
await cut.InvokeAsync(async () => await cut.Instance.DisposeAsync());
|
||||
Assert.False(await pending);
|
||||
Assert.Null(_service.Current);
|
||||
}
|
||||
|
||||
/// <summary>Names successive dialogs and connects prompt labels without stale descriptions.</summary>
|
||||
[Fact]
|
||||
public async Task Dialog_NamesAndPromptAssociationsFollowCurrentDialog()
|
||||
{
|
||||
var cut = Render<DialogHost>();
|
||||
await cut.InvokeAsync(() => { _ = _service.ConfirmAsync("First title", "First body"); });
|
||||
var first = cut.Find("[role=dialog]");
|
||||
var titleId = first.GetAttribute("aria-labelledby");
|
||||
Assert.False(string.IsNullOrWhiteSpace(titleId));
|
||||
Assert.Equal("First title", cut.Find("#" + titleId).TextContent);
|
||||
var descriptionId = first.GetAttribute("aria-describedby");
|
||||
Assert.Equal("First body", cut.Find("#" + descriptionId).TextContent);
|
||||
await cut.InvokeAsync(() => cut.Find("button.btn-close").Click());
|
||||
await cut.InvokeAsync(() => { _ = _service.PromptAsync("Second title", "Enter a value"); });
|
||||
var prompt = cut.Find("[role=dialog]");
|
||||
Assert.Equal("Second title", cut.Find("#" + prompt.GetAttribute("aria-labelledby")).TextContent);
|
||||
var input = cut.Find("input");
|
||||
Assert.Equal(input.Id, cut.Find("label").GetAttribute("for"));
|
||||
Assert.False(string.IsNullOrWhiteSpace(input.Id));
|
||||
Assert.DoesNotContain("First body", cut.Markup);
|
||||
await cut.InvokeAsync(() => cut.Find("button.btn-close").Click());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ShowAsync renders a custom body and resolves the typed result.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user