Files
sufi-blazor/tests/Components/Navigation/SbTreeViewTests.cs
T
2026-05-18 15:53:59 +03:30

343 lines
11 KiB
C#

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<SufiBlazorResource>
{
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<LocalizedString> GetAllStrings(bool includeParentCultures) => Array.Empty<LocalizedString>();
}
public class SbTreeViewTests : BunitContext
{
private class TreeTestItem
{
public string Name { get; set; } = "";
public string? Icon { get; set; }
public List<TreeTestItem> Children { get; set; } = new();
}
public SbTreeViewTests()
{
Services.AddSingleton<IStringLocalizer<SufiBlazorResource>>(new StubStringLocalizer());
}
private IRenderedComponent<SbTreeView<TreeTestItem>> RenderTreeView(
Action<ComponentParameterCollectionBuilder<SbTreeView<TreeTestItem>>>? configure = null)
{
return Render<SbTreeView<TreeTestItem>>(p => configure?.Invoke(p));
}
private static List<TreeTestItem> 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<TreeTestItem> { 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<TreeTestItem>())
.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<TreeTestItem> { 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<TreeTestItem> { 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<TreeTestItem>(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<TreeTestItem> { 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<TreeTestItem> { 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<TreeTestItem>(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<TreeTestItem> { 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);
}
}