From 7e5842c2ad28aee436f9c8f28b8cebb649d51ee1 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 23 Apr 2026 15:18:41 +0800 Subject: [PATCH 1/4] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=85=20Checkbox=20?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E5=8D=95=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/TableTest.cs | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index da165529237..e43d4fddea7 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -6474,6 +6474,62 @@ public async Task DynamicContext_EqualityComparer() Assert.True(compared); } + [Fact] + public async Task DynamicContext_HeaderCheckGuid_Ok() + { + var localizer = Context.Services.GetRequiredService>(); + var selectedRows = new List(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.RenderMode, TableRenderMode.Table); + pb.Add(a => a.IsMultipleSelect, true); + pb.Add(a => a.DynamicContext, CreateDynamicContext(localizer)); + pb.Add(a => a.SelectedRows, selectedRows); + pb.Add(a => a.SelectedRowsChanged, EventCallback.Factory.Create>(this, rows => selectedRows = rows)); + }); + }); + + // 无选中行 + Assert.Empty(selectedRows); + + // 点击表头全选,选中行为 2 行 + var header = cut.FindComponents>()[0]; + await cut.InvokeAsync(header.Instance.OnToggleClick); + Assert.Equal(2, selectedRows.Count); + + // 再次点击表头全选,取消全选 + await cut.InvokeAsync(header.Instance.OnToggleClick); + Assert.Empty(selectedRows); + } + + [Fact] + public async Task DynamicContext_HeaderCheckGuid_ShowRowCheckboxCallback_Ok() + { + // 测试包含无法选中行逻辑 + var localizer = Context.Services.GetRequiredService>(); + var selectedRows = new List(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.RenderMode, TableRenderMode.Table); + pb.Add(a => a.IsMultipleSelect, true); + pb.Add(a => a.DynamicContext, CreateDynamicContext(localizer)); + pb.Add(a => a.ShowRowCheckboxCallback, row => Equals(row.GetValue("Id"), 0)); + pb.Add(a => a.SelectedRows, selectedRows); + pb.Add(a => a.SelectedRowsChanged, EventCallback.Factory.Create>(this, rows => selectedRows = rows)); + }); + }); + + Assert.Equal(2, cut.FindComponents>().Count); + + var header = cut.FindComponents>()[0]; + await cut.InvokeAsync(header.Instance.OnToggleClick); + Assert.Single(selectedRows); + } + [Fact] public async Task DynamicContext_Add() { From 45cd9c8bf4c92900f2c8fa2a1445dee74d0ebd25 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 23 Apr 2026 15:21:48 +0800 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BootstrapBlazor/Components/Table/Table.razor | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BootstrapBlazor/Components/Table/Table.razor b/src/BootstrapBlazor/Components/Table/Table.razor index d69fc591142..0dcb3fcb52b 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor +++ b/src/BootstrapBlazor/Components/Table/Table.razor @@ -282,13 +282,15 @@ { if (item is IDynamicObject d) { - } else { - } From 6c80f1281de38580fa20d85128045c0cf281af19 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 23 Apr 2026 15:24:55 +0800 Subject: [PATCH 3/4] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=85=20CardView=20?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=82=B9=E5=87=BB=20Checkbox=20?= =?UTF-8?q?=E9=80=89=E4=B8=AD=E8=A1=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/UnitTest/Components/TableTest.cs | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index e43d4fddea7..ad84cf60730 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -6474,6 +6474,36 @@ public async Task DynamicContext_EqualityComparer() Assert.True(compared); } + [Fact] + public async Task DynamicContext_CardView_CheckGuid_Ok() + { + var localizer = Context.Services.GetRequiredService>(); + var selectedRows = new List(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.RenderMode, TableRenderMode.CardView); + pb.Add(a => a.IsMultipleSelect, true); + pb.Add(a => a.DynamicContext, CreateDynamicContext(localizer)); + pb.Add(a => a.SelectedRows, selectedRows); + pb.Add(a => a.SelectedRowsChanged, EventCallback.Factory.Create>(this, rows => selectedRows = rows)); + }); + }); + + // CardView 模式无表头 + // 共 2 行数据选中第一行数据 + var input = cut.FindComponents>()[0]; + await cut.InvokeAsync(input.Instance.OnToggleClick); + + Assert.Single(selectedRows); + Assert.Equal(0, selectedRows[0].GetValue("Id")); + + // 取消选中 + await cut.InvokeAsync(input.Instance.OnToggleClick); + Assert.Empty(selectedRows); + } + [Fact] public async Task DynamicContext_HeaderCheckGuid_Ok() { From 0ba719398aafee818b8abebe661be1f885f49e58 Mon Sep 17 00:00:00 2001 From: Argo Zhang Date: Thu, 23 Apr 2026 15:35:03 +0800 Subject: [PATCH 4/4] =?UTF-8?q?test:=20=E5=A2=9E=E5=8A=A0=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E7=A8=8B=E5=BA=8F=E9=9B=86=E6=97=A0=E6=B3=95=E9=87=8A?= =?UTF-8?q?=E6=94=BE=E5=8D=95=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/TableTest.cs | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index ad84cf60730..b644c187b4a 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Components.Web.Virtualization; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; +using System.Collections.Concurrent; using System.ComponentModel.DataAnnotations; using System.Data; using System.Reflection; @@ -6560,6 +6561,40 @@ public async Task DynamicContext_HeaderCheckGuid_ShowRowCheckboxCallback_Ok() Assert.Single(selectedRows); } + + [Fact] + public async Task DynamicContext_ChangeDetection_Ok() + { + var localizer = Context.Services.GetRequiredService>(); + var cut = Context.Render(pb => + { + pb.AddChildContent>(pb => + { + pb.Add(a => a.RenderMode, TableRenderMode.Table); + pb.Add(a => a.IsMultipleSelect, true); + pb.Add(a => a.DynamicContext, CreateDynamicContext(localizer)); + }); + }); + + cut.Dispose(); + + // 表格使用动态创建类型后,不能被 Blazor 底层 ChangeDetection 缓存,否则生成的动态 Assembly 无法被释放 + // 通过反射查看是否被缓存 + var type = typeof(ComponentBase).Assembly.GetType("Microsoft.AspNetCore.Components.ChangeDetection"); + Assert.NotNull(type); + + var fieldInfo = type.GetField("_immutableObjectTypesCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + Assert.NotNull(fieldInfo); + + IEnumerable? items = null; + if (fieldInfo.GetValue(null) is ConcurrentDictionary cache) + { + items = cache.Keys.Where(i => i.Assembly.GetName().Name == "BootstrapBlazor_DynamicAssembly"); + } + Assert.NotNull(items); + Assert.Empty(items); + } + [Fact] public async Task DynamicContext_Add() {