ai-chatbot/tests/flow-board/special-layouts.spec.ts
134 79253245f3 fix(flow-board): align Xmind editor UI and restore marquee feedback
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.
2026-10-04 13:32:54 +08:00

126 lines
5.1 KiB
TypeScript

import { chooseXmindLayout, clickXmindAction, openXmindStructures } from "./editor-actions";
import { expect, test } from "@playwright/test";
import JSZip from "jszip";
test("special layouts change geometry and connectors, edit normally and round-trip their structure", 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: "结构试验",
relationships: [{ end1Id: "a", end2Id: "b", title: "关联" }],
rootTopic: {
id: "root",
title: "中心主题",
structureClass: "org.xmind.ui.fishbone.leftHeaded",
children: {
attached: [
{
id: "a",
title: "分支一",
children: {
attached: [
{ id: "a1", title: "细分任务一" },
{ id: "a2", title: "细分任务二" },
],
},
},
{ id: "b", title: "分支二" },
{ id: "c", title: "分支三" },
{ id: "d", 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");
await expect(page.locator('[plait-arrow-line="true"]')).toHaveCount(1);
await expect(canvas.locator('[data-special-axis="fishbone"]')).toHaveCount(1);
await expect(
canvas.locator('[data-special-connector="fishbone"]')
).toHaveCount(4);
await clickXmindAction(page, "适应画布");
const rect = async (title: string) =>
(await canvas.getByText(title, { exact: true }).boundingBox())!;
const center = await rect("中心主题");
const a = await rect("分支一");
const b = await rect("分支二");
const c = await rect("分支三");
expect(a.y + a.height).toBeLessThan(center.y);
expect(b.y).toBeGreaterThan(center.y + center.height);
expect(c.x).toBeGreaterThan(a.x + a.width);
await canvas.getByText("中心主题", { exact: true }).click();
await openXmindStructures(page);
await expect(
page.getByRole("button", { name: "鱼骨图", exact: true })
).toHaveAttribute("aria-pressed", "true");
await page.screenshot({
path: test.info().outputPath("fishbone-preview.png"),
animations: "disabled",
});
await chooseXmindLayout(page, "水平时间轴");
await expect(canvas.locator('[data-special-axis="timeline"]')).toHaveCount(1);
await expect(
canvas.locator('[data-special-connector="timeline"] circle')
).toHaveCount(4);
await expect(canvas.locator('[data-special-axis="fishbone"]')).toHaveCount(0);
await clickXmindAction(page, "撤销");
await expect(canvas.locator('[data-special-axis="fishbone"]')).toHaveCount(1);
await clickXmindAction(page, "重做");
await expect(canvas.locator('[data-special-axis="timeline"]')).toHaveCount(1);
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: 80, y: 80 } });
await expect(
canvas.getByText("已编辑的时间节点", { exact: true })
).toBeVisible();
await canvas.getByText("分支三", { exact: true }).click();
await page.getByRole("button", { name: "子主题", exact: true }).click();
await expect(canvas.locator('[contenteditable="true"]')).toBeFocused();
await page.keyboard.press("ControlOrMeta+A");
await page.keyboard.insertText("新增时间任务");
await page
.locator(".flow-board-canvas-shell")
.click({ position: { x: 80, y: 80 } });
await expect(canvas.getByText("新增时间任务", { exact: true })).toBeVisible();
await canvas.getByText("中心主题", { exact: true }).click();
await chooseXmindLayout(page, "括号图");
await expect(
canvas.locator('[data-special-structure="brace"] path')
).toHaveCount(3);
await expect(canvas.locator("[data-special-axis]")).toHaveCount(0);
await page.screenshot({
path: test.info().outputPath("brace-preview.png"),
animations: "disabled",
});
const downloading = page.waitForEvent("download");
await page.getByRole("button", { name: "导出 XMind", exact: true }).click();
const download = await downloading;
const stream = await download.createReadStream();
const chunks = [];
for await (const chunk of stream!) chunks.push(chunk);
const exported = await JSZip.loadAsync(Buffer.concat(chunks));
const content = JSON.parse(
await exported.file("content.json")!.async("string")
);
expect(content[0].rootTopic.structureClass).toBe("org.xmind.ui.brace.right");
await chooseXmindLayout(page, "向右逻辑");
await expect(canvas.locator("[data-special-structure]")).toHaveCount(0);
expect(errors).toEqual([]);
});