From ddce0f560ca11579cd538ee9815f32a75f057237 Mon Sep 17 00:00:00 2001 From: Tony996 <6914529@qq.com> Date: Mon, 6 Jul 2026 19:38:06 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E5=BD=93=E6=BF=80?= =?UTF-8?q?=E6=B4=BB=E6=8C=87=E5=AE=9A=E5=AD=90=E8=8A=82=E7=82=B9=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=BA=94=E5=85=A8=E9=83=A8=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 重构展开逻辑,当激活指定子节点时,应全部展开 --- src/BootstrapBlazor/Misc/ExpandableNodeCache.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs b/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs index 1f4d3d4dc9b..181b9b9b294 100644 --- a/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs +++ b/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs @@ -89,12 +89,6 @@ public async Task CheckExpandAsync(TNode node, Func Date: Mon, 6 Jul 2026 22:54:48 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix:更新单元测试,以匹配新单元测试框架 --- test/UnitTest/Dynamic/ChangeDetectionCleanTaskTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UnitTest/Dynamic/ChangeDetectionCleanTaskTest.cs b/test/UnitTest/Dynamic/ChangeDetectionCleanTaskTest.cs index 808122cb7f0..6fe90656030 100644 --- a/test/UnitTest/Dynamic/ChangeDetectionCleanTaskTest.cs +++ b/test/UnitTest/Dynamic/ChangeDetectionCleanTaskTest.cs @@ -119,7 +119,7 @@ public async Task CleanLoop_Ok() var removed = false; for (var i = 0; i < 100 && !removed; i++) { - await Task.Delay(50); + await Task.Delay(50, Xunit.TestContext.Current.CancellationToken); removed = !cache.ContainsKey(dynamicType); } Assert.True(removed); From a0c58b50074cfe40e9fb855cc8c9e713df1a10f0 Mon Sep 17 00:00:00 2001 From: Tony <6914529@qq.com> Date: Tue, 7 Jul 2026 06:59:36 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E7=88=B6=E8=8A=82=E7=82=B9=E5=B1=95?= =?UTF-8?q?=E5=BC=80=E9=80=BB=E8=BE=91=E5=8F=98=E5=8A=A8=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=E6=9C=9F=E6=9C=9B?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor:由于逻辑改变,默认存在子节点时,父节点全部展开,此时数量要将子节点数也要包括在内 --- test/UnitTest/Components/TableTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index cc79e1b65ba..28d7dfaf16a 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -4052,7 +4052,8 @@ public async Task IsTree_KeepCollapsed() Assert.Contains("fa-solid fa-caret-right", cut.Markup); nodes = cut.FindAll("tbody tr"); - Assert.Equal(2, nodes.Count); + // 单元测试默认节点为展开状态,因此这里将期望值改为4 + Assert.Equal(4, nodes.Count); } private static Task> OnQueryAsync(QueryPageOptions _, IStringLocalizer localizer) From 2f4b94a6bbd9d3037c01d48fecf7470ceea7b62e Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 9 Jul 2026 13:56:41 +0800 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/TreeView/TreeView.razor.cs | 20 ++++++++++-- .../Misc/ExpandableNodeCache.cs | 24 ++++++++++++-- test/UnitTest/Components/TableTest.cs | 3 +- test/UnitTest/Components/TreeViewTest.cs | 31 +++++++++++++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs b/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs index 291a1337a35..27c72a17120 100644 --- a/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs +++ b/src/BootstrapBlazor/Components/TreeView/TreeView.razor.cs @@ -427,7 +427,7 @@ protected override async Task OnParametersSetAsync() if (_init == false) { _activeItem ??= Items.FirstOrDefaultActiveItem(); - _activeItem?.SetParentExpand, TItem>(true); + ExpandParentNodes(_activeItem); _init = true; _scrollIntoView = true; } @@ -762,11 +762,27 @@ private Task OnClickResetSearch() public void SetActiveItem(TreeViewItem? item) { _activeItem = item; - _activeItem?.SetParentExpand, TItem>(true); + ExpandParentNodes(_activeItem); + _rows = null; _scrollIntoView = true; StateHasChanged(); } + /// + /// 向上级联展开祖先节点并同步节点缓存状态 防止缓存中的收缩状态覆盖此处的展开状态 + /// Cascade expand ancestor nodes upwards and synchronize the node cache state to prevent the collapsed state in the cache from overriding the expanded state set here + /// + /// + private void ExpandParentNodes(TreeViewItem? item) + { + var parent = item?.Parent; + while (parent is TreeViewItem p) + { + _treeNodeStateCache.ExpandNode(p); + parent = p.Parent; + } + } + /// /// Set the 数据 source method for /// Set the data source method for diff --git a/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs b/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs index 181b9b9b294..1338834842a 100644 --- a/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs +++ b/src/BootstrapBlazor/Misc/ExpandableNodeCache.cs @@ -79,6 +79,18 @@ public virtual async Task ToggleNodeAsync(TNode node, Func + /// 展开指定节点并同步缓存状态 + /// Expand the specified node and synchronize the cache state + /// + /// + public void ExpandNode(IExpandableNode node) + { + node.IsExpand = true; + ExpandedNodeCache.Add(node.Value); + CollapsedNodeCache.Remove(node.Value); + } + /// /// 检查当前节点是否展开 /// Check whether current node is expanded @@ -89,8 +101,16 @@ public async Task CheckExpandAsync(TNode node, Func> OnQueryAsync(QueryPageOptions _, IStringLocalizer localizer) diff --git a/test/UnitTest/Components/TreeViewTest.cs b/test/UnitTest/Components/TreeViewTest.cs index 6547bfe329c..6bae224a38f 100644 --- a/test/UnitTest/Components/TreeViewTest.cs +++ b/test/UnitTest/Components/TreeViewTest.cs @@ -150,6 +150,37 @@ public async Task Items_SetActive() await cut.InvokeAsync(() => cut.Instance.SetActiveItem(activeItem)); } + [Fact] + public async Task SetActiveItem_ExpandCollapsedParent() + { + // https://github.com/dotnetcore/BootstrapBlazor/issues/8185 + // 手动收缩父节点后再设置激活子节点 祖先节点应重新展开 + var items = TreeFoo.GetTreeItems(); + var cut = Context.Render>(pb => + { + pb.Add(a => a.Items, items); + }); + + // 初始激活节点为 Sub Menu Three 祖先节点 Navigation two 处于展开状态 + // 手动收缩 Navigation two 节点 + await cut.InvokeAsync(() => cut.FindAll(".node-icon.visible")[0].Click()); + Assert.DoesNotContain("Sub menu 1", cut.Markup); + + // 设置激活节点为 Navigation two 的子节点 Sub menu 1 祖先节点应重新展开 + await cut.InvokeAsync(() => cut.Instance.SetActiveItem(items[1].Items[0].Value)); + Assert.Contains("Sub menu 1", cut.Markup); + + // 模拟父组件重新渲染 缓存中的收缩状态不应覆盖程序设置的展开状态 + cut.Render(); + var node = cut.Find(".active .tree-node-text"); + Assert.Equal("Sub menu 1", node.TextContent); + + // 再次渲染 确认展开状态稳定 不出现展开收缩循环切换 + cut.Render(); + node = cut.Find(".active .tree-node-text"); + Assert.Equal("Sub menu 1", node.TextContent); + } + [Fact] public void AppendNode_Ok() { From 99f3901420d3974a0996a1a24c6c1f391ea9c478 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 9 Jul 2026 14:37:38 +0800 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=E6=9B=B4=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/TreeViewTest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/UnitTest/Components/TreeViewTest.cs b/test/UnitTest/Components/TreeViewTest.cs index 6bae224a38f..f91ef2524ed 100644 --- a/test/UnitTest/Components/TreeViewTest.cs +++ b/test/UnitTest/Components/TreeViewTest.cs @@ -179,6 +179,11 @@ public async Task SetActiveItem_ExpandCollapsedParent() cut.Render(); node = cut.Find(".active .tree-node-text"); Assert.Equal("Sub menu 1", node.TextContent); + + // 再次手动收缩激活节点的祖先节点后重新渲染 保持用户手动设置的收缩状态 + await cut.InvokeAsync(() => cut.FindAll(".node-icon.visible")[0].Click()); + cut.Render(); + Assert.DoesNotContain("Sub menu 1", cut.Markup); } [Fact]