import { expect, test } from "bun:test"; import type { BlockNode } from "@wrnexus/blocks"; import { importMjml, renderEmail } from "../src/index.ts"; const source = (attributes: string) => `Buy & save !`; const findButton = (node: BlockNode): BlockNode | undefined => node.type === "content.button" ? node : Object.values(node.slots).flat().map(findButton).find(Boolean); test("imported button preserves supported appearance and escaped label/link across export", async () => { let id = 0; const imported = importMjml(source('align="right" background-color="#123456" color="#abcdef" font-size="22px" font-weight="400" border-radius="12px"'), { id: () => `n${++id}` }); expect(imported.ok).toBe(true); if (!imported.ok) return; const button = findButton(imported.value.root)!; expect(button.props).toEqual({ label: "Buy & save !", href: "https://example.com/?a=1&b=2" }); expect(button.styles).toEqual({ textAlign: { base: "right" }, backgroundColor: { base: "#123456" }, color: { base: "#abcdef" }, fontSize: { base: "22px" }, fontWeight: { base: "400" }, borderRadius: { base: "12px" } }); const rendered = await renderEmail(imported.value, { mjml: true }); expect(rendered.ok).toBe(true); if (!rendered.ok) return; expect(rendered.value.parts.html.content).toContain("font-size:22px;font-weight:400;border-radius:12px"); expect(rendered.value.parts.text.content).toContain("Buy & save !"); const again = importMjml(rendered.value.exports!.mjml!, { id: () => `n${++id}` }); expect(again.ok).toBe(true); if (again.ok) { const next = findButton(again.value.root)!; expect(next.props).toEqual(button.props); expect(next.styles).toEqual(button.styles); } }); test("unsupported or unsafe button attributes produce explicit diagnostics", () => { for (const attributes of ['color="red;position:fixed"', 'align="justify"', 'font-size="expression(1)"', 'onclick="alert(1)"', 'padding="20px"']) { let id = 0; const result = importMjml(source(attributes), { id: () => `n${++id}` }); expect(result.ok).toBe(false); if (!result.ok) expect(result.errors.some(error => error.code === "mjml.button-attribute")).toBe(true); } });