Files
WRNexusJS/packages/email-renderer/test/marketing-images.test.ts
T
Clintchiz baee958c68
Quality / quality (ubuntu-latest) (push) Failing after 9m58s
Quality / quality (windows-latest) (push) Canceled after 0s
fix(email-renderer): preserve marketing images across output formats
2026-09-05 12:51:30 +05:30

115 lines
3.6 KiB
TypeScript

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<string, string>): 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<string, string> =
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 &amp; 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("<amp-img");
const product = await renderEmail(
document("commerce.product", {
title: "Product",
price: "12",
image: "https://example.com/p.png",
href: "https://example.com",
}),
{ amp: true },
);
expect(product.ok).toBe(true);
if (product.ok) expect(product.value.parts.amp?.content).toContain('width="600" height="400"');
});
test("marketing image dimensions reject invalid or injected values", async () => {
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);
}
});