Files
WRNexusJS/packages/email-renderer/test/root-mjml.test.ts
T

119 lines
3.4 KiB
TypeScript

import { expect, test } from "bun:test";
import type { BlockDocument, BlockNode } from "@wrnexus/blocks";
import { renderEmail, validateMjml } from "../src/index.ts";
const text = (id: string): BlockNode => ({
id,
type: "content.text",
version: 1,
props: { text: id },
styles: {},
slots: {},
});
const columns = (id: string): BlockNode => ({
id,
type: "layout.columns",
version: 1,
props: {},
styles: {},
slots: {
columns: [0, 1].map((index) => ({
id: `${id}-${index}`,
type: "layout.column",
version: 1,
props: { width: "50%" },
styles: {},
slots: { content: [text(`${id}-text-${index}`)] },
})),
},
});
test("invalid nested MJML is diagnosed and omitted while HTML and text remain usable", async () => {
const document: BlockDocument = {
schemaVersion: 1,
kind: "email",
meta: { subject: "Nested", lang: "en" },
theme: { tokens: {} },
root: {
id: "root",
type: "layout.email-document",
version: 1,
props: {},
styles: {},
slots: {
content: [
{
id: "outer",
type: "layout.section",
version: 1,
props: {},
styles: {},
slots: {
content: [
{
id: "inner",
type: "layout.section",
version: 1,
props: {},
styles: {},
slots: { content: [text("Retained nested content")] },
},
],
},
},
],
},
},
};
const rendered = await renderEmail(document, { mjml: true });
expect(rendered.ok).toBe(true);
if (!rendered.ok) return;
expect(rendered.value.exports?.mjml).toBeUndefined();
expect(rendered.value.warnings.some((warning) => warning.code === "mjml.validation")).toBe(true);
expect(rendered.value.parts.html.content).toContain("Retained nested content");
expect(rendered.value.parts.text.content).toContain("Retained nested content");
});
test("root content and separate column rows produce valid ordered MJML without changing the document", async () => {
const document: BlockDocument = {
schemaVersion: 1,
kind: "email",
meta: { subject: "Root blocks", lang: "en" },
theme: { tokens: {} },
root: {
id: "root",
type: "layout.email-document",
version: 1,
props: {},
styles: {},
slots: {
content: [
text("intro"),
columns("first"),
columns("second"),
{
id: "cta",
type: "content.button",
version: 1,
props: { label: "Act", href: "https://example.com" },
styles: {},
slots: {},
},
text("end"),
],
},
},
};
const before = structuredClone(document);
const rendered = await renderEmail(document, { mjml: true });
expect(rendered.ok).toBe(true);
if (!rendered.ok) return;
const mjml = rendered.value.exports!.mjml!;
expect(await validateMjml(mjml)).toEqual([]);
expect(mjml.match(/<mj-section\b/g)).toHaveLength(5);
expect(mjml.indexOf("intro")).toBeLessThan(mjml.indexOf("first-text-0"));
expect(mjml.indexOf("first-text-1")).toBeLessThan(mjml.indexOf("second-text-0"));
expect(mjml.indexOf("second-text-1")).toBeLessThan(mjml.indexOf(">Act<"));
expect(document).toEqual(before);
});