119 lines
3.4 KiB
TypeScript
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);
|
|
});
|