import { expect, test } from "@playwright/test"; import { normalizeFlowBoardSnapshot } from "@/app/api/flow-board/_utils"; import { getSafeTopicHref, getTopicDetails, patchTopicDetails, } from "@/lib/flow-board/topic-details"; test("topic notes, labels, links and resources survive snapshot serialization", () => { const topic = { id: "topic", type: "mindmap", xmind: { notes: { plain: { content: "第一行\n第二行" } }, labels: ["标签一", "标签二"], href: "https://example.com", topicLinkId: "target", attachment: { name: "spec.txt", url: "data:application/octet-stream;base64,eA==", mimeType: "application/octet-stream", }, }, data: { image: { url: "data:image/png;base64,eA==", width: 32, height: 32 }, }, }; const snapshot = normalizeFlowBoardSnapshot( JSON.parse(JSON.stringify({ children: [topic] })) ); expect(snapshot.children[0]).toEqual(topic); expect(getTopicDetails(topic)).toMatchObject({ notes: "第一行\n第二行", labels: ["标签一", "标签二"], topicLinkId: "target", attachment: { name: "spec.txt" }, }); }); test("editing a note preserves image metadata and non-edited labels and links", () => { const topic = { id: "topic", xmind: { notes: { plain: { content: "旧备注" } }, labels: ["保留"], image: { src: "xap:resources/a.png" }, href: "https://example.com", markers: [{ markerId: "priority-1" }], }, }; const metadata = patchTopicDetails(topic, { notes: "新备注" }); expect(metadata).toEqual({ ...topic.xmind, notes: { plain: { content: "新备注" } }, }); expect( getTopicDetails({ ...topic, xmind: patchTopicDetails(topic, { notes: "" }), }).notes ).toBe(""); }); test("links cannot execute script and external URLs require an allowed protocol", () => { for (const value of [ "javascript:alert(1)", "data:text/html,", "file:///etc/passwd", "//example.com", "xmind:#target", ]) expect(getSafeTopicHref(value)).toBeNull(); expect(getSafeTopicHref("https://example.com/a?q=b")).toBe( "https://example.com/a?q=b" ); expect(getSafeTopicHref("mailto:hello@example.com")).toBe( "mailto:hello@example.com" ); });