ai-chatbot/tests/routes/flow-board-outline.test.ts

110 lines
3.7 KiB
TypeScript

import { expect, test } from "@playwright/test";
import type { PlaitElement } from "@plait/core";
import {
applyOutlineCommand,
renameOutlineTopic,
} from "@/lib/flow-board/mind-outline";
import { getTopicEntries } from "@/lib/flow-board/topic-details";
const topic = (id: string, children: PlaitElement[] = []): PlaitElement => ({
id,
type: "mind_child",
data: {
topic: { type: "paragraph", children: [{ text: id, bold: true }] },
image: { url: "picture" },
},
xmind: { notes: "保留备注" },
children,
});
const root = (...children: PlaitElement[]): PlaitElement => ({
...topic("root", children),
type: "mindmap",
layout: "standard",
rightNodeCount: children.length,
points: [[0, 0]],
});
const find = (children: PlaitElement[], id: string) =>
getTopicEntries(children).find((entry) => entry.element.id === id)!;
test("outline nesting preserves IDs and metadata and rejects moving into descendants", () => {
const source = [root(topic("a"), topic("b", [topic("c")]))];
const before = JSON.stringify(source);
const nested = applyOutlineCommand(source, "b", "indent");
expect(find(nested.children, "b").path).toEqual([0, 0, 0]);
expect(find(nested.children, "c").path).toEqual([0, 0, 0, 0]);
expect(find(nested.children, "b").element.xmind).toEqual({
notes: "保留备注",
});
expect(JSON.stringify(source)).toBe(before);
expect(() =>
applyOutlineCommand(nested.children, "a", {
targetId: "c",
placement: "child",
})
).toThrow("不能把主题");
const outdented = applyOutlineCommand(nested.children, "b", "outdent");
expect(find(outdented.children, "b").path).toEqual([0, 1]);
});
test("outline deletion adjusts summary range without assigning unrelated topics", () => {
const source = [
root(topic("a"), topic("b"), topic("c"), {
...topic("summary"),
start: 0,
end: 1,
}),
];
const removed = applyOutlineCommand(source, "a", "delete");
expect(find(removed.children, "summary").element).toMatchObject({
start: 0,
end: 0,
});
expect(() => applyOutlineCommand(source, "b", "down")).toThrow(
"概要范围断开"
);
expect(find(source, "summary").element).toMatchObject({ start: 0, end: 1 });
});
test("outline insertion expands folded parents and includes new topics inside a summary", () => {
const source = [
root(topic("a"), topic("b"), { ...topic("summary"), start: 0, end: 1 }),
];
const inserted = applyOutlineCommand(source, "a", "sibling");
expect(find(inserted.children, "summary").element).toMatchObject({
start: 0,
end: 2,
});
expect(find(inserted.children, inserted.selectedId).path).toEqual([0, 1]);
const folded = [{ ...root(topic("a")), isCollapsed: true }];
expect(
applyOutlineCommand(folded, "root", "child").children[0].isCollapsed
).toBe(false);
});
test("outline drag moves a subtree across branches and retains annotation references", () => {
const relation = {
id: "link",
type: "arrow-line",
mindAnnotation: { kind: "relationship", sourceId: "b", targetId: "c" },
};
const source = [root(topic("a", [topic("b")]), topic("c")), relation];
const moved = applyOutlineCommand(source, "b", {
targetId: "c",
placement: "child",
});
expect(find(moved.children, "b").path).toEqual([0, 1, 0]);
expect(moved.children[1]).toEqual(relation);
});
test("outline creates the first root and renaming preserves image and text marks", () => {
const created = applyOutlineCommand([], null, "child");
expect(created.children[0]).toMatchObject({
type: "mindmap",
id: created.selectedId,
});
const data = renameOutlineTopic(topic("a"), "新名称");
expect(data).toMatchObject({
image: { url: "picture" },
topic: { children: [{ text: "新名称", bold: true }] },
});
});