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.  [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`));
});