first commit
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user