using Bunit; using Microsoft.Extensions.DependencyInjection; using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Shared; /// /// Deferred-work register row 75 — pins the confirm button's severity → Bootstrap class /// mapping against the REAL behind a real DialogHost, /// so these cover what the user actually sees rather than what an argument was. /// /// /// Row 28 split the label from the styling but left the styling a two-state boolean, so a /// consequential-but-not-destructive action had to choose between looking like a delete /// and looking like the safe default. Row 75 added as /// the middle rung. The three-way mapping is the whole of that change, and these are its /// pins; SecuredWritesTests separately pins that the Secured Writes page asks for /// the right one. /// /// /// /// bUnit cannot run real JS interop, so the host's sbDialog.* focus-trap calls run /// in as no-ops (same arrangement as /// FailoverConfirmLabelTests). /// /// public class DialogSeverityStylingTests : BunitContext { private readonly DialogService _dialog = new(); public DialogSeverityStylingTests() { Services.AddSingleton(_dialog); JSInterop.Mode = JSRuntimeMode.Loose; } /// /// Opens a confirm dialog on a live host and returns its confirm button. /// /// /// The ConfirmAsync task is deliberately not awaited — with the real service it /// does not complete until someone answers the dialog, so awaiting it would deadlock /// the test. The state is published synchronously before the task is returned. /// private AngleSharp.Dom.IElement OpenConfirm(Func> open) { var cut = Render(); cut.InvokeAsync(() => { _ = open(_dialog); }).GetAwaiter().GetResult(); cut.Render(); var footer = cut.FindAll("div.modal-footer button").ToList(); return footer.Single(b => b.TextContent.Trim() != "Cancel"); } [Fact] public void Caution_rendersAmber_notRedAndNotPrimary() { // The row-75 case. Amber is the point: not red (which reads "delete"), and not // the primary blue (which reads "safe default" — wrong for a live device write). var confirm = OpenConfirm(d => d.ConfirmAsync("Approve thing", "Sure?", DialogSeverity.Caution, "Approve")); Assert.Contains("btn-warning", confirm.ClassList); Assert.DoesNotContain("btn-danger", confirm.ClassList); Assert.DoesNotContain("btn-primary", confirm.ClassList); } [Fact] public void Destructive_stillRendersRed() { // Regression guard on the ~20 genuine delete call sites: row 75 is additive and // must not have shifted what danger looks like. var confirm = OpenConfirm(d => d.ConfirmAsync("Delete thing", "Sure?", DialogSeverity.Destructive)); Assert.Contains("btn-danger", confirm.ClassList); } [Fact] public void Normal_stillRendersPrimary() { var confirm = OpenConfirm(d => d.ConfirmAsync("Do thing", "Sure?", DialogSeverity.Normal)); Assert.Contains("btn-primary", confirm.ClassList); } [Theory] [InlineData(true, "btn-danger")] [InlineData(false, "btn-primary")] public void BooleanForm_mapsOntoTheSameTwoSeverities(bool danger, string expectedClass) { // The boolean overload is now a thin adapter onto the severity one. Every existing // call site goes through it, so this pins that the adapter did not shift any of // them: true is still red, false is still primary. // // One case per fact, deliberately: DialogService permits only one open dialog at a // time and throws on the second, so two OpenConfirm calls in one body would fail // on the service's own nested-dialog guard rather than on anything about styling. var confirm = OpenConfirm(d => d.ConfirmAsync("Do thing", "Sure?", danger: danger)); Assert.Contains(expectedClass, confirm.ClassList); } [Fact] public void UnlabelledCaution_readsConfirm_notDelete() { // Caution deliberately does NOT inherit the danger "Delete" fallback: a caution // action is by definition not a delete, so falling back to that verb would // reintroduce register row 28 through the new door. var confirm = OpenConfirm(d => d.ConfirmAsync("Approve thing", "Sure?", DialogSeverity.Caution)); Assert.Equal("Confirm", confirm.TextContent.Trim()); } [Fact] public void UnlabelledDestructive_stillFallsBackToDelete() { // The row-28 fallback is retained deliberately (the Playwright suites click the // literal "Delete"). Pinned here alongside its new sibling so the pair is read // together. var confirm = OpenConfirm(d => d.ConfirmAsync("Delete thing", "Sure?", DialogSeverity.Destructive)); Assert.Equal("Delete", confirm.TextContent.Trim()); } }