namespace ZB.MOM.WW.Theme.Tests; public class ThemeShellTests : BunitContext { [Fact] public void Renders_product_nav_and_body() { var cut = Render(p => p .Add(x => x.Product, "OtOpcUa") .Add(x => x.Nav, (RenderFragment)(b => b.AddMarkupContent(0, "N"))) .AddChildContent("
BODY
")); Assert.NotNull(cut.Find(".side-rail .brand")); Assert.Contains("OtOpcUa", cut.Markup); Assert.NotNull(cut.Find(".side-rail .rail-link")); Assert.NotNull(cut.Find("main.page .pagebody")); } [Fact] public void ScrollNav_defaults_off_and_markup_is_unchanged() { var cut = Render(p => p .Add(x => x.Product, "OtOpcUa") .Add(x => x.Nav, (RenderFragment)(b => b.AddMarkupContent(0, "N")))); Assert.Empty(cut.FindAll(".rail-scroll")); Assert.Equal("side-rail", cut.Find("nav").ClassName); Assert.NotNull(cut.Find("nav.side-rail > .rail-link")); } [Fact] public void ScrollNav_wraps_nav_in_scroll_pane_between_brand_and_footer() { var cut = Render(p => p .Add(x => x.Product, "ScadaBridge") .Add(x => x.ScrollNav, true) .Add(x => x.Nav, (RenderFragment)(b => b.AddMarkupContent(0, "N"))) .Add(x => x.RailFooter, (RenderFragment)(b => b.AddMarkupContent(0, "S")))); Assert.Contains("side-rail-scroll", cut.Find("nav").ClassList); Assert.NotNull(cut.Find("nav.side-rail-scroll > .rail-scroll > .rail-link")); // Footer is a sibling of the pane, not inside it, so it stays pinned. Assert.NotNull(cut.Find("nav.side-rail-scroll > .rail-foot .sess")); Assert.Empty(cut.FindAll(".rail-scroll .rail-foot")); var kids = cut.Find("nav").Children.Select(c => c.ClassName).ToList(); Assert.True(kids.IndexOf("rail-scroll") < kids.IndexOf("rail-foot")); } [Fact] public void Accent_sets_css_variable_on_shell_root() { var cut = Render(p => p .Add(x => x.Product, "ScadaBridge") .Add(x => x.Accent, "#2f855a")); var shell = cut.Find(".app-shell"); Assert.Contains("--accent: #2f855a", shell.GetAttribute("style")); } [Fact] public void No_accent_emits_no_style() { var cut = Render(p => p.Add(x => x.Product, "MXAccess Gateway")); Assert.False(cut.Find(".app-shell").HasAttribute("style")); } [Fact] public void RailFooter_renders_when_supplied() { var cut = Render(p => p .Add(x => x.Product, "OtOpcUa") .Add(x => x.RailFooter, (RenderFragment)(b => b.AddMarkupContent(0, "S")))); Assert.NotNull(cut.Find(".rail-foot .sess")); } // Theme-001: the mobile hamburger must work without Bootstrap collapse JS. // The shell is a native
disclosure whose is the toggle — // no data-bs-toggle / data-bs-target attributes, so no Bootstrap JS dependency. [Fact] public void Mobile_toggle_is_a_css_only_details_disclosure_not_bootstrap_collapse() { var cut = Render(p => p.Add(x => x.Product, "OtOpcUa")); // Root is a
whose direct is the hamburger toggle. var shell = cut.Find("details.app-shell"); var summary = cut.Find("details.app-shell > summary.rail-toggle"); Assert.NotNull(summary); // The rail is plain markup inside the
— no Bootstrap .collapse class. var rail = cut.Find("#theme-rail"); Assert.DoesNotContain("collapse", rail.ClassList); // No Bootstrap collapse JS hooks anywhere in the shell markup. Assert.DoesNotContain("data-bs-toggle", shell.OuterHtml); Assert.DoesNotContain("data-bs-target", shell.OuterHtml); } [Fact] public void Closed_shell_keeps_page_body_separate_from_the_toggled_rail() { var cut = Render(p => p.Add(x => x.Product, "OEE") .Add(x => x.Nav, builder => builder.AddMarkupContent(0, "OEE")) .AddChildContent("

OEE report

")); Assert.False(cut.Find("details.app-shell").HasAttribute("open")); Assert.NotNull(cut.Find("details.app-shell > #theme-rail > nav")); Assert.Equal("OEE report", cut.Find("details.app-shell > main.page > h1").TextContent); Assert.NotNull(cut.Find("details.app-shell > main.page > input")); Assert.Empty(cut.FindAll("#theme-rail main")); } }