import "../setup.ts"; import { assert, assertEquals, assertFalse, assertStringIncludes, } from "@std/assert"; import { EmailKit, esc } from "../../mail/layout.ts"; import { EMAIL_STYLES } from "../../mail/styles.ts"; import { STYLE_NAMES } from "../../../shared/theme.ts"; const doc = (kit: EmailKit) => kit.render({ subject: "S <1>", preheader: "P & Q", markdown: "# T\n\nbody\n\n## One\n\nfirst\n\n## Two\n\nsecond\n\n---\n\nwhy\n\n[Settings](https://x.test/?a=1&b=2)", }).html; Deno.test("esc: user text never becomes markup", () => { assertEquals( esc(`'&'`), "<b a="1">'&'</b>", ); }); Deno.test("EmailKit: auto inlines light and recolors in a dark media block", () => { const html = doc(new EmailKit({ style: "smooth", scheme: "auto" })); const { light, dark } = EMAIL_STYLES.smooth; assertStringIncludes(html, `background-color:${light.card}`); assertStringIncludes(html, "@media (prefers-color-scheme:dark)"); assertStringIncludes( html, `.b-card{background-color:${dark.card}!important}`, ); assertStringIncludes(html, `content="light dark"`); // The subject, preheader and link are escaped where they land. assertStringIncludes(html, "S <1>"); assertStringIncludes(html, "P & Q"); assertStringIncludes(html, `href="https://x.test/?a=1&b=2"`); }); Deno.test("EmailKit: a chosen scheme is the only one sent", () => { const { light, dark } = EMAIL_STYLES.kanagawa; const darkHtml = doc( new EmailKit({ style: "kanagawa", scheme: "dark" }), ); assertStringIncludes(darkHtml, `background-color:${dark.card}`); assertFalse(darkHtml.includes(light.card)); assertFalse(darkHtml.includes("prefers-color-scheme")); assertStringIncludes(darkHtml, `content="dark"`); const lightHtml = doc( new EmailKit({ style: "kanagawa", scheme: "light" }), ); assertStringIncludes(lightHtml, `background-color:${light.card}`); assertFalse(lightHtml.includes("prefers-color-scheme")); }); Deno.test("EmailKit: every style draws its own card and font", () => { for (const name of STYLE_NAMES) { const style = EMAIL_STYLES[name]; const html = doc(new EmailKit({ style: name, scheme: "light" })); assertStringIncludes(html, `background-color:${style.light.card}`); assertStringIncludes(html, `font-family:${style.font}`); assertEquals(html.includes("fonts.googleapis.com"), !!style.webFont, name); } // The rainbow and the hard shadow are what GeoCities and Brutalist look like. const geo = new EmailKit({ style: "geocities", scheme: "light" }); assertStringIncludes( geo.section("A", "", false), "linear-gradient(90deg,#ff004d", ); // On "auto" the dark rainbow replaces the light one, divider and bar alike. const geoAuto = doc(new EmailKit({ style: "geocities", scheme: "auto" })); assertStringIncludes( geoAuto, `.grad{background-color:#ff004d!important;background-image:linear-gradient(90deg,#ff004d`, ); assertStringIncludes(geoAuto, `class="grad"`); const brutal = doc( new EmailKit({ style: "brutalist", scheme: "light" }), ); assert(brutal.includes("box-shadow:5px 5px 0")); }); Deno.test("EmailKit.render: the Markdown is the text part, and its blocks take the style", () => { const kit = new EmailKit({ style: "smooth", scheme: "light" }); const markdown = [ "*Eyebrow*", "# Title", "## Section", "**12** upvotes", "- **@lou** said hi *Fri*\n > a quote", "1. ![](https://x.test/t.png) [A dump](https://x.test/d) *yours* · by @lou", "[Do it](https://x.test/go)", "```\nhttps://x.test/raw\n```", "*A note.*", "---", "Why you get this.", ].join("\n\n"); const { html, text } = kit.render({ subject: "S", preheader: "P", markdown }); assertEquals(text, markdown); const { light } = EMAIL_STYLES.smooth; assertStringIncludes(html, "text-transform:uppercase;margin:0 0 8px;"); assertStringIncludes(html, ">Title"); assertStringIncludes(html, `font-size:22px;`); assertStringIncludes(html, "“a quote”"); assertStringIncludes(html, `>1`); assertStringIncludes(html, `width="64" height="48"`); assertStringIncludes(html, `>A dump`); assertStringIncludes(html, `>yours`); assertStringIncludes(html, `>by @lou`); assertStringIncludes(html, `background-color:${light.accent};`); assertStringIncludes(html, ">Do it"); assertStringIncludes(html, "word-break:break-all;"); assertStringIncludes(html, "Why you get this."); // A divider takes its color after the shorthand that would reset it. assertFalse(html.includes(`border-color:${light.rule};border-top`)); });