feat(theme): upstream router controls and responsive shell fixes
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class BrandBarTests : TestContext
|
||||
public class BrandBarTests : BunitContext
|
||||
{
|
||||
[Fact]
|
||||
public void Renders_product_with_default_mark()
|
||||
{
|
||||
var cut = RenderComponent<BrandBar>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
var cut = Render<BrandBar>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
Assert.Contains("OtOpcUa", cut.Markup);
|
||||
Assert.NotNull(cut.Find(".brand .mark")); // default glyph when no Logo
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class BrandBarTests : TestContext
|
||||
[Fact]
|
||||
public void Custom_logo_replaces_default_mark()
|
||||
{
|
||||
var cut = RenderComponent<BrandBar>(p => p
|
||||
var cut = Render<BrandBar>(p => p
|
||||
.Add(x => x.Product, "ScadaBridge")
|
||||
.Add(x => x.Logo, (RenderFragment)(b => b.AddMarkupContent(0, "<img class='logo'/>"))));
|
||||
Assert.NotNull(cut.Find(".brand .logo"));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class CommonControlsTests : TestContext
|
||||
public class CommonControlsTests : BunitContext
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ButtonVariant.Primary, "btn-primary")]
|
||||
@@ -9,7 +9,7 @@ public class CommonControlsTests : TestContext
|
||||
[InlineData(ButtonVariant.Ghost, "btn-link")]
|
||||
public void TechButton_maps_variant(ButtonVariant v, string cls)
|
||||
{
|
||||
var cut = RenderComponent<TechButton>(p => p.Add(x => x.Variant, v).AddChildContent("Go"));
|
||||
var cut = Render<TechButton>(p => p.Add(x => x.Variant, v).AddChildContent("Go"));
|
||||
var btn = cut.Find("button");
|
||||
Assert.Contains("btn", btn.ClassList);
|
||||
Assert.Contains(cls, btn.ClassList);
|
||||
@@ -18,7 +18,7 @@ public class CommonControlsTests : TestContext
|
||||
[Fact]
|
||||
public void TechButton_busy_disables_and_passes_through_attributes()
|
||||
{
|
||||
var cut = RenderComponent<TechButton>(p => p
|
||||
var cut = Render<TechButton>(p => p
|
||||
.Add(x => x.Busy, true)
|
||||
.AddUnmatched("id", "save")
|
||||
.AddChildContent("Save"));
|
||||
@@ -30,14 +30,14 @@ public class CommonControlsTests : TestContext
|
||||
[Fact]
|
||||
public void TechButton_not_busy_is_not_disabled()
|
||||
{
|
||||
var cut = RenderComponent<TechButton>(p => p.Add(x => x.Busy, false).AddChildContent("Go"));
|
||||
var cut = Render<TechButton>(p => p.Add(x => x.Busy, false).AddChildContent("Go"));
|
||||
Assert.False(cut.Find("button").HasAttribute("disabled"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TechCard_renders_title_and_body()
|
||||
{
|
||||
var cut = RenderComponent<TechCard>(p => p
|
||||
var cut = Render<TechCard>(p => p
|
||||
.Add(x => x.Title, "Drivers")
|
||||
.AddChildContent("<div class='b'>x</div>"));
|
||||
Assert.Contains("Drivers", cut.Find(".panel-head").TextContent);
|
||||
@@ -47,7 +47,7 @@ public class CommonControlsTests : TestContext
|
||||
[Fact]
|
||||
public void TechField_renders_label_hint_error()
|
||||
{
|
||||
var cut = RenderComponent<TechField>(p => p
|
||||
var cut = Render<TechField>(p => p
|
||||
.Add(x => x.Label, "Name")
|
||||
.Add(x => x.Hint, "required")
|
||||
.Add(x => x.Error, "missing")
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class LoginCardTests : TestContext
|
||||
public class LoginCardTests : BunitContext
|
||||
{
|
||||
[Fact]
|
||||
public void Posts_to_action_with_username_password_fields()
|
||||
{
|
||||
var cut = RenderComponent<LoginCard>(p => p
|
||||
var cut = Render<LoginCard>(p => p
|
||||
.Add(x => x.Product, "OtOpcUa")
|
||||
.Add(x => x.Action, "/auth/login"));
|
||||
var form = cut.Find("form");
|
||||
@@ -19,7 +19,7 @@ public class LoginCardTests : TestContext
|
||||
[Fact]
|
||||
public void ReturnUrl_renders_hidden_input()
|
||||
{
|
||||
var cut = RenderComponent<LoginCard>(p => p
|
||||
var cut = Render<LoginCard>(p => p
|
||||
.Add(x => x.Product, "OtOpcUa")
|
||||
.Add(x => x.ReturnUrl, "/clusters"));
|
||||
var hidden = cut.Find("input[name=returnUrl]");
|
||||
@@ -29,7 +29,7 @@ public class LoginCardTests : TestContext
|
||||
[Fact]
|
||||
public void Error_renders_notice()
|
||||
{
|
||||
var cut = RenderComponent<LoginCard>(p => p
|
||||
var cut = Render<LoginCard>(p => p
|
||||
.Add(x => x.Product, "OtOpcUa")
|
||||
.Add(x => x.Error, "Bad credentials"));
|
||||
Assert.Contains("Bad credentials", cut.Find(".notice").TextContent);
|
||||
@@ -38,7 +38,7 @@ public class LoginCardTests : TestContext
|
||||
[Fact]
|
||||
public void No_returnUrl_no_hidden_input()
|
||||
{
|
||||
var cut = RenderComponent<LoginCard>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
var cut = Render<LoginCard>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
Assert.Empty(cut.FindAll("input[name=returnUrl]"));
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class LoginCardTests : TestContext
|
||||
[Fact]
|
||||
public void Product_is_wrapped_in_login_product_span()
|
||||
{
|
||||
var cut = RenderComponent<LoginCard>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
var cut = Render<LoginCard>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
var product = cut.Find(".login-title .login-product");
|
||||
Assert.Equal("OtOpcUa", product.TextContent);
|
||||
Assert.Contains("sign in", cut.Find(".login-title").TextContent);
|
||||
@@ -57,7 +57,7 @@ public class LoginCardTests : TestContext
|
||||
[Fact]
|
||||
public void Heading_overrides_default_heading_when_set()
|
||||
{
|
||||
var cut = RenderComponent<LoginCard>(p => p
|
||||
var cut = Render<LoginCard>(p => p
|
||||
.Add(x => x.Product, "OtOpcUa")
|
||||
.Add(x => x.Heading, "Welcome back"));
|
||||
var title = cut.Find(".login-title");
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class NavRailTests : TestContext
|
||||
public class NavRailTests : BunitContext
|
||||
{
|
||||
[Fact]
|
||||
public void NavRailItem_renders_rail_link_with_href_and_text()
|
||||
{
|
||||
var cut = RenderComponent<NavRailItem>(p => p
|
||||
var cut = Render<NavRailItem>(p => p
|
||||
.Add(x => x.Href, "/clusters")
|
||||
.Add(x => x.Text, "Clusters"));
|
||||
var a = cut.Find("a.rail-link");
|
||||
@@ -17,7 +17,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailItem_wraps_icon_in_rail_ico_span_when_supplied()
|
||||
{
|
||||
var cut = RenderComponent<NavRailItem>(p => p
|
||||
var cut = Render<NavRailItem>(p => p
|
||||
.Add(x => x.Href, "/clusters")
|
||||
.Add(x => x.Text, "Clusters")
|
||||
.Add(x => x.Icon, (RenderFragment)(b => b.AddMarkupContent(0, "<svg class='ico'/>"))));
|
||||
@@ -29,7 +29,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailItem_omits_rail_ico_span_when_no_icon()
|
||||
{
|
||||
var cut = RenderComponent<NavRailItem>(p => p
|
||||
var cut = Render<NavRailItem>(p => p
|
||||
.Add(x => x.Href, "/clusters")
|
||||
.Add(x => x.Text, "Clusters"));
|
||||
Assert.Empty(cut.FindAll(".rail-ico"));
|
||||
@@ -38,7 +38,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_renders_title_and_children_open_by_default()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Navigation")
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
var details = cut.Find("details.rail-section");
|
||||
@@ -52,7 +52,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_summary_aria_expanded_true_when_open()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Navigation")
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.Equal("true", cut.Find("summary.rail-eyebrow-toggle").GetAttribute("aria-expanded"));
|
||||
@@ -61,7 +61,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_collapsed_when_not_expanded()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Nav").Add(x => x.Expanded, false)
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.False(cut.Find("details.rail-section").HasAttribute("open"));
|
||||
@@ -71,7 +71,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_summary_aria_expanded_false_when_collapsed()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Nav").Add(x => x.Expanded, false)
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.Equal("false", cut.Find("summary.rail-eyebrow-toggle").GetAttribute("aria-expanded"));
|
||||
@@ -80,7 +80,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_emits_data_nav_key_slug_from_title_by_default()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Site Calls")
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.Equal("site-calls", cut.Find("details.rail-section").GetAttribute("data-nav-key"));
|
||||
@@ -89,7 +89,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_emits_explicit_key_when_supplied()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Navigation").Add(x => x.Key, "nav")
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.Equal("nav", cut.Find("details.rail-section").GetAttribute("data-nav-key"));
|
||||
@@ -98,7 +98,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_whitespace_only_title_yields_empty_data_nav_key()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, " ")
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.Equal("", cut.Find("details.rail-section").GetAttribute("data-nav-key"));
|
||||
@@ -107,7 +107,7 @@ public class NavRailTests : TestContext
|
||||
[Fact]
|
||||
public void NavRailSection_slug_preserves_unicode_letters()
|
||||
{
|
||||
var cut = RenderComponent<NavRailSection>(p => p
|
||||
var cut = Render<NavRailSection>(p => p
|
||||
.Add(x => x.Title, "Café")
|
||||
.AddChildContent("<a class='rail-link'>X</a>"));
|
||||
Assert.Equal("café", cut.Find("details.rail-section").GetAttribute("data-nav-key"));
|
||||
|
||||
@@ -39,6 +39,36 @@ public class StaticAssetsTests
|
||||
public void NavStateScript_ships() =>
|
||||
Assert.True(File.Exists(Path.Combine(Wwwroot, "js", "nav-state.js")));
|
||||
|
||||
[Fact]
|
||||
public void Shared_layout_ships_row_action_specificity_and_variable_size_modifiers()
|
||||
{
|
||||
var css = File.ReadAllText(Path.Combine(Wwwroot, "css", "layout.css"));
|
||||
Assert.Matches(@"td\.row-actions\s*\{\s*text-align:\s*right", css);
|
||||
foreach (var selector in new[] { ".page-head", ".page-meta", ".app-section", ".app-head",
|
||||
".app-meta", ".group-head", ".group-name", ".group-meta", ".count-badge",
|
||||
".count-badge.warn", ".notice.info", ".row-actions", ".check-data" })
|
||||
Assert.Contains(selector, css);
|
||||
Assert.Contains("var(--tt-depth, 0)", css);
|
||||
Assert.Matches(@"\.btn-lg\s*\{\s*--bs-btn-font-size:\s*1rem", css);
|
||||
Assert.Matches(@"\.tt-leaf\s*\{\s*visibility:\s*hidden", css);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Closed_mobile_shell_hides_only_the_rail_and_keeps_main_content_visible()
|
||||
{
|
||||
var css = File.ReadAllText(Path.Combine(Wwwroot, "css", "layout.css"));
|
||||
css = Regex.Replace(css, @"/\*.*?\*/", "", RegexOptions.Singleline);
|
||||
var baseRules = css[..css.IndexOf("@media", StringComparison.Ordinal)];
|
||||
Assert.Matches(@"\.app-shell::details-content\s*\{[^}]*display:\s*contents", baseRules);
|
||||
Assert.Matches(@"\.app-shell\s*>\s*main\.page\s*\{[^}]*display:\s*block", baseRules);
|
||||
var mobile = css[css.IndexOf("@media (max-width: 991.98px)", StringComparison.Ordinal)..];
|
||||
Assert.Matches(@"\.app-shell:not\(\[open\]\)\s*>\s*#theme-rail\s*\{\s*display:\s*none", mobile);
|
||||
var page = Regex.Match(mobile, @"\.app-shell\s*>\s*\.page\s*\{([^}]*)\}").Groups[1].Value;
|
||||
Assert.Matches(@"\bwidth:\s*100%", page);
|
||||
Assert.Matches(@"\bmax-width:\s*100%", page);
|
||||
Assert.Matches(@"\bbox-sizing:\s*border-box", page);
|
||||
}
|
||||
|
||||
// Theme-002: .chip-idle pairs the idle background with the matching --idle
|
||||
// foreground token (per DESIGN-TOKENS.md), not --ink-soft.
|
||||
[Fact]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class StatusPillTests : TestContext
|
||||
public class StatusPillTests : BunitContext
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(StatusState.Ok, "chip-ok")]
|
||||
@@ -10,7 +10,7 @@ public class StatusPillTests : TestContext
|
||||
[InlineData(StatusState.Info, "chip-info")]
|
||||
public void Maps_state_to_chip_class(StatusState state, string expected)
|
||||
{
|
||||
var cut = RenderComponent<StatusPill>(p => p
|
||||
var cut = Render<StatusPill>(p => p
|
||||
.Add(x => x.State, state)
|
||||
.AddChildContent("Connected"));
|
||||
var span = cut.Find("span");
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public sealed class TechButtonExtensionsTests : BunitContext
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(ButtonSize.Default, null)]
|
||||
[InlineData(ButtonSize.Small, "btn-sm")]
|
||||
[InlineData(ButtonSize.Large, "btn-lg")]
|
||||
public void Size_preserves_variant_and_composes_both_class_inputs(ButtonSize size, string? expected)
|
||||
{
|
||||
var cut = Render<TechButton>(p => p
|
||||
.Add(x => x.Variant, ButtonVariant.Secondary)
|
||||
.Add(x => x.Size, size)
|
||||
.Add(x => x.Class, "explicit-action")
|
||||
.Add(x => x.AdditionalAttributes, new Dictionary<string, object>
|
||||
{
|
||||
["class"] = "caller-action", ["aria-label"] = "Save changes"
|
||||
})
|
||||
.AddChildContent("Save"));
|
||||
|
||||
var button = cut.Find("button");
|
||||
foreach (var cls in new[] { "btn", "btn-outline-secondary", "explicit-action", "caller-action" })
|
||||
Assert.Contains(cls, button.ClassList);
|
||||
Assert.Equal("Save changes", button.GetAttribute("aria-label"));
|
||||
if (expected is not null) Assert.Contains(expected, button.ClassList);
|
||||
else
|
||||
{
|
||||
Assert.DoesNotContain("btn-sm", button.ClassList);
|
||||
Assert.DoesNotContain("btn-lg", button.ClassList);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Class_composition_keeps_busy_type_and_click_behavior()
|
||||
{
|
||||
var clicks = 0;
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
["class"] = "caller", ["disabled"] = false,
|
||||
["onclick"] = EventCallback.Factory.Create(this, () => clicks++)
|
||||
};
|
||||
var cut = Render<TechButton>(p => p
|
||||
.Add(x => x.Class, "extra")
|
||||
.Add(x => x.Type, "submit")
|
||||
.Add(x => x.Busy, true)
|
||||
.Add(x => x.AdditionalAttributes, attributes)
|
||||
.AddChildContent("Save"));
|
||||
|
||||
Assert.True(cut.Find("button").HasAttribute("disabled"));
|
||||
Assert.Equal("submit", cut.Find("button").GetAttribute("type"));
|
||||
Assert.NotNull(cut.Find(".spinner-border"));
|
||||
Assert.Equal("caller", attributes["class"]);
|
||||
cut.Render(p => p.Add(x => x.Busy, false));
|
||||
cut.Find("button").Click();
|
||||
Assert.Equal(1, clicks);
|
||||
Assert.DoesNotContain(cut.FindAll("span"), x => x.ClassList.Contains("spinner-border"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Lowercase_class_attribute_preserves_required_button_classes()
|
||||
{
|
||||
var cut = Render<TechButton>(p => p.AddUnmatched("class", "caller").AddChildContent("Go"));
|
||||
foreach (var cls in new[] { "btn", "btn-primary", "caller" })
|
||||
Assert.Contains(cls, cut.Find("button").ClassList);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class ThemeHeadTests : TestContext
|
||||
public class ThemeHeadTests : BunitContext
|
||||
{
|
||||
[Fact]
|
||||
public void Emits_theme_and_layout_links_to_content_path()
|
||||
{
|
||||
var cut = RenderComponent<ThemeHead>();
|
||||
var cut = Render<ThemeHead>();
|
||||
var hrefs = cut.FindAll("link").Select(l => l.GetAttribute("href")).ToList();
|
||||
Assert.Contains("_content/ZB.MOM.WW.Theme/css/theme.css", hrefs);
|
||||
Assert.Contains("_content/ZB.MOM.WW.Theme/css/layout.css", hrefs);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class ThemeScriptsTests : TestContext
|
||||
public class ThemeScriptsTests : BunitContext
|
||||
{
|
||||
[Fact]
|
||||
public void ThemeScripts_emits_nav_state_script_tag()
|
||||
{
|
||||
var cut = RenderComponent<ThemeScripts>();
|
||||
var cut = Render<ThemeScripts>();
|
||||
var script = cut.Find("script");
|
||||
Assert.Equal("_content/ZB.MOM.WW.Theme/js/nav-state.js", script.GetAttribute("src"));
|
||||
Assert.True(script.HasAttribute("defer"));
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public class ThemeShellTests : TestContext
|
||||
public class ThemeShellTests : BunitContext
|
||||
{
|
||||
[Fact]
|
||||
public void Renders_product_nav_and_body()
|
||||
{
|
||||
var cut = RenderComponent<ThemeShell>(p => p
|
||||
var cut = Render<ThemeShell>(p => p
|
||||
.Add(x => x.Product, "OtOpcUa")
|
||||
.Add(x => x.Nav, (RenderFragment)(b => b.AddMarkupContent(0, "<a class='rail-link'>N</a>")))
|
||||
.AddChildContent("<div class='pagebody'>BODY</div>"));
|
||||
@@ -18,7 +18,7 @@ public class ThemeShellTests : TestContext
|
||||
[Fact]
|
||||
public void Accent_sets_css_variable_on_shell_root()
|
||||
{
|
||||
var cut = RenderComponent<ThemeShell>(p => p
|
||||
var cut = Render<ThemeShell>(p => p
|
||||
.Add(x => x.Product, "ScadaBridge")
|
||||
.Add(x => x.Accent, "#2f855a"));
|
||||
var shell = cut.Find(".app-shell");
|
||||
@@ -28,14 +28,14 @@ public class ThemeShellTests : TestContext
|
||||
[Fact]
|
||||
public void No_accent_emits_no_style()
|
||||
{
|
||||
var cut = RenderComponent<ThemeShell>(p => p.Add(x => x.Product, "MXAccess Gateway"));
|
||||
var cut = Render<ThemeShell>(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 = RenderComponent<ThemeShell>(p => p
|
||||
var cut = Render<ThemeShell>(p => p
|
||||
.Add(x => x.Product, "OtOpcUa")
|
||||
.Add(x => x.RailFooter, (RenderFragment)(b => b.AddMarkupContent(0, "<span class='sess'>S</span>"))));
|
||||
Assert.NotNull(cut.Find(".rail-foot .sess"));
|
||||
@@ -47,7 +47,7 @@ public class ThemeShellTests : TestContext
|
||||
[Fact]
|
||||
public void Mobile_toggle_is_a_css_only_details_disclosure_not_bootstrap_collapse()
|
||||
{
|
||||
var cut = RenderComponent<ThemeShell>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
var cut = Render<ThemeShell>(p => p.Add(x => x.Product, "OtOpcUa"));
|
||||
|
||||
// Root is a <details> whose direct <summary> is the hamburger toggle.
|
||||
var shell = cut.Find("details.app-shell");
|
||||
@@ -62,4 +62,18 @@ public class ThemeShellTests : TestContext
|
||||
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<ThemeShell>(p => p.Add(x => x.Product, "OEE")
|
||||
.Add(x => x.Nav, builder => builder.AddMarkupContent(0, "<a href='/oee'>OEE</a>"))
|
||||
.AddChildContent("<h1>OEE report</h1><input aria-label='Filter' />"));
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
namespace ZB.MOM.WW.Theme.Tests;
|
||||
|
||||
public sealed class TreeTableTests : BunitContext
|
||||
{
|
||||
private sealed record TestRow(string Key, int Depth, bool HasChildren, string Name) : ITreeRow;
|
||||
|
||||
private IRenderedComponent<TreeTable<TestRow>> RenderTreeTable(IReadOnlyList<TestRow> rows,
|
||||
IReadOnlySet<string>? collapsed = null, Action<string>? toggle = null)
|
||||
=> Render<TreeTable<TestRow>>(p => p
|
||||
.Add(x => x.Rows, rows)
|
||||
.Add(x => x.Collapsed, collapsed ?? new HashSet<string>())
|
||||
.Add(x => x.Label, row => row.Name)
|
||||
.Add(x => x.Head, builder => builder.AddMarkupContent(0, "<th>Scope</th><th>Value</th>"))
|
||||
.Add(x => x.Row, row => builder =>
|
||||
{
|
||||
builder.OpenElement(0, "td");
|
||||
builder.AddContent(1, "Value for " + row.Name);
|
||||
builder.CloseElement();
|
||||
})
|
||||
.Add(x => x.OnToggle, key => toggle?.Invoke(key)));
|
||||
|
||||
[Fact]
|
||||
public void Caller_fragments_and_arbitrary_depth_are_rendered_without_inline_stylesheets()
|
||||
{
|
||||
var cut = RenderTreeTable([new("root", 0, true, "Site"), new("deep", 7, false, "Machine <7>")]);
|
||||
Assert.Equal(2, cut.FindAll("thead th").Count);
|
||||
Assert.Equal(2, cut.FindAll("tbody tr").Count);
|
||||
Assert.Equal("Value for Machine <7>", cut.FindAll("tbody td")[3].TextContent);
|
||||
Assert.Contains("Machine <7>", cut.Find(".tt-d7 .tt-name").TextContent);
|
||||
Assert.Contains("--tt-depth: 7", cut.Find(".tt-d7 .tt-name").GetAttribute("style"));
|
||||
Assert.Empty(cut.FindAll("style"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Leaf_keeps_alignment_without_promising_a_subtree()
|
||||
{
|
||||
var cut = RenderTreeTable([new("leaf", 1, false, "Cell")]);
|
||||
var twist = cut.Find("button");
|
||||
Assert.Contains("tt-leaf", twist.ClassList);
|
||||
Assert.True(twist.HasAttribute("disabled"));
|
||||
Assert.False(twist.HasAttribute("aria-expanded"));
|
||||
Assert.False(twist.HasAttribute("aria-label"));
|
||||
Assert.Equal("true", cut.Find("i").GetAttribute("aria-hidden"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Toggle_reports_stable_key_and_caller_controls_expansion()
|
||||
{
|
||||
string? toggled = null;
|
||||
var cut = RenderTreeTable([new("site-key", 0, true, "Site")],
|
||||
new HashSet<string> { "site-key" }, key => toggled = key);
|
||||
Assert.Equal("false", cut.Find("button").GetAttribute("aria-expanded"));
|
||||
Assert.Equal("Expand Site", cut.Find("button").GetAttribute("aria-label"));
|
||||
cut.Find("button").Click();
|
||||
Assert.Equal("site-key", toggled);
|
||||
Assert.Equal("false", cut.Find("button").GetAttribute("aria-expanded"));
|
||||
cut.Render(p => p.Add(x => x.Collapsed, new HashSet<string>()));
|
||||
Assert.Equal("true", cut.Find("button").GetAttribute("aria-expanded"));
|
||||
Assert.Equal("Collapse Site", cut.Find("button").GetAttribute("aria-label"));
|
||||
Assert.Contains("bi-chevron-down", cut.Find("i").ClassList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_rows_keep_the_header_and_empty_body()
|
||||
{
|
||||
var cut = RenderTreeTable([]);
|
||||
Assert.Equal(2, cut.FindAll("thead th").Count);
|
||||
Assert.Empty(cut.FindAll("tbody tr"));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="bunit" />
|
||||
<PackageReference Include="AngleSharp" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
|
||||
Reference in New Issue
Block a user