using FluentAssertions; using Xunit; using Workflow.Application.Form.ComponentRegistry.Handlers; using Workflow.Application.Form.ComponentRegistry.Queries; using Workflow.Domain.Entities; namespace BuildFlow.Workflow.Tests.Form; /// /// manifest handler 单元测试:验证后端元数据驱动的物料清单查询逻辑 /// (分组、排序、IsActive 过滤、轻量投影不含 SupportedProps)。 /// public class GetFormComponentManifestQueryHandlerTests { private static WorkflowDbContext CreateDb(params FormComponentRegistry[] seeds) { var db = TestDbFactory.Create(); if (seeds.Length > 0) { db.FormComponentRegistries.AddRange(seeds); db.SaveChanges(); } return db; } private static FormComponentRegistry Seed( string name, string category, int sortOrder, bool active = true, string supportedProps = "{\"basic\":[]}") => new() { Id = Guid.NewGuid(), Name = name, DisplayName = name + "显示名", Category = category, Icon = "lucide:test", DefaultSchema = "{}", SupportedProps = supportedProps, IsActive = active, SortOrder = sortOrder, }; [Fact] public async Task Returns_Groups_By_Category_Sorted_By_SortOrder() { // 跨分类、乱序插入,验证分组聚合 + 组内/组间排序 var db = CreateDb( Seed("Select", "选择控件", 2), Seed("Input", "基础输入", 1), Seed("RadioGroup", "选择控件", 1), Seed("Textarea", "基础输入", 2) ); var handler = new GetFormComponentManifestQueryHandler(db); var result = await handler.Handle(new GetFormComponentManifestQuery(), default); // 分组顺序:基础输入(sortOrder min=1) 在 选择控件(min=1) 之前?同 min 时按出现稳定顺序 // 基础输入组内最小 sortOrder=1,选择控件组内最小=1,两者相等,稳定排序保持插入顺序 result.Groups.Should().HaveCount(2); var basic = result.Groups.First(g => g.Title == "基础输入"); basic.Items.Select(i => i.Name).Should().Equal(["Input", "Textarea"]); var select = result.Groups.First(g => g.Title == "选择控件"); select.Items.Select(i => i.Name).Should().Equal(["RadioGroup", "Select"]); } [Fact] public async Task Filters_Out_Inactive_Components() { var db = CreateDb( Seed("Input", "基础输入", 1, active: true), Seed("Hidden", "基础输入", 2, active: false) ); var handler = new GetFormComponentManifestQueryHandler(db); var result = await handler.Handle(new GetFormComponentManifestQuery(), default); result.Groups.Should().ContainSingle(); result.Groups[0].Items.Should().ContainSingle(); result.Groups[0].Items[0].Name.Should().Be("Input"); } [Fact] public async Task Projection_Excludes_SupportedProps() { // manifest 是轻量接口:物料面板不需要 SupportedProps(那是属性面板用的全量接口的职责) var db = CreateDb( Seed("Input", "基础输入", 1, supportedProps: "{\"basic\":[{\"key\":\"placeholder\"}]}") ); var handler = new GetFormComponentManifestQueryHandler(db); var result = await handler.Handle(new GetFormComponentManifestQuery(), default); var item = result.Groups[0].Items[0]; // ComponentManifestItem 只应有这 6 个字段,不含 SupportedProps item.Name.Should().Be("Input"); item.DisplayName.Should().Be("Input显示名"); item.Category.Should().Be("基础输入"); item.Icon.Should().Be("lucide:test"); item.DefaultSchema.Should().Be("{}"); item.SortOrder.Should().Be(1); // 确认 DTO 类型本身没有 SupportedProps 字段(编译期保证,运行期额外断言属性集) item.GetType().GetProperties().Select(p => p.Name) .Should().NotContain("SupportedProps"); } [Fact] public async Task Returns_Empty_When_No_Active_Components() { var db = CreateDb(Seed("Hidden", "x", 1, active: false)); var handler = new GetFormComponentManifestQueryHandler(db); var result = await handler.Handle(new GetFormComponentManifestQuery(), default); result.Groups.Should().BeEmpty(); } }