import { openXmindStructures } from "./editor-actions"; import { expect, type Page, test } from "@playwright/test"; import JSZip from "jszip"; async function upload(page: Page, files: Record, name = "测试导图.xmind") { const zip = new JSZip(); for (const [path, content] of Object.entries(files)) zip.file(path, content); await page.locator('input[type=file][accept*=xmind]').setInputFiles({ name, mimeType: "application/octet-stream", buffer: await zip.generateAsync({ type: "nodebuffer" }), }); } test("modern XMind preserves sheets, free topics, summaries, boundaries, relationships and style", async ({ page }) => { const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); await page.goto("/dev/flow-board-preview", { waitUntil: "commit" }); const root = { id: "root", title: "新品项目", structureClass: "org.xmind.ui.logic.right", children: { attached: [{ id: "research", title: "市场调研", style: { properties: { "svg:fill": "#fff4cc", "fo:font-size": 22, "fo:font-weight": "bold" } }, children: { attached: [{ id: "a", title: "用户访谈" }, { id: "b", title: "竞品分析" }] } }, { id: "design", title: "产品设计" }], summary: [{ id: "summary", title: "启动阶段" }], detached: [{ id: "free", title: "待讨论", position: { x: 450, y: 300 } }], }, summaries: [{ range: "(0,1)", topicId: "summary" }], boundaries: [{ range: "(0,1)" }] }; await upload(page, { "content.json": JSON.stringify([ { title: "产品计划", rootTopic: root, relationships: [{ end1Id: "research", end2Id: "design", title: "输入设计" }] }, { title: "上线清单", rootTopic: { id: "root2", title: "正式上线", structureClass: "org.xmind.ui.org-chart.down", children: { attached: [{ id: "qa", title: "质量验收" }] } } }, ]) }); const canvas = page.locator(".drawnix"); 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.getByText("市场调研", { exact: true })).toHaveCSS("font-size", "22px"); await canvas.getByText("市场调研", { exact: true }).click(); await expect(page.getByLabel("节点字号")).toHaveValue("22"); await openXmindStructures(page); await expect(page.getByRole("button", { name: "向右逻辑", exact: true })).toHaveAttribute("aria-pressed", "true"); await page.getByRole("button", { name: "上线清单", exact: true }).click(); await expect(canvas.getByText("质量验收", { exact: true })).toBeVisible(); await expect(canvas.getByText("新品项目", { exact: true })).toHaveCount(0); await page.getByRole("button", { name: "产品计划", exact: true }).click(); await expect(canvas.getByText("输入设计", { exact: true })).toBeVisible(); await canvas.getByText("产品设计", { exact: true }).dblclick(); await page.keyboard.press("ControlOrMeta+A"); await page.keyboard.insertText("设计已更新"); await page.locator('.flow-board-canvas-shell').click({ position: { x: 100, y: 100 } }); await expect(canvas.getByText("设计已更新", { exact: true })).toBeVisible(); expect(errors).toEqual([]); }); test("legacy XML XMind retains separate sheets and folded branches", async ({ page }) => { await page.goto("/dev/flow-board-preview", { waitUntil: "commit" }); await upload(page, { "content.xml": ` 旧版计划旧版中心折叠分支内部任务 旧版归档归档中心`, "styles.xml": ``, }); const canvas = page.locator(".drawnix"); await expect(canvas.getByText("折叠分支", { exact: true })).toBeVisible(); await expect(canvas.getByText("内部任务", { exact: true })).toHaveCount(0); await canvas.getByText("折叠分支", { exact: true }).click(); await page.keyboard.press("ControlOrMeta+/"); await expect(canvas.getByText("内部任务", { exact: true })).toBeVisible(); await page.getByRole("button", { name: "旧版归档", exact: true }).click(); await expect(canvas.getByText("归档中心", { exact: true })).toBeVisible(); }); test("invalid import preserves the existing workbook and gives a readable error", async ({ page }) => { await page.goto("/dev/flow-board-preview", { waitUntil: "commit" }); await upload(page, { "content.json": JSON.stringify([{ title: "保留画布", rootTopic: { id: "root", title: "已有内容" } }]) }); await expect(page.locator(".drawnix").getByText("已有内容", { exact: true })).toBeVisible(); await upload(page, { "content.json": "broken json" }, "损坏文件.xmind"); await expect(page.getByText("XMind 内容解析失败,请在 XMind 中重新保存后再导入", { exact: true })).toBeVisible(); await expect(page.locator(".drawnix").getByText("已有内容", { exact: true })).toBeVisible(); await page.locator('input[type=file][accept*=xmind]').setInputFiles({ name: "伪装文件.xmind", mimeType: "application/octet-stream", buffer: Buffer.from("not a zip") }); await expect(page.getByText("无法打开 XMind 文件,请确认文件完整且未加密", { exact: true })).toBeVisible(); });