using Bunit; using Bunit.TestDoubles; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using SufiChain.SufiBlazor.Components.Navigation; using Xunit; namespace SufiChain.SufiBlazor.Tests.Components.Navigation; public class SbNavMenuTests : BunitContext { private IRenderedComponent RenderNavMenu( Action>? configure = null) { return Render(p => configure?.Invoke(p)); } private IRenderedComponent RenderNavItem( Action>? configure = null) { return Render(p => configure?.Invoke(p)); } // --- SbNavMenu tests --- [Fact] public void RendersNavMenuStructure() { // Arrange & Act var cut = RenderNavMenu(p => p.AddChildContent("Nav content")); // Assert var nav = cut.Find("nav.sb-nav-menu"); Assert.NotNull(nav); Assert.Equal("nav", nav.TagName.ToLowerInvariant()); Assert.Equal("Navigation", nav.GetAttribute("aria-label")); } [Fact] public void RendersChildContent() { // Arrange & Act var cut = RenderNavMenu(p => p.AddChildContent("
  • Item
")); // Assert Assert.Contains("Item", cut.Markup); Assert.NotNull(cut.Find("ul")); } [Fact] public void UsesCustomAriaLabel() { // Arrange & Act var cut = RenderNavMenu(p => p .Add(x => x.AriaLabel, "Main menu") .AddChildContent("")); // Assert var nav = cut.Find("nav"); Assert.Equal("Main menu", nav.GetAttribute("aria-label")); } [Fact] public void AddsCollapsedClassWhenCollapsedTrue() { // Arrange & Act var cut = RenderNavMenu(p => p .Add(x => x.Collapsed, true) .AddChildContent("")); // Assert var nav = cut.Find("nav.sb-nav-menu"); Assert.Contains("sb-nav-menu--collapsed", nav.ClassList); } [Fact] public void DoesNotAddCollapsedClassWhenCollapsedFalse() { // Arrange & Act var cut = RenderNavMenu(p => p .Add(x => x.Collapsed, false) .AddChildContent("")); // Assert var nav = cut.Find("nav.sb-nav-menu"); Assert.DoesNotContain("sb-nav-menu--collapsed", nav.ClassList); } [Fact] public void AppliesClassParameter() { // Arrange & Act var cut = RenderNavMenu(p => p .Add(x => x.Class, "sidebar-menu") .AddChildContent("")); // Assert var nav = cut.Find("nav.sb-nav-menu"); Assert.Contains("sidebar-menu", nav.ClassList); } [Fact] public void AppliesStyleParameter() { // Arrange & Act var cut = RenderNavMenu(p => p .Add(x => x.Style, "width: 200px;") .AddChildContent("")); // Assert var nav = cut.Find("nav"); var style = nav.GetAttribute("style"); Assert.Contains("width: 200px", style); } [Fact] public async Task ToggleAsync_FlipsCollapsedAndInvokesCallback() { // Arrange var received = false; var cut = RenderNavMenu(p => p .Add(x => x.Collapsed, false) .Add(x => x.CollapsedChanged, EventCallback.Factory.Create(this, v => received = v)) .AddChildContent("")); // Act await cut.InvokeAsync(() => cut.Instance.ToggleAsync()); // Assert Assert.True(received); cut.Render(); var nav = cut.Find("nav.sb-nav-menu"); Assert.Contains("sb-nav-menu--collapsed", nav.ClassList); } [Fact] public void PassesAdditionalAttributes() { // Arrange & Act var cut = RenderNavMenu(p => p .Add(x => x.AdditionalAttributes, new Dictionary { { "data-testid", "main-nav" } }) .AddChildContent("")); // Assert var nav = cut.Find("nav"); Assert.Equal("main-nav", nav.GetAttribute("data-testid")); } [Fact] public void RendersNavItemChildren() { // Arrange & Act var cut = Render(p => p.AddChildContent(b => { b.OpenComponent(0); b.AddAttribute(1, "Href", "/"); b.AddAttribute(2, "Text", "Home"); b.CloseComponent(); b.OpenComponent(3); b.AddAttribute(4, "Href", "/about"); b.AddAttribute(5, "Text", "About"); b.CloseComponent(); })); // Assert var nav = cut.Find("nav.sb-nav-menu"); Assert.NotNull(nav); var items = cut.FindAll("a.sb-nav-item"); Assert.Equal(2, items.Count); Assert.Contains("Home", cut.Markup); Assert.Contains("About", cut.Markup); } // --- SbNavItem tests --- [Fact] public void NavItem_RendersAsAnchor() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/dashboard") .Add(x => x.Text, "Dashboard")); // Assert var anchor = cut.Find("a.sb-nav-item"); Assert.NotNull(anchor); Assert.Equal("/dashboard", anchor.GetAttribute("href")); } [Fact] public void NavItem_RendersText() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/") .Add(x => x.Text, "Home")); // Assert var text = cut.Find(".sb-nav-item__text"); Assert.NotNull(text); Assert.Contains("Home", text.TextContent); } [Fact] public void NavItem_RendersIconWhenProvided() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/") .Add(x => x.Text, "Home") .Add(x => x.Icon, (RenderFragment)(b => b.AddMarkupContent(0, "ico")))); // Assert var icon = cut.Find(".sb-nav-item__icon"); Assert.NotNull(icon); Assert.Contains("ico", icon.InnerHtml); } [Fact] public void NavItem_RendersBadgeWhenProvided() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/inbox") .Add(x => x.Text, "Inbox") .Add(x => x.Badge, (RenderFragment)(b => b.AddMarkupContent(0, "3")))); // Assert var badge = cut.Find(".sb-nav-item__badge"); Assert.NotNull(badge); Assert.Contains("3", badge.TextContent); } [Fact] public void NavItem_AddsActiveClassWhenCurrentPage() { // Arrange - navigate to /products then render item with Href="/products" var navMan = Services.GetRequiredService(); navMan.NavigateTo("http://localhost/products"); var cut = RenderNavItem(p => p .Add(x => x.Href, "/products") .Add(x => x.Text, "Products")); // Assert var anchor = cut.Find("a.sb-nav-item"); Assert.Contains("sb-nav-item--active", anchor.ClassList); } [Fact] public void NavItem_DoesNotAddActiveClassWhenNotCurrentPage() { // Arrange - default URI is base, render item with Href="/other" var cut = RenderNavItem(p => p .Add(x => x.Href, "/other-page") .Add(x => x.Text, "Other")); // Assert var anchor = cut.Find("a.sb-nav-item"); Assert.DoesNotContain("sb-nav-item--active", anchor.ClassList); } [Fact] public void NavItem_AddsDisabledClassWhenDisabledTrue() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/") .Add(x => x.Text, "Disabled") .Add(x => x.Disabled, true)); // Assert var anchor = cut.Find("a.sb-nav-item"); Assert.Contains("sb-nav-item--disabled", anchor.ClassList); } [Fact] public void NavItem_AppliesClassParameter() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/") .Add(x => x.Text, "Item") .Add(x => x.Class, "custom-item")); // Assert var anchor = cut.Find("a.sb-nav-item"); Assert.Contains("custom-item", anchor.ClassList); } [Fact] public void NavItem_AppliesStyleParameter() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/") .Add(x => x.Text, "Item") .Add(x => x.Style, "font-weight: bold;")); // Assert var anchor = cut.Find("a"); var style = anchor.GetAttribute("style"); Assert.Contains("font-weight: bold", style); } [Fact] public void NavItem_PassesAdditionalAttributes() { // Arrange & Act var cut = RenderNavItem(p => p .Add(x => x.Href, "/") .Add(x => x.Text, "Item") .Add(x => x.AdditionalAttributes, new Dictionary { { "data-testid", "nav-home" } })); // Assert var anchor = cut.Find("a"); Assert.Equal("nav-home", anchor.GetAttribute("data-testid")); } [Fact] public void NavItem_WorksInsideSbNavMenu() { // Arrange & Act var cut = Render(p => p.AddChildContent(b => { b.OpenComponent(0); b.AddAttribute(1, "Href", "/dashboard"); b.AddAttribute(2, "Text", "Dashboard"); b.CloseComponent(); })); // Assert var nav = cut.Find("nav.sb-nav-menu"); var item = cut.Find("a.sb-nav-item"); Assert.NotNull(nav); Assert.NotNull(item); Assert.Equal("/dashboard", item.GetAttribute("href")); Assert.Contains("Dashboard", cut.Markup); } }