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.
136 lines
5.6 KiB
TypeScript
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([]);
|
|
});
|