import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { Window } from "happy-dom";
const code = new Bun.Transpiler({ loader: "ts" })
.transformSync(readFileSync(new URL("../src/canvas.ts", import.meta.url), "utf8"))
.replace(/^import .*;\s*$/gm, "")
.replace("export function mountBlockCanvas", "function mountBlockCanvas");
test("canvas annotates exact node locations and cleans editor decorations without changing content", () => {
const window = new Window();
const root = window.document.body;
root.innerHTML =
'
';
const original = root.innerHTML;
const tree = {
id: "root",
type: "layout.email-document",
slots: { content: [{ id: "column", type: "layout.column", slots: { content: [] } }] },
};
const before = JSON.stringify(tree);
let destroyed = 0;
const mount = new Function("mountBlockEditor", code + ";return mountBlockCanvas;")(() => ({
destroy: () => destroyed++,
refresh() {},
}));
const mounted = mount(root, tree, {});
const column = root.querySelector('[data-block-id="column"]')!;
expect(column.getAttribute("data-block-parent")).toBe("root");
expect(column.getAttribute("data-block-slot")).toBe("content");
expect(column.getAttribute("draggable")).toBe("false");
expect(root.querySelectorAll("[data-block-handle]")).toHaveLength(1);
const handle = root.querySelector("[data-block-handle]")!;
let clicks = 0,
pointers = 0;
column.addEventListener("click", () => clicks++);
column.addEventListener("dblclick", () => clicks++);
column.addEventListener("pointerdown", () => pointers++);
handle.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));
handle.dispatchEvent(new window.MouseEvent("dblclick", { bubbles: true }));
handle.dispatchEvent(new window.PointerEvent("pointerdown", { bubbles: true }));
expect(clicks).toBe(0);
expect(pointers).toBe(1);
expect(root.querySelector("[data-block-slot-target]")?.getAttribute("data-block-parent")).toBe(
"column",
);
expect(JSON.stringify(tree)).toBe(before);
mounted.destroy();
mounted.destroy();
expect(destroyed).toBe(1);
expect(root.innerHTML).toBe(original);
});
test("failed canvas mounting rolls back annotations", () => {
const root = new Window().document.body;
root.innerHTML = '';
const before = root.innerHTML;
const mount = new Function("mountBlockEditor", code + ";return mountBlockCanvas;")(() => {
throw new Error("backend unavailable");
});
expect(() =>
mount(
root,
{ id: "root", type: "root", slots: { content: [{ id: "child", type: "text", slots: {} }] } },
{},
),
).toThrow("backend unavailable");
expect(root.innerHTML).toBe(before);
});