mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2024-11-29 18:49:08 +08:00
perf(TreeView): improve cache for TreeViewNode (#4677)
Some checks are pending
Build project / build (push) Waiting to run
Some checks are pending
Build project / build (push) Waiting to run
* 优化TreeView组件 虚拟滚动性能,更换List为HashSet,降低时间复杂度 * refactor: 重构代码 * refactor: 使用主构造函数精简代码 * refactor: 改用私有变量反射 * refactor: 增加 HashSetComparer 比较器 * refactor: 重构节点缓存类 * refactor: TreeView 组件更新比较器 * refactor: Table 组件更新比较器 * refactor: 改造缓存类 * test: 更新单元测试 * test: 补充 HashSetComparer 单元测试 --------- Co-authored-by: Argo Zhang <argo@live.ca>
This commit is contained in:
parent
313ccb7e99
commit
c66f7dc283
@ -761,7 +761,7 @@ public partial class Table<TItem> : ITable, IModelEqualityComparer<TItem> where
|
||||
base.OnInitialized();
|
||||
|
||||
// 初始化节点缓存
|
||||
TreeNodeCache ??= new(Equals);
|
||||
TreeNodeCache ??= new(this);
|
||||
OnInitLocalization();
|
||||
|
||||
// 设置 OnSort 回调方法
|
||||
|
@ -338,7 +338,7 @@ public partial class TreeView<TItem> : IModelEqualityComparer<TItem>
|
||||
base.OnInitialized();
|
||||
|
||||
// 初始化节点缓存
|
||||
TreeNodeStateCache ??= new(Equals);
|
||||
TreeNodeStateCache ??= new(this);
|
||||
NotSetOnTreeExpandErrorMessage = Localizer[nameof(NotSetOnTreeExpandErrorMessage)];
|
||||
}
|
||||
|
||||
|
@ -10,26 +10,32 @@ namespace BootstrapBlazor.Components;
|
||||
/// </summary>
|
||||
/// <typeparam name="TNode"></typeparam>
|
||||
/// <typeparam name="TItem"></typeparam>
|
||||
/// <remarks>
|
||||
/// 构造函数
|
||||
/// </remarks>
|
||||
public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) where TNode : IExpandableNode<TItem>
|
||||
public class ExpandableNodeCache<TNode, TItem> where TNode : IExpandableNode<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 所有已展开行集合 作为缓存使用
|
||||
/// </summary>
|
||||
protected List<TItem> ExpandedNodeCache { get; } = new(50);
|
||||
protected HashSet<TItem> ExpandedNodeCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 所有已收缩行集合 作为缓存使用
|
||||
/// </summary>
|
||||
protected List<TItem> CollapsedNodeCache { get; } = new(50);
|
||||
protected HashSet<TItem> CollapsedNodeCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 对象比较器
|
||||
/// </summary>
|
||||
protected IEqualityComparer<TItem> EqualityComparer { get; } = new ModelComparer<TItem>(comparer);
|
||||
protected IEqualityComparer<TItem> EqualityComparer { get; }
|
||||
|
||||
/// <remarks>
|
||||
/// 构造函数
|
||||
/// </remarks>
|
||||
public ExpandableNodeCache(IModelEqualityComparer<TItem> comparer)
|
||||
{
|
||||
EqualityComparer = new ModelHashSetComparer<TItem>(comparer);
|
||||
ExpandedNodeCache = new(50, EqualityComparer);
|
||||
CollapsedNodeCache = new(50, EqualityComparer);
|
||||
}
|
||||
/// <summary>
|
||||
/// 节点展开收缩状态切换方法
|
||||
/// </summary>
|
||||
@ -42,13 +48,10 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
if (node.IsExpand)
|
||||
{
|
||||
// 展开节点缓存增加此节点
|
||||
if (!ExpandedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
{
|
||||
ExpandedNodeCache.Add(node.Value);
|
||||
}
|
||||
ExpandedNodeCache.Add(node.Value);
|
||||
|
||||
// 收缩节点缓存移除此节点
|
||||
CollapsedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
CollapsedNodeCache.Remove(node.Value);
|
||||
|
||||
// 无子项时通过回调方法延时加载
|
||||
if (!node.Items.Any())
|
||||
@ -64,13 +67,10 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
else
|
||||
{
|
||||
// 展开节点缓存移除此节点
|
||||
ExpandedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
ExpandedNodeCache.Remove(node.Value);
|
||||
|
||||
// 收缩节点缓存添加此节点
|
||||
if (!CollapsedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
{
|
||||
CollapsedNodeCache.Add(node.Value);
|
||||
}
|
||||
CollapsedNodeCache.Add(node.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,20 +85,18 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
if (node.IsExpand)
|
||||
{
|
||||
// 已收缩
|
||||
if (CollapsedNodeCache.Contains(node.Value, EqualityComparer))
|
||||
if (CollapsedNodeCache.Contains(node.Value))
|
||||
{
|
||||
node.IsExpand = false;
|
||||
}
|
||||
else if (!ExpandedNodeCache.Contains(node.Value, EqualityComparer))
|
||||
{
|
||||
// 状态为 展开
|
||||
ExpandedNodeCache.Add(node.Value);
|
||||
}
|
||||
|
||||
// 状态为 展开
|
||||
ExpandedNodeCache.Add(node.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var needRemove = true;
|
||||
if (ExpandedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
if (ExpandedNodeCache.Contains(node.Value))
|
||||
{
|
||||
// 原来是展开状态,
|
||||
if (node.HasChildren)
|
||||
@ -119,7 +117,7 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
}
|
||||
if (needRemove)
|
||||
{
|
||||
ExpandedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
ExpandedNodeCache.Remove(node.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,7 +130,7 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
/// <param name="ret">查询结果 查无资料时为 null</param>
|
||||
/// <returns>是否存在 <paramref name="target"/></returns>
|
||||
/// <remarks>采广度优先搜寻</remarks>
|
||||
public bool TryFind(IEnumerable<TNode> items, TItem target, [MaybeNullWhen(false)] out TNode ret)
|
||||
public bool TryFind(List<TNode> items, TItem target, [MaybeNullWhen(false)] out TNode ret)
|
||||
{
|
||||
ret = Find(items, target);
|
||||
return ret != null;
|
||||
@ -145,7 +143,7 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
/// <param name="target"></param>
|
||||
/// <returns>查询结果 查无资料时为 null</returns>
|
||||
/// <remarks>采广度优先搜寻</remarks>
|
||||
private TNode? Find(IEnumerable<TNode> items, TItem target) => Find(items, target, out _);
|
||||
private TNode? Find(List<TNode> items, TItem target) => Find(items, target, out _);
|
||||
|
||||
/// <summary>
|
||||
/// 在全部树状结构 <paramref name="source"/> 中寻找指定 <paramref name="target"/>
|
||||
@ -155,14 +153,14 @@ public class ExpandableNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer
|
||||
/// <param name="degree">树状阶层,起始为0</param>
|
||||
/// <returns>查询结果 查无资料时为 null</returns>
|
||||
/// <remarks>采广度优先搜寻</remarks>
|
||||
public TNode? Find(IEnumerable<TNode> source, TItem target, out int degree)
|
||||
public TNode? Find(List<TNode> source, TItem target, out int degree)
|
||||
{
|
||||
degree = -1;
|
||||
var ret = source.FirstOrDefault(item => EqualityComparer.Equals(item.Value, target));
|
||||
if (ret == null)
|
||||
{
|
||||
var children = source.SelectMany(e => e.Items.OfType<TNode>());
|
||||
if (children.Any())
|
||||
var children = source.SelectMany(e => e.Items.OfType<TNode>()).ToList();
|
||||
if (children.Count != 0)
|
||||
{
|
||||
ret = Find(children, target, out degree);
|
||||
}
|
||||
|
@ -1,52 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 模型比较器
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem"></typeparam>
|
||||
public class ModelComparer<TItem> : IEqualityComparer<TItem>
|
||||
{
|
||||
private readonly Func<TItem, TItem, bool> _comparer;
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public ModelComparer(Func<TItem, TItem, bool> comparer)
|
||||
{
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equals 方法
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public bool Equals(TItem? x, TItem? y)
|
||||
{
|
||||
bool ret;
|
||||
if (x != null && y != null)
|
||||
{
|
||||
// 均不为空时走 comparer 方法判断
|
||||
ret = _comparer(x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 有一个为空时 判断是否均为空
|
||||
// 均为空时为 true 否则 false
|
||||
ret = x == null && y == null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetHashCode 方法
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public int GetHashCode([DisallowNull] TItem obj) => obj.GetHashCode();
|
||||
}
|
32
src/BootstrapBlazor/Misc/ModelHashSetComparer.cs
Normal file
32
src/BootstrapBlazor/Misc/ModelHashSetComparer.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 模型比较器
|
||||
/// </summary>
|
||||
/// <typeparam name="TItem"></typeparam>
|
||||
public class ModelHashSetComparer<TItem>(IModelEqualityComparer<TItem> comparer) : IEqualityComparer<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Equals 方法
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
/// <returns></returns>
|
||||
public bool Equals(TItem? x, TItem? y) => comparer.Equals(x, y);
|
||||
|
||||
/// <summary>
|
||||
/// GetHashCode 方法
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public int GetHashCode([DisallowNull] TItem obj)
|
||||
{
|
||||
var keyValue = Utility.GetKeyValue<TItem, object>(obj, comparer.CustomKeyAttribute);
|
||||
return keyValue?.GetHashCode() ?? obj.GetHashCode();
|
||||
}
|
||||
}
|
@ -10,65 +10,61 @@ namespace BootstrapBlazor.Components;
|
||||
/// </summary>
|
||||
/// <typeparam name="TNode"></typeparam>
|
||||
/// <typeparam name="TItem"></typeparam>
|
||||
public class TreeNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) : ExpandableNodeCache<TNode, TItem>(comparer) where TNode : ICheckableNode<TItem>
|
||||
public class TreeNodeCache<TNode, TItem> : ExpandableNodeCache<TNode, TItem> where TNode : ICheckableNode<TItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获得 所有选中节点集合 作为缓存使用
|
||||
/// </summary>
|
||||
protected List<TItem> CheckedNodeCache { get; } = new(50);
|
||||
private readonly HashSet<TItem> _checkedNodeCache;
|
||||
|
||||
/// <summary>
|
||||
/// 获得 所有未选中节点集合 作为缓存使用
|
||||
/// </summary>
|
||||
protected List<TItem> UncheckedNodeCache { get; } = new(50);
|
||||
private readonly HashSet<TItem> _uncheckedNodeCache;
|
||||
|
||||
/// <summary>
|
||||
/// 获得 所有未选中节点集合 作为缓存使用
|
||||
/// </summary>
|
||||
protected List<TItem> IndeterminateNodeCache { get; } = new(50);
|
||||
private readonly HashSet<TItem> _indeterminateNodeCache;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="comparer"></param>
|
||||
public TreeNodeCache(IModelEqualityComparer<TItem> comparer) : base(comparer)
|
||||
{
|
||||
_checkedNodeCache = new(50, EqualityComparer);
|
||||
_uncheckedNodeCache = new(50, EqualityComparer);
|
||||
_indeterminateNodeCache = new(50, EqualityComparer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换选中状态方法
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <returns></returns>
|
||||
public virtual void ToggleCheck(TNode node)
|
||||
public void ToggleCheck(TNode node)
|
||||
{
|
||||
if (node.CheckedState == CheckboxState.Checked)
|
||||
{
|
||||
// 未选中节点缓存移除此节点
|
||||
UncheckedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
IndeterminateNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
_uncheckedNodeCache.Remove(node.Value);
|
||||
_indeterminateNodeCache.Remove(node.Value);
|
||||
|
||||
// 选中节点缓存添加此节点
|
||||
if (!CheckedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
{
|
||||
CheckedNodeCache.Add(node.Value);
|
||||
}
|
||||
_checkedNodeCache.Add(node.Value);
|
||||
}
|
||||
else if (node.CheckedState == CheckboxState.UnChecked)
|
||||
{
|
||||
// 选中节点缓存添加此节点
|
||||
CheckedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
IndeterminateNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
_checkedNodeCache.Remove(node.Value);
|
||||
_indeterminateNodeCache.Remove(node.Value);
|
||||
|
||||
// 未选中节点缓存移除此节点
|
||||
if (!UncheckedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
{
|
||||
UncheckedNodeCache.Add(node.Value);
|
||||
}
|
||||
_uncheckedNodeCache.Add(node.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 不确定节点缓存添加此节点
|
||||
CheckedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
UncheckedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
_checkedNodeCache.Remove(node.Value);
|
||||
_uncheckedNodeCache.Remove(node.Value);
|
||||
|
||||
// 未选中节点缓存移除此节点
|
||||
if (!IndeterminateNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
{
|
||||
IndeterminateNodeCache.Add(node.Value);
|
||||
}
|
||||
_indeterminateNodeCache.Add(node.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,46 +75,46 @@ public class TreeNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) : Ex
|
||||
/// <returns></returns>
|
||||
private void IsChecked(TNode node)
|
||||
{
|
||||
// 当前节点状态为未确定状态
|
||||
var nodes = node.Items.OfType<ICheckableNode<TItem>>();
|
||||
if (CheckedNodeCache.Any(i => EqualityComparer.Equals(i, node.Value)))
|
||||
var nodes = node.Items.OfType<ICheckableNode<TItem>>().ToList();
|
||||
if (_checkedNodeCache.Contains(node.Value))
|
||||
{
|
||||
node.CheckedState = CheckboxState.Checked;
|
||||
}
|
||||
else if (UncheckedNodeCache.Contains(node.Value, EqualityComparer))
|
||||
else if (_uncheckedNodeCache.Contains(node.Value))
|
||||
{
|
||||
node.CheckedState = CheckboxState.UnChecked;
|
||||
}
|
||||
else if (IndeterminateNodeCache.Contains(node.Value, EqualityComparer))
|
||||
else if (_indeterminateNodeCache.Contains(node.Value))
|
||||
{
|
||||
node.CheckedState = CheckboxState.Indeterminate;
|
||||
}
|
||||
CheckChildren(nodes);
|
||||
|
||||
void CheckChildren(IEnumerable<ICheckableNode<TItem>> nodes)
|
||||
CheckChildren(nodes, node);
|
||||
}
|
||||
|
||||
private void CheckChildren(List<ICheckableNode<TItem>> nodes, TNode node)
|
||||
{
|
||||
if (nodes.Count != 0)
|
||||
{
|
||||
if (nodes.Any())
|
||||
{
|
||||
CheckedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
UncheckedNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
IndeterminateNodeCache.RemoveAll(i => EqualityComparer.Equals(i, node.Value));
|
||||
_checkedNodeCache.Remove(node.Value);
|
||||
_uncheckedNodeCache.Remove(node.Value);
|
||||
_indeterminateNodeCache.Remove(node.Value);
|
||||
|
||||
// 查看子节点状态
|
||||
if (nodes.All(i => i.CheckedState == CheckboxState.Checked))
|
||||
{
|
||||
node.CheckedState = CheckboxState.Checked;
|
||||
CheckedNodeCache.Add(node.Value);
|
||||
}
|
||||
else if (nodes.All(i => i.CheckedState == CheckboxState.UnChecked))
|
||||
{
|
||||
node.CheckedState = CheckboxState.UnChecked;
|
||||
UncheckedNodeCache.Add(node.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.CheckedState = CheckboxState.Indeterminate;
|
||||
IndeterminateNodeCache.Add(node.Value);
|
||||
}
|
||||
// 查看子节点状态
|
||||
if (nodes.All(i => i.CheckedState == CheckboxState.Checked))
|
||||
{
|
||||
node.CheckedState = CheckboxState.Checked;
|
||||
_checkedNodeCache.Add(node.Value);
|
||||
}
|
||||
else if (nodes.All(i => i.CheckedState == CheckboxState.UnChecked))
|
||||
{
|
||||
node.CheckedState = CheckboxState.UnChecked;
|
||||
_uncheckedNodeCache.Add(node.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
node.CheckedState = CheckboxState.Indeterminate;
|
||||
_indeterminateNodeCache.Add(node.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,27 +123,27 @@ public class TreeNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) : Ex
|
||||
/// 重置是否选中状态
|
||||
/// </summary>
|
||||
/// <param name="nodes"></param>
|
||||
public void IsChecked(IEnumerable<TNode> nodes)
|
||||
public void IsChecked(List<TNode> nodes)
|
||||
{
|
||||
if (nodes.Any())
|
||||
if (nodes.Count != 0)
|
||||
{
|
||||
ResetCheckNodes(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetCheckNodes(IEnumerable<TNode> items)
|
||||
private void ResetCheckNodes(List<TNode> items)
|
||||
{
|
||||
// 恢复当前节点状态
|
||||
foreach (var node in items)
|
||||
{
|
||||
// 恢复当前节点状态
|
||||
foreach (var node in items)
|
||||
// 恢复子节点
|
||||
if (node.Items.Any())
|
||||
{
|
||||
// 恢复子节点
|
||||
if (node.Items.Any())
|
||||
{
|
||||
IsChecked(node.Items.OfType<TNode>());
|
||||
}
|
||||
|
||||
// 设置本节点
|
||||
IsChecked(node);
|
||||
IsChecked(node.Items.OfType<TNode>().ToList());
|
||||
}
|
||||
|
||||
// 设置本节点
|
||||
IsChecked(node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,18 +153,18 @@ public class TreeNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) : Ex
|
||||
/// <param name="nodes">数据集合</param>
|
||||
/// <param name="node">指定节点</param>
|
||||
/// <returns></returns>
|
||||
public TNode? FindParentNode(IEnumerable<TNode> nodes, TNode node)
|
||||
public TNode? FindParentNode(List<TNode> nodes, TNode node)
|
||||
{
|
||||
TNode? ret = default;
|
||||
foreach (var treeNode in nodes)
|
||||
{
|
||||
var subNodes = treeNode.Items.OfType<TNode>();
|
||||
var subNodes = treeNode.Items.OfType<TNode>().ToList();
|
||||
if (subNodes.Any(i => EqualityComparer.Equals(i.Value, node.Value)))
|
||||
{
|
||||
ret = treeNode;
|
||||
break;
|
||||
}
|
||||
if (ret == null && subNodes.Any())
|
||||
if (ret == null && subNodes.Count != 0)
|
||||
{
|
||||
ret = FindParentNode(subNodes, node);
|
||||
}
|
||||
@ -181,9 +177,10 @@ public class TreeNodeCache<TNode, TItem>(Func<TItem, TItem, bool> comparer) : Ex
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
UncheckedNodeCache.Clear();
|
||||
CheckedNodeCache.Clear();
|
||||
IndeterminateNodeCache.Clear();
|
||||
_uncheckedNodeCache.Clear();
|
||||
_checkedNodeCache.Clear();
|
||||
_indeterminateNodeCache.Clear();
|
||||
|
||||
ExpandedNodeCache.Clear();
|
||||
CollapsedNodeCache.Clear();
|
||||
}
|
||||
|
@ -609,6 +609,9 @@ public class TreeViewTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void CascadeSetCheck_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<TreeView<TreeFoo>>();
|
||||
var comparer = cut.Instance;
|
||||
|
||||
var items = new List<TreeFoo>()
|
||||
{
|
||||
new() { Text = "Test1", Id = "01" },
|
||||
@ -619,7 +622,7 @@ public class TreeViewTest : BootstrapBlazorTestBase
|
||||
var node = TreeFoo.CascadingTree(items).First();
|
||||
|
||||
// 设置当前几点所有子项选中状态
|
||||
var cache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(Comparer);
|
||||
var cache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(comparer);
|
||||
node.CheckedState = CheckboxState.Checked;
|
||||
node.SetChildrenCheck(cache);
|
||||
Assert.True(node.GetAllTreeSubItems().All(i => i.CheckedState == CheckboxState.Checked));
|
||||
@ -628,6 +631,9 @@ public class TreeViewTest : BootstrapBlazorTestBase
|
||||
[Fact]
|
||||
public void SetParentCheck_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<TreeView<TreeFoo>>();
|
||||
var comparer = cut.Instance;
|
||||
|
||||
var items = new List<TreeFoo>()
|
||||
{
|
||||
new() { Text = "Test1", Id = "01" },
|
||||
@ -638,7 +644,7 @@ public class TreeViewTest : BootstrapBlazorTestBase
|
||||
Assert.Equal("Test3", node.Value.Text);
|
||||
|
||||
// 设置当前节点所有父项选中状态
|
||||
var cache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(Comparer);
|
||||
var cache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(comparer);
|
||||
node.CheckedState = CheckboxState.Checked;
|
||||
node.SetParentCheck(cache);
|
||||
|
||||
|
@ -11,7 +11,7 @@ public class ModelEqualityComparerTest
|
||||
public void Equals_Ok()
|
||||
{
|
||||
Assert.True(IModelEqualityComparerExtensions.Equals<Foo>(null!, null, null));
|
||||
Assert.False(IModelEqualityComparerExtensions.Equals<Foo>(null!, null, new Foo()));
|
||||
Assert.False(IModelEqualityComparerExtensions.Equals<Foo>(null!, new Foo(), null));
|
||||
Assert.False(IModelEqualityComparerExtensions.Equals(null!, null, new Foo()));
|
||||
Assert.False(IModelEqualityComparerExtensions.Equals(null!, new Foo(), null));
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,16 @@
|
||||
|
||||
namespace UnitTest.Misc;
|
||||
|
||||
public class TreeNodeCacheTest
|
||||
public class TreeNodeCacheTest : BootstrapBlazorTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void ToggleCheck_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<TreeView<TreeFoo>>();
|
||||
var comparer = cut.Instance;
|
||||
|
||||
var items = TreeFoo.GetTreeItems();
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(Comparer);
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(comparer);
|
||||
nodeCache.IsChecked(items);
|
||||
|
||||
// 设置 1010 节点为选中状态
|
||||
@ -204,8 +207,11 @@ public class TreeNodeCacheTest
|
||||
[Fact]
|
||||
public void FindParentNode_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<TreeView<TreeFoo>>();
|
||||
var comparer = cut.Instance;
|
||||
|
||||
var items = TreeFoo.GetTreeItems();
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(Comparer);
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(comparer);
|
||||
var targetId = nodeCache.FindParentNode(items, new TreeViewItem<TreeFoo>(new TreeFoo() { Id = "1110" }))?.Value?.Id;
|
||||
Assert.Equal("1080", targetId);
|
||||
}
|
||||
@ -213,8 +219,11 @@ public class TreeNodeCacheTest
|
||||
[Fact]
|
||||
public void SetChildrenCheck_Ok()
|
||||
{
|
||||
var cut = Context.RenderComponent<TreeView<TreeFoo>>();
|
||||
var comparer = cut.Instance;
|
||||
|
||||
var items = TreeFoo.GetTreeItems();
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(Comparer);
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(comparer);
|
||||
var count = GetUncheckItemCount(nodeCache);
|
||||
Assert.Equal(0, count);
|
||||
|
||||
@ -231,7 +240,10 @@ public class TreeNodeCacheTest
|
||||
[Fact]
|
||||
public void Reset_Ok()
|
||||
{
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(Comparer);
|
||||
var cut = Context.RenderComponent<TreeView<TreeFoo>>();
|
||||
var comparer = cut.Instance;
|
||||
|
||||
var nodeCache = new TreeNodeCache<TreeViewItem<TreeFoo>, TreeFoo>(comparer);
|
||||
|
||||
// 设置 1070 节点为选中状态
|
||||
var node = new TreeViewItem<TreeFoo>(new TreeFoo()
|
||||
@ -292,8 +304,8 @@ public class TreeNodeCacheTest
|
||||
{
|
||||
var count = 0;
|
||||
var type = treeNodeCache.GetType();
|
||||
var pi = type.GetProperty("CheckedNodeCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (pi != null && pi.GetValue(treeNodeCache) is List<TreeFoo> data)
|
||||
var pi = type.GetField("_checkedNodeCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (pi != null && pi.GetValue(treeNodeCache) is HashSet<TreeFoo> data)
|
||||
{
|
||||
count = data.Count;
|
||||
}
|
||||
@ -304,8 +316,8 @@ public class TreeNodeCacheTest
|
||||
{
|
||||
var count = 0;
|
||||
var type = treeNodeCache.GetType();
|
||||
var pi = type.GetProperty("UncheckedNodeCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (pi != null && pi.GetValue(treeNodeCache) is List<TreeFoo> data)
|
||||
var pi = type.GetField("_uncheckedNodeCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (pi != null && pi.GetValue(treeNodeCache) is HashSet<TreeFoo> data)
|
||||
{
|
||||
count = data.Count;
|
||||
}
|
||||
@ -316,8 +328,8 @@ public class TreeNodeCacheTest
|
||||
{
|
||||
var count = 0;
|
||||
var type = treeNodeCache.GetType();
|
||||
var pi = type.GetProperty("IndeterminateNodeCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (pi != null && pi.GetValue(treeNodeCache) is List<TreeFoo> data)
|
||||
var pi = type.GetField("_indeterminateNodeCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (pi != null && pi.GetValue(treeNodeCache) is HashSet<TreeFoo> data)
|
||||
{
|
||||
count = data.Count;
|
||||
}
|
||||
|
93
test/UnitTest/Utils/HashSetComparerTest.cs
Normal file
93
test/UnitTest/Utils/HashSetComparerTest.cs
Normal file
@ -0,0 +1,93 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace UnitTest.Utils;
|
||||
|
||||
public class HasSetComparerTest : BootstrapBlazorTestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GetHashCode_NoKey_Ok()
|
||||
{
|
||||
var comparer = new ModelHashSetComparer<Dummy>(new MockModelEqualityComparer<Dummy>() { });
|
||||
|
||||
var obj = new Dummy() { Id = 1 };
|
||||
|
||||
// 未提供 KeyAttribute 标签内部等同 obj.GetHashCode()
|
||||
Assert.Equal(obj.GetHashCode(), comparer.GetHashCode(obj));
|
||||
|
||||
// 空值相等
|
||||
Assert.True(comparer.Equals(null, null));
|
||||
|
||||
// 未提供 ModelEqualityComparer 两个对象不相等
|
||||
Assert.False(comparer.Equals(new Dummy() { Id = 1 }, new Dummy() { Id = 1 }));
|
||||
|
||||
// 空与非空比较
|
||||
Assert.False(comparer.Equals(new Dummy(), null));
|
||||
Assert.False(comparer.Equals(null, new Dummy()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_Key_Ok()
|
||||
{
|
||||
var comparer = new ModelHashSetComparer<Dog>(new MockModelEqualityComparer<Dog>() { });
|
||||
|
||||
var obj = new Dog() { Id = 1 };
|
||||
|
||||
// 提供 KeyAttribute 标签内部不等同 obj.GetHashCode()
|
||||
Assert.NotEqual(1, obj.GetHashCode());
|
||||
Assert.Equal(1, comparer.GetHashCode(obj));
|
||||
Assert.True(comparer.Equals(new Dog() { Id = 1 }, new Dog() { Id = 1 }));
|
||||
|
||||
// 空值相等
|
||||
Assert.True(comparer.Equals(null, null));
|
||||
|
||||
// 空与非空比较
|
||||
Assert.False(comparer.Equals(new Dog(), null));
|
||||
Assert.False(comparer.Equals(null, new Dog()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_Comparer_Ok()
|
||||
{
|
||||
var comparer = new ModelHashSetComparer<Dummy>(new MockModelEqualityComparer<Dummy>() { ModelEqualityComparer = (x, y) => x.Id == y.Id });
|
||||
|
||||
var obj = new Dummy() { Id = 1 };
|
||||
|
||||
// 未提供 KeyAttribute 标签内部等同 obj.GetHashCode()
|
||||
Assert.Equal(obj.GetHashCode(), comparer.GetHashCode(obj));
|
||||
|
||||
// 空值相等
|
||||
Assert.True(comparer.Equals(null, null));
|
||||
|
||||
// 提供 ModelEqualityComparer 两个对象相等
|
||||
Assert.True(comparer.Equals(new Dummy() { Id = 1 }, new Dummy() { Id = 1 }));
|
||||
|
||||
// 空与非空比较
|
||||
Assert.False(comparer.Equals(new Dummy(), null));
|
||||
Assert.False(comparer.Equals(null, new Dummy()));
|
||||
}
|
||||
|
||||
class Dummy
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
class Dog
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
class MockModelEqualityComparer<TModel> : IModelEqualityComparer<TModel>
|
||||
{
|
||||
public Func<TModel, TModel, bool>? ModelEqualityComparer { get; set; }
|
||||
|
||||
public Type CustomKeyAttribute { get; set; } = typeof(KeyAttribute);
|
||||
|
||||
public bool Equals(TModel? x, TModel? y) => this.Equals<TModel>(x, y);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
namespace UnitTest.Utils;
|
||||
|
||||
public class TItemComparerTest
|
||||
{
|
||||
[Fact]
|
||||
public void GetHashCode_Ok()
|
||||
{
|
||||
var comparer = new ModelComparer<Dummy>((x, y) => x.Id == y.Id);
|
||||
|
||||
var obj = new Dummy();
|
||||
Assert.Equal(obj.GetHashCode(), comparer!.GetHashCode(obj));
|
||||
Assert.True(comparer.Equals(null, null));
|
||||
Assert.True(comparer.Equals(new Dummy() { Id = 1 }, new Dummy() { Id = 1 }));
|
||||
|
||||
Assert.False(comparer.Equals(new Dummy(), null));
|
||||
Assert.False(comparer.Equals(null, new Dummy()));
|
||||
}
|
||||
|
||||
class Dummy
|
||||
{
|
||||
public int Id { get; set; }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user