fix(centralui): Esc-to-close and aria attributes on ExecutionDetailModal

This commit is contained in:
Joseph Doherty
2026-05-22 01:43:41 -04:00
parent 386cd0b955
commit 5c86983ef6
3 changed files with 40 additions and 2 deletions

View File

@@ -13,13 +13,16 @@
<div class="modal-backdrop fade show" data-test="execution-detail-backdrop"
@onclick="HandleClose"></div>
<div class="modal fade show d-block execution-detail-modal" tabindex="-1"
data-test="execution-detail-modal" role="dialog">
data-test="execution-detail-modal" role="dialog"
aria-modal="true" aria-labelledby="execution-detail-modal-title"
@onkeydown="HandleKeyDown">
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<div>
<div class="text-muted small text-uppercase">Execution</div>
<h5 class="modal-title mb-0 d-flex align-items-baseline gap-2">
<h5 id="execution-detail-modal-title"
class="modal-title mb-0 d-flex align-items-baseline gap-2">
<span class="font-monospace">Execution @ShortExecutionId()</span>
@if (!_loading && _error is null)
{

View File

@@ -1,5 +1,6 @@
using System.Globalization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using ScadaLink.CentralUI.Services;
using ScadaLink.Commons.Entities.Audit;
using ScadaLink.Commons.Types.Audit;
@@ -112,6 +113,10 @@ public partial class ExecutionDetailModal
try
{
// No CancellationToken is passed deliberately: this is a bounded,
// small (~100-row) query for one execution, so the IDisposable/CTS
// machinery is not worth it for a modal. The closed → open guard in
// OnParametersSetAsync cleanly re-loads on the next open if needed.
_rows = await AuditLogQueryService.QueryAsync(
new AuditLogQueryFilter(ExecutionId: ExecutionId.Value),
new AuditLogPaging(PageSize: RowPageSize));
@@ -150,6 +155,19 @@ public partial class ExecutionDetailModal
}
}
/// <summary>
/// Closes the modal when Escape is pressed, matching the header X, backdrop
/// click, and footer Close affordances. The root <c>.modal</c> div carries
/// <c>tabindex="-1"</c> so it can receive the keydown.
/// </summary>
private async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Escape")
{
await HandleClose();
}
}
/// <summary>First 8 hex digits of the execution id, mirroring the UI's short-id convention.</summary>
private string ShortExecutionId()
{

View File

@@ -263,6 +263,23 @@ public class ExecutionDetailModalTests : BunitContext
Assert.True(closed);
}
[Fact]
public void EscapeKey_RaisesOnClose()
{
var executionId = Guid.NewGuid();
StubRows(new[] { MakeEvent(executionId), MakeEvent(executionId) });
var closed = false;
var cut = Render<ExecutionDetailModal>(p => p
.Add(c => c.ExecutionId, executionId)
.Add(c => c.IsOpen, true)
.Add(c => c.OnClose, EventCallback.Factory.Create(this, () => closed = true)));
cut.Find("[data-test=\"execution-detail-modal\"]").KeyDown("Escape");
Assert.True(closed);
}
[Fact]
public void Header_ShowsShortExecutionId_AndRowCount()
{