using System.Globalization; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Bunit; using SufiChain.SufiBlazor.Components.Navigation; using SufiChain.SufiBlazor.Localization; using Xunit; namespace SufiChain.SufiBlazor.Tests.Components.Navigation; file class StubStringLocalizer : IStringLocalizer { public LocalizedString this[string name] => new(name, name); public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments)); public IEnumerable GetAllStrings(bool includeParentCultures) => Array.Empty(); } public class SbTreeViewTests : BunitContext { private class TreeTestItem { public string Name { get; set; } = ""; public string? Icon { get; set; } public List Children { get; set; } = new(); } public SbTreeViewTests() { Services.AddSingleton>(new StubStringLocalizer()); } private IRenderedComponent> RenderTreeView( Action>>? configure = null) { return Render>(p => configure?.Invoke(p)); } private static List CreateSimpleTree() => new() { new TreeTestItem { Name = "Root 1" }, new TreeTestItem { Name = "Root 2", Children = new() { new TreeTestItem { Name = "Child 2.1" }, new TreeTestItem { Name = "Child 2.2" } } } }; // --- SbTreeView tests --- [Fact] public void RendersTreeViewStructure() { // Arrange & Act var items = new List { new() { Name = "Item 1" } }; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert var tree = cut.Find(".sb-treeview"); Assert.NotNull(tree); Assert.Equal("tree", tree.GetAttribute("role")); } [Fact] public void RendersEmptyWhenNoItems() { // Arrange & Act var cut = RenderTreeView(p => p .Add(x => x.Items, new List()) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert var tree = cut.Find(".sb-treeview"); Assert.NotNull(tree); Assert.Empty(cut.FindAll(".sb-treeview-node")); } [Fact] public void RendersRootNodes() { // Arrange & Act var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert Assert.Contains("Root 1", cut.Markup); Assert.Contains("Root 2", cut.Markup); var nodes = cut.FindAll(".sb-treeview-node"); Assert.Equal(2, nodes.Count); } [Fact] public void RendersChildrenWhenExpanded() { // Arrange - Root 2 has children, expand via IsExpandedSelector var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.IsExpandedSelector, i => i.Name == "Root 2")); // Assert Assert.Contains("Child 2.1", cut.Markup); Assert.Contains("Child 2.2", cut.Markup); } [Fact] public void DoesNotRenderChildrenWhenCollapsed() { // Arrange - all collapsed (default) var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert - children not visible when collapsed Assert.DoesNotContain("Child 2.1", cut.Markup); Assert.DoesNotContain("Child 2.2", cut.Markup); } [Fact] public void AppliesClassParameter() { // Arrange & Act var items = new List { new() { Name = "Item" } }; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.Class, "my-tree")); // Assert var tree = cut.Find(".sb-treeview"); Assert.Contains("my-tree", tree.ClassList); } [Fact] public void RendersIconWhenIconSelectorProvided() { // Arrange & Act var items = new List { new() { Name = "Folder", Icon = "folder" } }; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.IconSelector, i => i.Icon)); // Assert var icon = cut.Find(".sb-treeview-node__icon"); Assert.NotNull(icon); } [Fact] public void AppliesSelectedClassWhenSelectedItemMatches() { // Arrange var items = CreateSimpleTree(); var selected = items[0]; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.SelectedItem, selected)); // Assert var selectedContent = cut.Find(".sb-treeview-node__content--selected"); Assert.NotNull(selectedContent); Assert.Contains("Root 1", selectedContent.TextContent); } [Fact] public async Task ClickingNodeInvokesSelectedItemChanged() { // Arrange TreeTestItem? received = null; var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.SelectedItemChanged, EventCallback.Factory.Create(this, item => received = item))); // Act - click Root 1 content var content = cut.Find(".sb-treeview-node__content"); await cut.InvokeAsync(() => content.Click()); // Assert Assert.NotNull(received); Assert.Equal("Root 1", received.Name); } [Fact] public void ShowsCheckboxesWhenShowCheckboxesTrue() { // Arrange & Act var items = new List { new() { Name = "Item" } }; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.ShowCheckboxes, true)); // Assert var checkbox = cut.Find("input.sb-treeview-node__checkbox"); Assert.NotNull(checkbox); Assert.Equal("checkbox", checkbox.GetAttribute("type")); } [Fact] public void DoesNotShowCheckboxesByDefault() { // Arrange & Act var items = new List { new() { Name = "Item" } }; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert Assert.Empty(cut.FindAll("input.sb-treeview-node__checkbox")); } [Fact] public async Task ToggleButtonExpandsNode() { // Arrange var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Initially children not visible Assert.DoesNotContain("Child 2.1", cut.Markup); // Act - click toggle on Root 2 var toggle = cut.FindAll("button.sb-treeview-node__toggle").Last(); await cut.InvokeAsync(() => toggle.Click()); cut.Render(); // Assert - children now visible Assert.Contains("Child 2.1", cut.Markup); } [Fact] public async Task ToggleInvokesOnToggleCallback() { // Arrange TreeTestItem? toggled = null; var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.OnToggle, EventCallback.Factory.Create(this, item => toggled = item))); // Act var toggle = cut.FindAll("button.sb-treeview-node__toggle").Last(); await cut.InvokeAsync(() => toggle.Click()); // Assert Assert.NotNull(toggled); Assert.Equal("Root 2", toggled.Name); } [Fact] public void NodeHasCorrectRoleAndLevel() { // Arrange & Act var items = new List { new() { Name = "Item" } }; var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert var node = cut.Find(".sb-treeview-node"); Assert.Equal("treeitem", node.GetAttribute("role")); Assert.Equal("1", node.GetAttribute("aria-level")); } // --- SbTreeViewNode tests (via SbTreeView) --- [Fact] public void Node_LeafHasSpacerInsteadOfToggle() { // Arrange - Root 1 has no children var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert - leaf has spacer var spacer = cut.Find(".sb-treeview-node__spacer"); Assert.NotNull(spacer); } [Fact] public void Node_WithChildrenHasToggleButton() { // Arrange & Act var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children)); // Assert var toggles = cut.FindAll("button.sb-treeview-node__toggle"); Assert.Single(toggles); // Root 2 has children } [Fact] public void Node_ExpandedHasExpandedClass() { // Arrange var items = CreateSimpleTree(); var cut = RenderTreeView(p => p .Add(x => x.Items, items) .Add(x => x.TextSelector, i => i.Name) .Add(x => x.ChildSelector, i => i.Children) .Add(x => x.IsExpandedSelector, i => i.Name == "Root 2")); // Assert var expandedNode = cut.Find(".sb-treeview-node--expanded"); Assert.NotNull(expandedNode); } }