import { expect, test } from "bun:test"; import type { BlockDocument } from "@wrnexus/blocks"; import { renderEmail, validateMjml } from "../src/index.ts"; function document(type: string, props: Record): BlockDocument { return { schemaVersion: 1, kind: "email", meta: { subject: "Store update", lang: "en" }, theme: { tokens: {} }, root: { id: "root", type: "layout.email-document", version: 1, props: { width: "600" }, styles: {}, slots: { content: [ { id: "section", type: "layout.section", version: 1, props: { background: "#ffffff", padding: "24px" }, styles: {}, slots: { content: [{ id: "item", type, version: 1, props, styles: {}, slots: {} }] }, }, ], }, }, }; } test("product and RSS images survive HTML, AMP and strict MJML exports", async () => { for (const type of ["commerce.product", "content.feed-item"]) { const props: Record = type === "commerce.product" ? { title: "Tea & coffee", image: "https://example.com/product.png", href: "https://example.com/product", price: "$12.00", buttonLabel: "Shop now", width: "320", height: "180", } : { title: "Tea & coffee", image: "https://example.com/article.png", href: "https://example.com/article", summary: "Latest news", width: "320", height: "180", }; const result = await renderEmail(document(type, props), { amp: true, mjml: true }); expect(result.ok).toBe(true); if (!result.ok) continue; for (const output of [ result.value.parts.html.content, result.value.parts.amp?.content, result.value.exports?.mjml, ]) { expect(output).toContain(props.image); expect(output).toContain(props.href); expect(output).toContain("Tea & coffee"); if (type === "commerce.product") expect(output).toContain("$12.00"); } expect(result.value.parts.amp?.validation.valid).toBe(true); expect(result.value.parts.amp?.content).toContain( 'width="320" height="180" layout="responsive"', ); expect(result.value.parts.amp?.content).toContain(".marketing-image img{object-fit:contain}"); expect(await validateMjml(result.value.exports!.mjml!)).toEqual([]); } }); test("image-free RSS and older product documents remain renderable", async () => { const rss = await renderEmail( document("content.feed-item", { title: "News", summary: "Update", href: "https://example.com", image: "", }), { amp: true, mjml: true }, ); expect(rss.ok).toBe(true); if (rss.ok) expect(rss.value.parts.amp?.content).not.toContain(" { for (const width of ["0", "4001", "100%", '600" onerror="alert(1)']) { const result = await renderEmail( document("commerce.product", { title: "Product", price: "12", image: "https://example.com/p.png", href: "https://example.com", width, }), ); expect(result.ok).toBe(false); } });