Add native-style menus, search and presentation controls, typography resources, and Xmind import/export styling support. Bundle canvas styles for preview and production, show marquee borders and selection fills, and align editor density with the project UI. Include interaction regression coverage and client comparison notes.
134 lines
5.4 KiB
TypeScript
134 lines
5.4 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.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 expect(page.getByLabel("主题备注")).toHaveValue("编辑后的备注");
|
|
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(page.getByLabel("主题备注")).toHaveValue("编辑后的备注");
|
|
await page.getByRole("button", { name: "上线", exact: true }).click();
|
|
await expect(canvas.getByText("功能验收", { exact: true })).toBeVisible();
|
|
expect(errors).toEqual([]);
|
|
});
|