ai-chatbot/tests/flow-board/xmind-export.spec.ts
134 c3f5903a86 feat(flow-board): add rich notes, Gantt tasks and PNG export
Add native task metadata, working-day calendar settings, dependency scheduling and rich note editing. Support complete-sheet PNG export with resolution and transparency controls, preserve XMind extensions and update browser regressions.
2026-10-06 18:06:29 +08:00

136 lines
5.6 KiB
TypeScript

import { clickXmindAction } from "./editor-actions";
import { expect, test } from "@playwright/test";
import JSZip from "jszip";
const pixel =
"iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAO0lEQVR4AezXwQkAMAgDwND9d27rBD4FOdB3wv1yktzJrwI/f+4UIECAAAECBAgQIECAAAEC+wW63f0AAAD//+3aM8YAAAAGSURBVAMABkQgIT13JVIAAAAASUVORK5CYII=";
test("export downloads all sheets and preserves editable topic content after reimport", async ({
page,
}) => {
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/dev/flow-board-preview", { waitUntil: "commit" });
const source = new JSZip();
source.file(
"content.json",
JSON.stringify([
{
title: "策划",
rootTopic: {
id: "r",
title: "产品发布",
image: { src: "xap:resources/image.png", width: 48 },
structureClass: "org.xmind.ui.logic.right",
children: {
attached: [
{
id: "a",
title: "设计方案",
notes: { plain: { content: "备注第一行\n备注第二行" } },
labels: ["重点"],
style: {
properties: {
"fo:font-size": "22",
"fo:font-weight": "bold",
"svg:fill": "#fff4cc",
},
},
},
{ id: "b", title: "交付文件", href: "xap:attachments/需求.txt" },
{ id: "c", title: "定位设计", href: "xmind:#a" },
],
summary: [{ id: "summary", title: "准备阶段" }],
detached: [
{ id: "free", title: "待讨论", position: { x: 400, y: 300 } },
],
},
summaries: [{ range: "(0,1)", topicId: "summary" }],
boundaries: [{ range: "(0,1)" }],
},
relationships: [{ end1Id: "a", end2Id: "b", title: "交付" }],
},
{
title: "上线",
rootTopic: {
id: "root2",
title: "验收清单",
structureClass: "org.xmind.ui.org-chart.down",
children: { attached: [{ id: "item", title: "功能验收" }] },
},
},
])
);
source.file("attachments/需求.txt", "附件原文");
source.file("resources/image.png", pixel, { base64: true });
const input = page.locator("input[accept*=xmind]");
await input.setInputFiles({
name: "发布计划.xmind",
mimeType: "application/octet-stream",
buffer: await source.generateAsync({ type: "nodebuffer" }),
});
const canvas = page.locator(".drawnix");
await canvas.getByText("设计方案", { exact: true }).click();
await clickXmindAction(page, "备注");
await page.getByRole("button",{name:"主题详情",exact:true}).click();
await page.getByLabel("主题备注").fill("编辑后的备注");
const downloading = page.waitForEvent("download");
await page.getByRole("button", { name: "导出 XMind", exact: true }).click();
const downloaded = await downloading;
expect(downloaded.suggestedFilename()).toBe("发布计划.xmind");
const stream = await downloaded.createReadStream();
const chunks = [];
for await (const chunk of stream!) chunks.push(chunk);
const bytes = Buffer.concat(chunks);
const exported = await JSZip.loadAsync(bytes);
const content = JSON.parse(
await exported.file("content.json")!.async("string")
);
expect(content).toHaveLength(2);
expect(content[0].rootTopic.children.attached[0].notes.plain.content).toBe(
"编辑后的备注"
);
expect(content[0].relationships[0].title).toBe("交付");
expect(
await exported
.file(content[0].rootTopic.image.src.replace(/^xap:/, ""))!
.async("base64")
).toBe(pixel);
const attachmentPath = content[0].rootTopic.children.attached[1].href.replace(
/^xap:/,
""
);
expect(await exported.file(attachmentPath)!.async("string")).toBe("附件原文");
await downloaded.saveAs(test.info().outputPath("发布计划.xmind"));
await page.getByRole("button", { name: "上线", exact: true }).click();
await expect(canvas.getByText("功能验收", { exact: true })).toBeVisible();
await input.setInputFiles({
name: "往返验证.xmind",
mimeType: "application/octet-stream",
buffer: bytes,
});
await expect(canvas.getByText("设计方案", { exact: true })).toHaveCSS(
"font-size",
"22px"
);
for (const label of ["准备阶段", "待讨论", "交付"])
await expect(canvas.getByText(label, { exact: true })).toBeVisible();
await expect(page.locator("[plait-geometry=true]")).toHaveCount(1);
await expect(page.locator("[plait-arrow-line=true]")).toHaveCount(1);
await expect(
canvas.locator('img[src^="data:image/png"], image[href^="data:image/png"]')
).toHaveCount(1);
await canvas.getByText("设计方案", { exact: true }).click();
await clickXmindAction(page, "备注");
await page.getByRole("button",{name:"主题详情",exact:true}).click();
await expect.poll(() => page.getByLabel("主题备注").locator("p").allTextContents().then(lines => lines.join("\n"))).toBe("编辑后的备注");
await expect(
page.getByRole("button", { name: "移除标签 重点", exact: true })
).toBeVisible();
await canvas.getByText("定位设计", { exact: true }).click();
await page.getByRole("button", { name: "定位目标主题", exact: true }).click();
await expect.poll(() => page.getByLabel("主题备注").locator("p").allTextContents().then(lines => lines.join("\n"))).toBe("编辑后的备注");
await page.getByRole("button", { name: "上线", exact: true }).click();
await expect(canvas.getByText("功能验收", { exact: true })).toBeVisible();
expect(errors).toEqual([]);
});