feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -2,21 +2,39 @@ import { expect, test } from "bun:test";
|
||||
import type { BlockNode } from "@wrnexus/blocks";
|
||||
import { importMjml, renderEmail } from "../src/index.ts";
|
||||
|
||||
const source = (attributes: string) => `<mjml><mj-body><mj-section><mj-column><mj-button href="https://example.com/?a=1&b=2" ${attributes}>Buy & save !</mj-button></mj-column></mj-section></mj-body></mjml>`;
|
||||
const findButton = (node: BlockNode): BlockNode | undefined => node.type === "content.button" ? node : Object.values(node.slots).flat().map(findButton).find(Boolean);
|
||||
const source = (attributes: string) =>
|
||||
`<mjml><mj-body><mj-section><mj-column><mj-button href="https://example.com/?a=1&b=2" ${attributes}>Buy & save !</mj-button></mj-column></mj-section></mj-body></mjml>`;
|
||||
const findButton = (node: BlockNode): BlockNode | undefined =>
|
||||
node.type === "content.button"
|
||||
? node
|
||||
: Object.values(node.slots).flat().map(findButton).find(Boolean);
|
||||
|
||||
test("imported button preserves supported appearance and escaped label/link across export", async () => {
|
||||
let id = 0;
|
||||
const imported = importMjml(source('align="right" background-color="#123456" color="#abcdef" font-size="22px" font-weight="400" border-radius="12px"'), { id: () => `n${++id}` });
|
||||
const imported = importMjml(
|
||||
source(
|
||||
'align="right" background-color="#123456" color="#abcdef" font-size="22px" font-weight="400" border-radius="12px"',
|
||||
),
|
||||
{ id: () => `n${++id}` },
|
||||
);
|
||||
expect(imported.ok).toBe(true);
|
||||
if (!imported.ok) return;
|
||||
const button = findButton(imported.value.root)!;
|
||||
expect(button.props).toEqual({ label: "Buy & save !", href: "https://example.com/?a=1&b=2" });
|
||||
expect(button.styles).toEqual({ textAlign: { base: "right" }, backgroundColor: { base: "#123456" }, color: { base: "#abcdef" }, fontSize: { base: "22px" }, fontWeight: { base: "400" }, borderRadius: { base: "12px" } });
|
||||
expect(button.styles).toEqual({
|
||||
textAlign: { base: "right" },
|
||||
backgroundColor: { base: "#123456" },
|
||||
color: { base: "#abcdef" },
|
||||
fontSize: { base: "22px" },
|
||||
fontWeight: { base: "400" },
|
||||
borderRadius: { base: "12px" },
|
||||
});
|
||||
const rendered = await renderEmail(imported.value, { mjml: true });
|
||||
expect(rendered.ok).toBe(true);
|
||||
if (!rendered.ok) return;
|
||||
expect(rendered.value.parts.html.content).toContain("font-size:22px;font-weight:400;border-radius:12px");
|
||||
expect(rendered.value.parts.html.content).toContain(
|
||||
"font-size:22px;font-weight:400;border-radius:12px",
|
||||
);
|
||||
expect(rendered.value.parts.text.content).toContain("Buy & save !");
|
||||
const again = importMjml(rendered.value.exports!.mjml!, { id: () => `n${++id}` });
|
||||
expect(again.ok).toBe(true);
|
||||
@@ -28,10 +46,17 @@ test("imported button preserves supported appearance and escaped label/link acro
|
||||
});
|
||||
|
||||
test("unsupported or unsafe button attributes produce explicit diagnostics", () => {
|
||||
for (const attributes of ['color="red;position:fixed"', 'align="justify"', 'font-size="expression(1)"', 'onclick="alert(1)"', 'padding="20px"']) {
|
||||
for (const attributes of [
|
||||
'color="red;position:fixed"',
|
||||
'align="justify"',
|
||||
'font-size="expression(1)"',
|
||||
'onclick="alert(1)"',
|
||||
'padding="20px"',
|
||||
]) {
|
||||
let id = 0;
|
||||
const result = importMjml(source(attributes), { id: () => `n${++id}` });
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.errors.some(error => error.code === "mjml.button-attribute")).toBe(true);
|
||||
if (!result.ok)
|
||||
expect(result.errors.some((error) => error.code === "mjml.button-attribute")).toBe(true);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user