fix(email-renderer): stack AMP columns at mobile widths
Quality / quality (ubuntu-latest) (push) Failing after 1s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
Clintchiz committed 2026-09-05 13:21:56 +05:30
1 parent 8e51b28583
commit 2f735b267b
4 files changed
+10 -5

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/email-renderer", "name": "@wrnexus/email-renderer",
"version": "0.1.17", "version": "0.1.18",
"type": "module", "type": "module",
"description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.", "description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.",
"exports": { "exports": {
+2 -2
View File
@@ -204,7 +204,7 @@ export const columnsBlock = defineBlock({
node, node,
), ),
ampEmail: ({ node, slots }) => ampEmail: ({ node, slots }) =>
emailCell(`<table role="presentation" width="100%"><tr>${slots.columns}</tr></table>`, node), emailCell(`<table role="presentation" width="100%"><tr class="wrn-columns">${slots.columns}</tr></table>`, node),
mjml: ({ slots }) => slots.columns, mjml: ({ slots }) => slots.columns,
}, },
textRenderer: ({ slots }) => slots.columns, textRenderer: ({ slots }) => slots.columns,
@@ -234,7 +234,7 @@ export const columnBlock = defineBlock({
emailHtml: ({ node, slots }) => emailHtml: ({ node, slots }) =>
`<td data-block-id="${html(node.id)}" class="wrn-column" width="${html(attrs(node).width)}" valign="top"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">${slots.content}</table></td>`, `<td data-block-id="${html(node.id)}" class="wrn-column" width="${html(attrs(node).width)}" valign="top"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">${slots.content}</table></td>`,
ampEmail: ({ node, slots }) => ampEmail: ({ node, slots }) =>
`<td width="${html(attrs(node).width)}" valign="top"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">${slots.content}</table></td>`, `<td class="wrn-column" width="${html(attrs(node).width)}" valign="top"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">${slots.content}</table></td>`,
mjml: ({ node, slots }) => mjml: ({ node, slots }) =>
`<mj-column width="${html(attrs(node).width)}">${slots.content}</mj-column>`, `<mj-column width="${html(attrs(node).width)}">${slots.content}</mj-column>`,
}, },
+1 -1
View File
@@ -81,7 +81,7 @@ const head = (document: BlockDocument, body: string, direction: "ltr" | "rtl") =
}; };
const ampHead = (document: BlockDocument, body: string, direction: "ltr" | "rtl") => { const ampHead = (document: BlockDocument, body: string, direction: "ltr" | "rtl") => {
const props = document.root.props as Record<string, unknown>; const props = document.root.props as Record<string, unknown>;
const css = `.marketing-image img{object-fit:contain}body{margin:0;color:${props.textColor ?? "#111827"};font-family:${props.fontFamily ?? "Arial, sans-serif"}}a{color:${props.linkColor ?? "#2563eb"}}.email-bg{background:${props.background ?? "#f3f4f6"}}.email-body{max-width:${props.width ?? "600"}px;width:100%;background:${props.contentBackground ?? "#ffffff"}}`; const css = `@media only screen and (max-width:480px){.email-body .wrn-columns{display:block}.email-body .wrn-column{display:block;width:100%}}.marketing-image img{object-fit:contain}body{margin:0;color:${props.textColor ?? "#111827"};font-family:${props.fontFamily ?? "Arial, sans-serif"}}a{color:${props.linkColor ?? "#2563eb"}}.email-bg{background:${props.background ?? "#f3f4f6"}}.email-body{max-width:${props.width ?? "600"}px;width:100%;background:${props.contentBackground ?? "#ffffff"}}`;
return `<!doctype html><html amp4email data-css-strict lang="${escapeHtml(document.meta.lang)}" dir="${direction}"><head><meta charset="utf-8"><script async src="https://cdn.ampproject.org/v0.js"></script><style amp4email-boilerplate>body{visibility:hidden}</style><style amp-custom>${css}.section{padding:24px}.button{background:#2563eb;border-radius:6px;color:#fff;display:inline-block;font-weight:700;padding:12px 20px;text-decoration:none}.spacer{height:24px}</style></head><body>${body}</body></html>`; return `<!doctype html><html amp4email data-css-strict lang="${escapeHtml(document.meta.lang)}" dir="${direction}"><head><meta charset="utf-8"><script async src="https://cdn.ampproject.org/v0.js"></script><style amp4email-boilerplate>body{visibility:hidden}</style><style amp-custom>${css}.section{padding:24px}.button{background:#2563eb;border-radius:6px;color:#fff;display:inline-block;font-weight:700;padding:12px 20px;text-decoration:none}.spacer{height:24px}</style></head><body>${body}</body></html>`;
}; };
+6 -1
View File
@@ -62,13 +62,18 @@ describe("email renderer", () => {
const draft=structuredClone(document); const draft=structuredClone(document);
const content=draft.root.slots.content[0]!.slots.content; const content=draft.root.slots.content[0]!.slots.content;
draft.root.slots.content[0]!.slots.content=[{id:"columns",type:"layout.columns",version:1,props:{},styles:{},slots:{columns:Array.from({length:count},(_,index)=>({id:`column-${index}`,type:"layout.column",version:1,props:{width:`${100/count}%`},styles:{},slots:{content:index===0?content:[]}}))}}]; draft.root.slots.content[0]!.slots.content=[{id:"columns",type:"layout.columns",version:1,props:{},styles:{},slots:{columns:Array.from({length:count},(_,index)=>({id:`column-${index}`,type:"layout.column",version:1,props:{width:`${100/count}%`},styles:{},slots:{content:index===0?content:[]}}))}}];
const result=await renderEmail(draft,{mjml:true}); const result=await renderEmail(draft,{mjml:true,amp:true});
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
if(!result.ok)continue; if(!result.ok)continue;
const mjml=result.value.exports!.mjml!; const mjml=result.value.exports!.mjml!;
expect(mjml.match(/<mj-column\b/g)).toHaveLength(count); expect(mjml.match(/<mj-column\b/g)).toHaveLength(count);
expect(await validateMjml(mjml)).toEqual([]); expect(await validateMjml(mjml)).toEqual([]);
expect(mjml).toContain("Hello &lt;world&gt;"); expect(mjml).toContain("Hello &lt;world&gt;");
const amp=result.value.parts.amp;
expect(amp?.validation.valid).toBe(true);
expect(amp?.content.match(/class="wrn-column"/g)).toHaveLength(count);
expect(amp?.content).toContain('class="wrn-columns"');
expect(amp?.content).toContain('@media only screen and (max-width:480px){.email-body .wrn-columns{display:block}.email-body .wrn-column{display:block;width:100%}}');
} }
}); });
test("global brand logo survives HTML, AMP, and strict MJML export", async () => { test("global brand logo survives HTML, AMP, and strict MJML export", async () => {