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.
110 lines
4.3 KiB
TypeScript
110 lines
4.3 KiB
TypeScript
import { chooseXmindLayout, clickXmindAction } from "./editor-actions";
|
|
import { expect, test } from "@playwright/test";
|
|
import JSZip from "jszip";
|
|
|
|
test("vertical timeline mirrors branches, edits children and survives sheet switching and file round-trip", 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: "年度计划",
|
|
structureClass: "org.xmind.ui.timeline.vertical",
|
|
children: {
|
|
attached: [
|
|
{ id: "a", title: "第一阶段" },
|
|
{
|
|
id: "b",
|
|
title: "第二阶段",
|
|
children: { attached: [{ id: "b1", title: "已有左侧任务" }] },
|
|
},
|
|
{ id: "c", title: "第三阶段" },
|
|
{ id: "d", title: "第四阶段" },
|
|
],
|
|
},
|
|
},
|
|
},
|
|
{ title: "另一个画布", rootTopic: { id: "other", title: "备用中心" } },
|
|
])
|
|
);
|
|
await page.locator("input[accept*=xmind]").setInputFiles({
|
|
name: "纵向计划.xmind",
|
|
mimeType: "application/octet-stream",
|
|
buffer: await zip.generateAsync({ type: "nodebuffer" }),
|
|
});
|
|
const canvas = page.locator(".drawnix");
|
|
const axis = canvas.locator('[data-special-axis="verticalTimeline"]');
|
|
await expect(axis).toHaveCount(1);
|
|
await clickXmindAction(page, "适应画布");
|
|
const box = async (title: string) =>
|
|
(await canvas.getByText(title, { exact: true }).boundingBox())!;
|
|
const root = await box("年度计划"),
|
|
first = await box("第一阶段"),
|
|
second = await box("第二阶段"),
|
|
third = await box("第三阶段");
|
|
expect(first.x).toBeGreaterThan(root.x + root.width / 2);
|
|
expect(second.x + second.width).toBeLessThan(root.x + root.width / 2);
|
|
expect(second.y).toBeGreaterThan(first.y + first.height);
|
|
expect(third.y).toBeGreaterThan(second.y + second.height);
|
|
expect((await box("已有左侧任务")).x).toBeLessThan(second.x);
|
|
await canvas.getByText("第二阶段", { exact: true }).click();
|
|
await page.getByRole("button", { name: "子主题", exact: true }).click();
|
|
await expect(canvas.locator('[contenteditable="true"]')).toBeFocused();
|
|
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((await box("新左侧任务")).x).toBeLessThan((await box("第二阶段")).x);
|
|
await canvas.getByText("年度计划", { exact: true }).click();
|
|
await chooseXmindLayout(page, "向右逻辑");
|
|
await expect(axis).toHaveCount(0);
|
|
await clickXmindAction(page, "撤销");
|
|
await expect(axis).toHaveCount(1);
|
|
await page.getByRole("button", { name: "另一个画布", exact: true }).click();
|
|
await expect(axis).toHaveCount(0);
|
|
await page.getByRole("button", { name: "纵向计划", exact: true }).click();
|
|
await expect(axis).toHaveCount(1);
|
|
const downloading = page.waitForEvent("download");
|
|
await page.getByRole("button", { name: "导出 XMind", exact: true }).click();
|
|
const stream = await (await downloading).createReadStream();
|
|
const chunks = [];
|
|
for await (const chunk of stream!) chunks.push(chunk);
|
|
const buffer = Buffer.concat(chunks);
|
|
const exported = await JSZip.loadAsync(buffer);
|
|
const sheets = JSON.parse(
|
|
await exported.file("content.json")!.async("string")
|
|
);
|
|
expect(sheets[0].rootTopic.structureClass).toBe(
|
|
"org.xmind.ui.timeline.vertical"
|
|
);
|
|
expect(
|
|
sheets[0].rootTopic.children.attached[1].children.attached.map(
|
|
(t: { title: string }) => t.title
|
|
)
|
|
).toContain("新左侧任务");
|
|
await page
|
|
.locator("input[accept*=xmind]")
|
|
.setInputFiles({
|
|
name: "往返.xmind",
|
|
mimeType: "application/octet-stream",
|
|
buffer,
|
|
});
|
|
await expect(axis).toHaveCount(1);
|
|
await expect(canvas.getByText("新左侧任务", { exact: true })).toBeVisible();
|
|
await clickXmindAction(page, "适应画布");
|
|
await page.screenshot({
|
|
path: test.info().outputPath("vertical-timeline-preview.png"),
|
|
animations: "disabled",
|
|
});
|
|
expect(errors).toEqual([]);
|
|
});
|