using Bunit; using Microsoft.AspNetCore.Components; using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared; /// /// Accessibility regression tests. /// Locks the a11y attributes on TreeView chevron toggles and the /// ToastNotification live-region container against future regressions. /// public class AccessibilityTests : BunitContext { // ── TreeView harness (mirrors TreeViewTests setup) ───────────────────────── private record TestNode(string Key, string Label, List Children); private static List BranchRoots() => new() { new("root-1", "Root One", new() { new("child-1", "Child One", new()), }), new("leaf-1", "Leaf One", new()), }; private IRenderedComponent> RenderTreeView(List? items = null) { return Render>(parameters => { parameters .Add(p => p.Items, items ?? BranchRoots()) .Add(p => p.ChildrenSelector, n => n.Children) .Add(p => p.HasChildrenSelector, n => n.Children.Count > 0) .Add(p => p.KeySelector, n => n.Key) .Add(p => p.NodeContent, node => builder => { builder.AddMarkupContent(0, $"{node.Label}"); }); }); } // ── Test 1: TreeView chevron toggle a11y attributes ──────────────────────── [Fact] public void TreeViewToggle_HasAccessibleNameAndRole() { // BranchRoots has one branch node ("root-1") and one leaf node ("leaf-1"). // The branch node's toggle span is the only .tv-toggle rendered. var cut = RenderTreeView(); var toggle = cut.Find(".tv-toggle"); // role="button" makes it announced as a button by screen readers. Assert.Equal("button", toggle.GetAttribute("role")); // tabindex="0" puts it in the natural tab order. Assert.Equal("0", toggle.GetAttribute("tabindex")); // aria-label must be non-empty (e.g. "Expand root-1"). var label = toggle.GetAttribute("aria-label"); Assert.NotNull(label); Assert.NotEmpty(label!); // aria-expanded reflects the collapsed state ("false" when collapsed). Assert.Equal("false", toggle.GetAttribute("aria-expanded")); } [Fact] public void TreeViewToggle_AriaExpanded_ReflectsExpandedState() { var cut = RenderTreeView(); // Collapsed initially. var toggle = cut.Find(".tv-toggle"); Assert.Equal("false", toggle.GetAttribute("aria-expanded")); // Click to expand. toggle.Click(); // aria-expanded must now be "true". var expandedToggle = cut.Find(".tv-toggle"); Assert.Equal("true", expandedToggle.GetAttribute("aria-expanded")); // aria-label should also flip to "Collapse …". var labelAfterExpand = expandedToggle.GetAttribute("aria-label"); Assert.NotNull(labelAfterExpand); Assert.StartsWith("Collapse", labelAfterExpand!); } // ── Test 2: ToastNotification live-region regression lock ───────────────── [Fact] public void ToastNotification_Container_IsAriaLivePolite() { // Render ToastNotification — it has no required services/cascading params. var cut = Render(); // The outermost div is the live-region container. // Find the element that carries aria-live (may be the root or a child div). var liveRegion = cut.Find("[aria-live]"); Assert.Equal("polite", liveRegion.GetAttribute("aria-live")); Assert.Equal("true", liveRegion.GetAttribute("aria-atomic")); } }