import type { PublicOrganizationRecord } from "@umt/contracts"; import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import { createSlotRegistry, type SlotEntity, slots, visibleActions } from "~/lib/slots.ts"; import { SlotPanels, useSlotActions } from "./slot-mounts.tsx"; // 前端插槽(ADR-006):按位置注册、按 order 与注册顺序排列、默认直接显示(没有开关)、挂载点渲染。 const entity: SlotEntity = { id: "e1", title: "示例剧", kind: "series", status: "active" }; const record = (over: Partial = {}) => ({ id: "r1", status: "succeeded", action: "copy", media: { entityId: "e1", title: "示例剧" }, ...over, }) as unknown as PublicOrganizationRecord; describe("插槽注册表", () => { it("按 order 排序,同序保持注册顺序;同一位置 id 重复报错", () => { const reg = createSlotRegistry(); const Panel = (name: string) => () => createElement("p", null, name); reg.register("entity.panels", { id: "b", feature: "x", Component: Panel("B") }); reg.register("entity.panels", { id: "a", feature: "x", order: 10, Component: Panel("A") }); reg.register("entity.panels", { id: "c", feature: "x", Component: Panel("C") }); expect(reg.entries("entity.panels").map((e) => e.id)).toEqual(["a", "b", "c"]); expect(reg.entries("settings.sections")).toEqual([]); expect(() => reg.register("entity.panels", { id: "a", feature: "x", Component: Panel("A2") })).toThrow(/已有 a/); }); it("面板类挂载点按顺序渲染全部组件,拿到页面给的 props;没有注册时什么都不渲染", () => { const reg = createSlotRegistry(); reg.register("entity.panels", { id: "p", feature: "x", Component: ({ entity: e }) => createElement("section", null, `面板:${e.title}`), }); const html = renderToStaticMarkup( createElement(SlotPanels, { position: "entity.panels", props: { entity }, registry: reg }), ); expect(html).toBe("
面板:示例剧
"); expect( renderToStaticMarkup( createElement(SlotPanels, { position: "entity.panels", props: { entity }, registry: createSlotRegistry() }), ), ).toBe(""); }); it("动作类:visible 过滤,缺省显示;挂载点给出可见项", () => { const reg = createSlotRegistry(); const Dialog = () => null; reg.register("entity.actions", { id: "always", feature: "x", label: "总在", Dialog }); reg.register("entity.actions", { id: "active-only", feature: "x", label: "未合并才有", visible: ({ entity: e }) => e.status !== "merged", Dialog, }); expect(visibleActions(reg, "entity.actions", { entity }).map((a) => a.id)).toEqual(["always", "active-only"]); expect(visibleActions(reg, "entity.actions", { entity: { ...entity, status: "merged" } }).map((a) => a.id)).toEqual( ["always"], ); const Probe = () => { const s = useSlotActions("entity.actions", reg); return createElement( "ul", null, s.items({ entity }).map((a) => createElement("li", { key: a.id }, a.label)), s.dialog, ); }; expect(renderToStaticMarkup(createElement(Probe))).toBe(""); }); }); describe("补字幕样板挂在两个真实位置", () => { it("作品页操作区「补字幕」:合并后的作品不显示", () => { expect(visibleActions(slots, "entity.actions", { entity }).map((a) => [a.feature, a.label])).toEqual([ ["subtitles", "补字幕"], ]); expect(visibleActions(slots, "entity.actions", { entity: { ...entity, status: "merged" } })).toEqual([]); }); it("整理记录行菜单「补字幕」:只给成功 / 部分成功、认到作品的入库类整理", () => { const labels = (r: PublicOrganizationRecord) => visibleActions(slots, "organize.record.actions", { record: r }).map((a) => a.label); expect(labels(record())).toEqual(["补字幕"]); expect(labels(record({ status: "partial", action: "move" }))).toEqual(["补字幕"]); expect(labels(record({ status: "failed" }))).toEqual([]); expect(labels(record({ action: "trash" }))).toEqual([]); expect(labels(record({ media: null }))).toEqual([]); }); });