import { clickXmindAction } from "./editor-actions"; import { expect, test } from "@playwright/test"; import JSZip from "jszip"; const pixel = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAO0lEQVR4AezXwQkAMAgDwND9d27rBD4FOdB3wv1yktzJrwI/f+4UIECAAAECBAgQIECAAAEC+wW63f0AAAD//+3aM8YAAAAGSURBVAMABkQgIT13JVIAAAAASUVORK5CYII="; async function expectNote(page: import("@playwright/test").Page, text: string) { await expect.poll(() => page.getByLabel("主题备注").locator("p").allTextContents().then(lines => lines.join("\n"))).toBe(text); } test("topic content imports, edits, undoes, survives sheet changes and follows internal links", async ({ page, }) => { const errors: string[] = []; page.on("pageerror", (error) => errors.push(error.message)); await page.goto("/dev/flow-board-preview", { waitUntil: "commit" }); const zip = new JSZip(); zip.file( "content.json", JSON.stringify([ { title: "产品工作表", rootTopic: { id: "root", title: "产品说明", notes: { plain: { content: "第一行说明\n第二行说明" } }, labels: ["产品", "待审核"], href: "https://example.com/docs", image: { src: "xap:resources/pixel.png", width: 48 }, children: { attached: [ { id: "branch", title: "折叠分支", branch: "folded", children: { attached: [ { id: "target", title: "目标主题", notes: { plain: { content: "目标的备注" } }, }, ], }, }, { id: "link", title: "链接入口", href: "xmind:#target" }, { id: "attachment", title: "附加文件", href: "xap:attachments/spec.txt", }, ], }, }, }, { title: "空白工作表", rootTopic: { id: "empty", title: "独立中心" } }, ]) ); zip.file("resources/pixel.png", pixel, { base64: true }); zip.file("attachments/spec.txt", "产品需求附件"); await page.locator("input[accept*=xmind]").setInputFiles({ name: "主题内容.xmind", mimeType: "application/octet-stream", buffer: await zip.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 expectNote(page, "第一行说明\n第二行说明"); await expect( page.getByRole("button", { name: "移除标签 产品", exact: true }) ).toBeVisible(); await expect(page.getByLabel("主题外部链接")).toHaveValue( "https://example.com/docs" ); await expect( canvas.locator('img[src^="data:image/png"], image[href^="data:image/png"]') ).toHaveCount(1); await page.screenshot({ path: test.info().outputPath("topic-content-panel.png"), }); await page.getByLabel("主题备注").fill("更新后的多行备注\n保留换行"); await page.getByLabel("新增主题标签").fill("完成"); await page.getByRole("button", { name: "添加", exact: true }).click(); await expect( page.getByRole("button", { name: "移除标签 完成", exact: true }) ).toBeVisible(); await clickXmindAction(page, "撤销"); await expect( page.getByRole("button", { name: "移除标签 完成", exact: true }) ).toHaveCount(0); await expectNote(page, "更新后的多行备注\n保留换行"); await clickXmindAction(page, "重做"); await expect( page.getByRole("button", { name: "移除标签 完成", exact: true }) ).toBeVisible(); await page.getByLabel("主题外部链接").fill("javascript:alert(1)"); await page.getByLabel("主题外部链接").blur(); await expect(page.getByLabel("主题外部链接")).toHaveAttribute( "aria-invalid", "true" ); await page.getByRole("button", { name: "关闭主题内容", exact: true }).click(); await clickXmindAction(page, "备注"); await page.getByRole("button", {name:"主题详情",exact:true}).click(); await expect(page.getByLabel("主题外部链接")).toHaveValue( "https://example.com/docs" ); await page.getByRole("button", { name: "空白工作表", exact: true }).click(); await page.getByRole("button", { name: "产品工作表", exact: true }).click(); await canvas.getByText("产品说明", { exact: true }).click(); await expectNote(page, "更新后的多行备注\n保留换行"); await canvas.getByText("链接入口", { exact: true }).click(); await page.getByRole("button", { name: "定位目标主题", exact: true }).click(); await expect(canvas.getByText("目标主题", { exact: true })).toBeVisible(); await expectNote(page, "目标的备注"); await canvas.getByText("附加文件", { exact: true }).click(); const downloadPromise = page.waitForEvent("download"); await page.getByRole("link", { name: "下载 spec.txt", exact: true }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe("spec.txt"); const stream = await download.createReadStream(); const chunks = []; for await (const chunk of stream!) chunks.push(chunk); expect(Buffer.concat(chunks).toString()).toBe("产品需求附件"); await page .getByLabel("上传主题附件") .setInputFiles({ name: "新版需求.txt", mimeType: "text/plain", buffer: Buffer.from("替换后的附件"), }); await expect( page.getByRole("link", { name: "下载 新版需求.txt", exact: true }) ).toHaveAttribute( "href", `data:application/octet-stream;base64,${Buffer.from("替换后的附件").toString("base64")}` ); await clickXmindAction(page, "撤销"); await expect( page.getByRole("link", { name: "下载 spec.txt", exact: true }) ).toBeVisible(); await page.getByRole("button", { name: "移除附件", exact: true }).click(); await expect( page.getByRole("link", { name: "下载 spec.txt", exact: true }) ).toHaveCount(0); await clickXmindAction(page, "撤销"); await expect( page.getByRole("link", { name: "下载 spec.txt", exact: true }) ).toBeVisible(); expect(errors).toEqual([]); }); test("legacy XML imports plain notes, separate labels and embedded pictures", async ({ page, }) => { await page.goto("/dev/flow-board-preview", { waitUntil: "commit" }); const zip = new JSZip(); zip.file( "content.xml", `旧版内容旧版资料旧版备注不重复合并的富文本备注缺失图片仍保留主题` ); zip.file("resources/image.png", pixel, { base64: true }); await page .locator("input[accept*=xmind]") .setInputFiles({ name: "旧版内容.xmind", mimeType: "application/octet-stream", buffer: await zip.generateAsync({ type: "nodebuffer" }), }); await expect( page.getByText("部分图片缺失、损坏或格式暂不支持", { exact: true }) ).toBeVisible(); const canvas = page.locator(".drawnix"); await canvas.getByText("旧版资料", { exact: true }).click(); await clickXmindAction(page, "备注"); await page.getByRole("button", {name:"主题详情",exact:true}).click(); await expectNote(page, "旧版备注"); await expect( page.getByRole("button", { name: "移除标签 计划", exact: true }) ).toBeVisible(); await expect( page.getByRole("button", { name: "移除标签 资料", exact: true }) ).toBeVisible(); await expect(page.getByLabel("主题外部链接")).toHaveValue( "https://example.com/legacy" ); await expect( canvas.locator('img[src^="data:image/png"], image[href^="data:image/png"]') ).toHaveCount(1); await expect( canvas.getByText("缺失图片仍保留主题", { exact: true }) ).toBeVisible(); });