76 lines
3.4 KiB
TypeScript
76 lines
3.4 KiB
TypeScript
import { describe, it } from "vitest";
|
|
import { capabilityIds } from "../../src/engine/registry";
|
|
import { writeFileSync } from "node:fs";
|
|
import { readFileSync } from "node:fs";
|
|
import "../../src/capabilities/index"; // 汇总导入全部能力模块(与运行时同构)
|
|
|
|
interface CatalogComp {
|
|
id: string;
|
|
name?: string;
|
|
fields?: { key: string; label?: string; widget?: string; options?: { label: string; value: string }[]; defaultValue?: unknown }[];
|
|
}
|
|
|
|
describe("系统组件兼容性全量审查", () => {
|
|
it("注册覆盖 + 面板可见性 + 字段契约核对", async () => {
|
|
const catalog: CatalogComp[] = JSON.parse(readFileSync("resources/catalog.json", "utf-8")).components ?? [];
|
|
const dup = JSON.parse(readFileSync("resources/capability-duplicates.json", "utf-8"));
|
|
const dupMap = new Map<string, { status: string; canonical?: string }>(
|
|
(Array.isArray(dup) ? dup : []).map((d: { id: string; status: string; canonical?: string }) => [d.id, { status: d.status, canonical: d.canonical }])
|
|
);
|
|
const registered = new Set(capabilityIds());
|
|
|
|
// 1) catalog 组件 → 运行时注册
|
|
const unregistered: { id: string; name?: string }[] = [];
|
|
for (const c of catalog) {
|
|
if (!registered.has(c.id)) unregistered.push({ id: c.id, name: c.name });
|
|
}
|
|
|
|
// 2) 幽灵注册:handler 有但 catalog 没有(可能是内部别名,不一定是问题,但要列出来)
|
|
const catalogIds = new Set(catalog.map((c) => c.id));
|
|
const ghosts: string[] = [];
|
|
for (const id of registered) {
|
|
if (!catalogIds.has(id)) ghosts.push(id);
|
|
}
|
|
|
|
// 3) duplicates 一致性:hide 必须有 canonical 且 canonical 已注册;keep/隐藏组件的执行器必须在
|
|
const dupIssues: string[] = [];
|
|
for (const [id, info] of dupMap) {
|
|
if (info.status === "hide" && info.canonical && !registered.has(info.canonical)) {
|
|
dupIssues.push(`hide 项 ${id} 的 canonical ${info.canonical} 未注册`);
|
|
}
|
|
if (info.status === "keep" && !registered.has(id)) {
|
|
dupIssues.push(`keep 项 ${id} 未注册(面板可见但运行会报未实现)`);
|
|
}
|
|
if (info.status === "hide" && !info.canonical && !registered.has(id)) {
|
|
dupIssues.push(`hide 项 ${id} 无 canonical 且未注册(旧流程节点将无法运行)`);
|
|
}
|
|
}
|
|
|
|
// 4) 字段契约抽查:select 枚举 defaultValue 必须在 options 里;hidden 字段不能 required
|
|
const fieldIssues: string[] = [];
|
|
for (const c of catalog) {
|
|
for (const f of c.fields ?? []) {
|
|
if (f.widget === "select" && Array.isArray(f.options) && f.options.length > 0) {
|
|
const values = f.options.map((o) => String(o.value));
|
|
if (f.defaultValue !== undefined && f.defaultValue !== "" && !values.includes(String(f.defaultValue))) {
|
|
fieldIssues.push(`${c.id}.${f.key} select 默认值 "${f.defaultValue}" 不在选项值 [${values.join(",")}] 中`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const report = {
|
|
catalogTotal: catalog.length,
|
|
registeredTotal: registered.size,
|
|
unregisteredCount: unregistered.length,
|
|
unregistered: unregistered.slice(0, 50),
|
|
ghostCount: ghosts.length,
|
|
ghosts: ghosts.slice(0, 40),
|
|
dupIssues,
|
|
fieldIssues: fieldIssues.slice(0, 40),
|
|
fieldIssueCount: fieldIssues.length
|
|
};
|
|
writeFileSync(".compat-report.txt", JSON.stringify(report, null, 1), "utf8");
|
|
}, 120000);
|
|
});
|