Files
scadalink-design/tests/ScadaLink.CentralUI.Tests/TreeViewTests.cs

242 lines
7.7 KiB
C#

using Bunit;
using Microsoft.AspNetCore.Components;
using ScadaLink.CentralUI.Components.Shared;
namespace ScadaLink.CentralUI.Tests;
/// <summary>
/// bUnit tests for the TreeView component covering core rendering,
/// expand/collapse behavior, ARIA attributes, and indentation.
/// </summary>
public class TreeViewTests : BunitContext
{
private record TestNode(string Key, string Label, List<TestNode> Children);
private static List<TestNode> SimpleRoots() => new()
{
new("a", "Alpha", new()
{
new("a1", "Alpha-1", new()),
new("a2", "Alpha-2", new()
{
new("a2x", "Alpha-2-X", new())
})
}),
new("b", "Beta", new()),
};
private IRenderedComponent<TreeView<TestNode>> RenderTreeView(
List<TestNode>? items = null,
RenderFragment? emptyContent = null,
int indentPx = 24,
Func<TestNode, bool>? initiallyExpanded = null)
{
return Render<TreeView<TestNode>>(parameters => parameters
.Add(p => p.Items, items ?? SimpleRoots())
.Add(p => p.ChildrenSelector, n => n.Children)
.Add(p => p.HasChildrenSelector, n => n.Children.Count > 0)
.Add(p => p.KeySelector, n => n.Key)
.Add(p => p.NodeContent, node => builder =>
{
builder.AddMarkupContent(0, $"<span class=\"node-label\">{node.Label}</span>");
})
.Add(p => p.IndentPx, indentPx)
.Add(p => p.EmptyContent, emptyContent)
.Add(p => p.InitiallyExpanded, initiallyExpanded));
}
[Fact]
public void RendersRootLevelItems_WithCorrectLabels()
{
var cut = RenderTreeView();
var labels = cut.FindAll(".node-label");
// Only root-level items visible (children collapsed)
Assert.Equal(2, labels.Count);
Assert.Equal("Alpha", labels[0].TextContent);
Assert.Equal("Beta", labels[1].TextContent);
}
[Fact]
public void RendersEmptyContent_WhenItemsEmpty()
{
var cut = RenderTreeView(
items: new List<TestNode>(),
emptyContent: builder =>
{
builder.AddMarkupContent(0, "<p class=\"empty-msg\">Nothing here</p>");
});
var msg = cut.Find(".empty-msg");
Assert.Equal("Nothing here", msg.TextContent);
Assert.Throws<Bunit.ElementNotFoundException>(() => cut.Find("ul[role='tree']"));
}
[Fact]
public void LeafNodes_HaveNoToggle()
{
var cut = RenderTreeView();
// Beta is a leaf (index 1 in the li list)
var treeItems = cut.FindAll("li[role='treeitem']");
var betaLi = treeItems[1]; // Beta is second root
Assert.Throws<Bunit.ElementNotFoundException>(() => betaLi.QuerySelector(".tv-toggle")
?? throw new Bunit.ElementNotFoundException(".tv-toggle"));
// Should have spacer instead
Assert.NotNull(betaLi.QuerySelector(".tv-spacer"));
}
[Fact]
public void BranchNodes_ShowCollapsedToggle()
{
var cut = RenderTreeView();
var alphaLi = cut.FindAll("li[role='treeitem']")[0];
Assert.Equal("false", alphaLi.GetAttribute("aria-expanded"));
var toggle = alphaLi.QuerySelector(".tv-toggle");
Assert.NotNull(toggle);
Assert.Equal("+", toggle!.TextContent);
}
[Fact]
public void CollapsedBranch_ChildrenNotInDom()
{
var cut = RenderTreeView();
// Alpha is collapsed by default, children should not be in DOM
var groups = cut.FindAll("ul[role='group']");
Assert.Empty(groups);
}
[Fact]
public void ClickToggle_ExpandsNode_ShowsChildren()
{
var cut = RenderTreeView();
// Click Alpha's toggle
var toggle = cut.Find(".tv-toggle");
toggle.Click();
// Alpha should now be expanded
var alphaLi = cut.FindAll("li[role='treeitem']")[0];
Assert.Equal("true", alphaLi.GetAttribute("aria-expanded"));
// Children should appear
var labels = cut.FindAll(".node-label");
Assert.Contains(labels, l => l.TextContent == "Alpha-1");
Assert.Contains(labels, l => l.TextContent == "Alpha-2");
}
[Fact]
public void ClickExpandedToggle_Collapses_HidesChildren()
{
var cut = RenderTreeView();
// Expand Alpha
var toggle = cut.Find(".tv-toggle");
toggle.Click();
// Verify children visible
Assert.Contains(cut.FindAll(".node-label"), l => l.TextContent == "Alpha-1");
// Collapse Alpha - find the toggle again (DOM changed)
var toggleAgain = cut.Find(".tv-toggle");
toggleAgain.Click();
// Children gone
var labels = cut.FindAll(".node-label");
Assert.DoesNotContain(labels, l => l.TextContent == "Alpha-1");
Assert.Empty(cut.FindAll("ul[role='group']"));
}
[Fact]
public void DeepNesting_ExpandParentThenChild_ShowsGrandchildren()
{
var cut = RenderTreeView();
// Expand Alpha
cut.Find(".tv-toggle").Click();
// Now find Alpha-2's toggle (Alpha-2 is a branch)
var toggles = cut.FindAll(".tv-toggle");
// toggles[0] = Alpha (now expanded, shows minus), toggles[1] = Alpha-2
Assert.True(toggles.Count >= 2);
toggles[1].Click();
// Alpha-2-X should be visible
var labels = cut.FindAll(".node-label");
Assert.Contains(labels, l => l.TextContent == "Alpha-2-X");
}
[Fact]
public void InitiallyExpanded_ExpandsMatchingNodes()
{
var cut = RenderTreeView(initiallyExpanded: n => n.Key == "a" || n.Key == "a2");
// Alpha and Alpha-2 should be expanded, so Alpha-2-X should be visible
var labels = cut.FindAll(".node-label");
Assert.Contains(labels, l => l.TextContent == "Alpha-1");
Assert.Contains(labels, l => l.TextContent == "Alpha-2");
Assert.Contains(labels, l => l.TextContent == "Alpha-2-X");
}
[Fact]
public void RootUl_HasRoleTree()
{
var cut = RenderTreeView();
var rootUl = cut.Find("ul[role='tree']");
Assert.NotNull(rootUl);
}
[Fact]
public void NodeLi_HasRoleTreeitem()
{
var cut = RenderTreeView();
var items = cut.FindAll("li[role='treeitem']");
Assert.Equal(2, items.Count); // Two root nodes
}
[Fact]
public void ExpandedBranch_HasAriaExpandedTrue()
{
var cut = RenderTreeView(initiallyExpanded: n => n.Key == "a");
var alphaLi = cut.FindAll("li[role='treeitem']")[0];
Assert.Equal("true", alphaLi.GetAttribute("aria-expanded"));
}
[Fact]
public void ChildGroup_HasRoleGroup()
{
var cut = RenderTreeView(initiallyExpanded: n => n.Key == "a");
var groups = cut.FindAll("ul[role='group']");
Assert.Single(groups);
}
[Fact]
public void Children_IndentedByIndentPxPerDepth()
{
var cut = RenderTreeView(indentPx: 30, initiallyExpanded: n => n.Key == "a" || n.Key == "a2");
var rows = cut.FindAll(".tv-row");
// Root nodes at depth 0: padding-left: 0px
// Children at depth 1: padding-left: 30px
// Grandchildren at depth 2: padding-left: 60px
// Find Alpha row (depth 0)
var alphaRow = rows[0];
Assert.Contains("padding-left: 0px", alphaRow.GetAttribute("style"));
// Find Alpha-1 row (depth 1)
var alpha1Row = rows[1];
Assert.Contains("padding-left: 30px", alpha1Row.GetAttribute("style"));
// Find Alpha-2-X row (depth 2) - it's after Alpha-2 at index 3
var alpha2xRow = rows[3];
Assert.Contains("padding-left: 60px", alpha2xRow.GetAttribute("style"));
}
}