242 lines
7.7 KiB
C#
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"));
|
|
}
|
|
}
|