513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
152 lines
6.1 KiB
C#
152 lines
6.1 KiB
C#
using Bunit;
|
|
using Microsoft.AspNetCore.Components;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests;
|
|
|
|
/// <summary>
|
|
/// bUnit tests for TreeView's Checkbox-selection mode (T19 of the Transport
|
|
/// feature). Verifies that:
|
|
/// - a checkbox renders next to every node in Checkbox mode,
|
|
/// - clicking a folder checkbox cascades selection to every descendant leaf,
|
|
/// - clicking a leaf produces a partial parent state (Indeterminate),
|
|
/// - Single-mode behaviour is preserved (regression).
|
|
/// </summary>
|
|
public class TreeViewMultiSelectTests : BunitContext
|
|
{
|
|
private record TestNode(string Key, string Label, List<TestNode> Children);
|
|
|
|
private static List<TestNode> TwoFoldersThreeLeaves() => new()
|
|
{
|
|
// Folder1 → Leaf1a, Leaf1b
|
|
new("f1", "Folder1", new()
|
|
{
|
|
new("l1a", "Leaf1a", new()),
|
|
new("l1b", "Leaf1b", new()),
|
|
}),
|
|
// Folder2 → Leaf2a
|
|
new("f2", "Folder2", new()
|
|
{
|
|
new("l2a", "Leaf2a", new()),
|
|
}),
|
|
};
|
|
|
|
private IRenderedComponent<TreeView<TestNode>> RenderCheckboxTree(
|
|
List<TestNode>? items = null,
|
|
HashSet<object>? selectedKeys = null,
|
|
Action<HashSet<object>>? onSelectedKeysChanged = null)
|
|
{
|
|
// Indeterminate is set via JS interop after each render. We stub it so
|
|
// bUnit's strict mode doesn't blow up on the unmocked call.
|
|
JSInterop.SetupVoid("treeviewStorage.setIndeterminate", _ => true).SetVoidResult();
|
|
|
|
return Render<TreeView<TestNode>>(parameters =>
|
|
{
|
|
parameters
|
|
.Add(p => p.Items, items ?? TwoFoldersThreeLeaves())
|
|
.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.SelectionMode, TreeViewSelectionMode.Checkbox)
|
|
.Add(p => p.InitiallyExpanded, _ => true)
|
|
.Add(p => p.SelectedKeys, selectedKeys);
|
|
|
|
if (onSelectedKeysChanged != null)
|
|
{
|
|
parameters.Add(p => p.SelectedKeysChanged, onSelectedKeysChanged);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>Verifies that checkbox mode renders a checkbox input for each node.</summary>
|
|
[Fact]
|
|
public void Checkbox_mode_renders_input_checkbox_per_node()
|
|
{
|
|
var cut = RenderCheckboxTree();
|
|
|
|
// 2 folders + 3 leaves = 5 nodes, expanded → 5 checkboxes.
|
|
var checkboxes = cut.FindAll("input.tv-checkbox");
|
|
Assert.Equal(5, checkboxes.Count);
|
|
}
|
|
|
|
/// <summary>Verifies that clicking a folder checkbox selects all of its descendant leaves.</summary>
|
|
[Fact]
|
|
public void Clicking_folder_checkbox_selects_all_descendant_leaves()
|
|
{
|
|
HashSet<object>? captured = null;
|
|
var cut = RenderCheckboxTree(
|
|
selectedKeys: new HashSet<object>(),
|
|
onSelectedKeysChanged: keys => captured = keys);
|
|
|
|
// First checkbox is Folder1 (the first root). It has leaves l1a + l1b.
|
|
var folderCheckbox = cut.FindAll("input.tv-checkbox")[0];
|
|
folderCheckbox.Change(true);
|
|
|
|
Assert.NotNull(captured);
|
|
Assert.Equal(2, captured!.Count);
|
|
Assert.Contains((object)"l1a", captured);
|
|
Assert.Contains((object)"l1b", captured);
|
|
}
|
|
|
|
/// <summary>Verifies that clicking a leaf makes its parent indeterminate when a sibling is unchecked.</summary>
|
|
[Fact]
|
|
public void Clicking_leaf_makes_parent_indeterminate_when_sibling_unchecked()
|
|
{
|
|
// Pre-select one of Folder1's two leaves → Folder1 should compute as
|
|
// Indeterminate on the next render (carries the `tv-checkbox-indeterminate`
|
|
// CSS marker class — the JS-set `indeterminate` DOM property is set via
|
|
// interop and isn't observable through bUnit's rendered HTML).
|
|
var cut = RenderCheckboxTree(
|
|
selectedKeys: new HashSet<object> { "l1a" });
|
|
|
|
// Folder1 checkbox is the first .tv-checkbox; should carry the partial
|
|
// marker class.
|
|
var folderCheckbox = cut.FindAll("input.tv-checkbox")[0];
|
|
var classAttr = folderCheckbox.GetAttribute("class") ?? string.Empty;
|
|
Assert.Contains("tv-checkbox-indeterminate", classAttr);
|
|
|
|
// …and not the fully-checked attribute.
|
|
Assert.NotEqual("true", folderCheckbox.GetAttribute("checked"));
|
|
}
|
|
|
|
/// <summary>Verifies that single selection mode behaves as before.</summary>
|
|
[Fact]
|
|
public void Single_mode_unchanged()
|
|
{
|
|
// Default SelectionMode is Single. No SelectedKeysChanged should fire,
|
|
// and SelectedKeyChanged (singular) should fire on content click.
|
|
object? selected = null;
|
|
HashSet<object>? bulk = null;
|
|
|
|
var cut = Render<TreeView<TestNode>>(parameters =>
|
|
{
|
|
parameters
|
|
.Add(p => p.Items, TwoFoldersThreeLeaves())
|
|
.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.InitiallyExpanded, _ => true)
|
|
.Add(p => p.Selectable, true)
|
|
.Add(p => p.SelectedKeyChanged, (Action<object?>)(k => selected = k))
|
|
.Add(p => p.SelectedKeysChanged, (Action<HashSet<object>>)(s => bulk = s));
|
|
});
|
|
|
|
// No checkboxes rendered in Single mode.
|
|
Assert.Empty(cut.FindAll("input.tv-checkbox"));
|
|
|
|
// Clicking the content fires SelectedKeyChanged (singular) and NOT
|
|
// SelectedKeysChanged (plural).
|
|
cut.Find(".tv-content").Click();
|
|
Assert.Equal("f1", selected);
|
|
Assert.Null(bulk);
|
|
}
|
|
}
|