ai-chatbot/tests/routes/flow-board-xmind-export.test.ts

112 lines
3.8 KiB
TypeScript

import { expect, test } from "@playwright/test";
import JSZip from "jszip";
import { exportXmind } from "@/lib/flow-board/xmind-export";
const topic = (id: string, title: string, children: any[] = []): any => ({
id,
type: "mind_child",
data: { topic: { children: [{ text: title }] } },
children,
});
const root = (id: string, title: string, children: any[] = []): any => ({
...topic(id, title, children),
type: "mindmap",
layout: "right",
points: [[0, 0]],
});
const read = async (result: Awaited<ReturnType<typeof exportXmind>>) =>
JSZip.loadAsync(await result.blob.arrayBuffer());
test("XMind export uses current active content, fresh IDs and valid cross-topic references", async () => {
const a = topic("a", "研发");
const b = topic("b", "上线");
const r = root("r", "最新中心", [
a,
b,
{ ...topic("sum", "第一阶段"), start: 0, end: 1 },
]);
a.xmind = {
topicLinkId: "b",
notes: { plain: { content: "计划\n第二行" } },
labels: ["工作"],
};
a.data.topic.children[0] = { text: "研发", bold: true, "font-size": 24 };
const elements = [
r,
{
id: "boundary",
type: "geometry",
mindAnnotation: { kind: "boundary", topicIds: ["a", "b"], padding: 18 },
},
{
id: "rel",
type: "arrow-line",
texts: [{ text: { children: [{ text: "前置" }] } }],
mindAnnotation: { kind: "relationship", sourceId: "a", targetId: "b" },
},
];
const snapshot = {
activeSheetId: "one",
children: elements,
sheets: [
{ id: "one", title: "计划", children: [root("r", "旧数据")] },
{ id: "two", title: "归档", children: [root("r", "第二中心")] },
],
};
const before = JSON.stringify(snapshot);
const result = await exportXmind(snapshot, "计划");
const zip = await read(result);
const sheets = JSON.parse(await zip.file("content.json")!.async("string"));
expect(sheets).toHaveLength(2);
const out = sheets[0].rootTopic;
expect(out.title).toBe("最新中心");
expect(out.children.attached[0].href).toBe(
`xmind:#${out.children.attached[1].id}`
);
expect(out.children.attached[0].notes.plain.content).toBe("计划\n第二行");
expect(out.children.attached[0].style.properties["fo:font-size"]).toBe("24");
expect(out.boundaries[0].range).toBe("(0,1)");
expect(out.summaries[0].topicId).toBe(out.children.summary[0].id);
expect(sheets[0].relationships[0]).toMatchObject({
end1Id: out.children.attached[0].id,
end2Id: out.children.attached[1].id,
title: "前置",
});
expect(sheets[1].rootTopic.id).not.toBe(out.id);
expect(JSON.stringify(snapshot)).toBe(before);
expect(result.warnings).toEqual([]);
});
test("XMind attachment bytes and manifest entries are portable", async () => {
const r = root("root", "附件");
r.xmind = {
attachment: {
name: "需求.txt",
url: "data:application/octet-stream;base64,5YaF5a65",
mimeType: "application/octet-stream",
},
};
const zip = await read(await exportXmind({ children: [r] }, "附件"));
const sheets = JSON.parse(await zip.file("content.json")!.async("string"));
const path = sheets[0].rootTopic.href.replace(/^xap:/, "");
expect(await zip.file(path)!.async("string")).toBe("内容");
expect(
JSON.parse(await zip.file("manifest.json")!.async("string"))["file-entries"]
).toHaveProperty([path]);
expect(
JSON.parse(await zip.file("metadata.json")!.async("string"))
.dataStructureVersion
).toBe("2");
});
test("unsupported board elements warn and empty mind sheets never export misleading workbooks", async () => {
const result = await exportXmind(
{ children: [root("r", "中心"), { id: "text", type: "text" }] },
"测试"
);
expect(result.warnings[0]).toContain("自由画布");
await expect(exportXmind({ children: [] }, "空白")).rejects.toThrow(
"没有可导出"
);
});