import { test } from "node:test";
import assert from "node:assert/strict";
import {
parseRichText,
richTextExcerpt,
richTextNodes,
richTextToEditorHtml,
richTextToEmailHtml,
richTextToPlain,
safeHttpUrl,
} from "../src/lib/rich-text";
/** Sérialise l'arbre React pour inspecter ce qui a réellement été construit. */
function serialize(source: string) {
return JSON.stringify(richTextNodes(source));
}
test("les paragraphes sont séparés par une ligne vide", () => {
const blocks = parseRichText("Premier.\n\nSecond.");
assert.equal(blocks.length, 2);
assert.equal(blocks[0].kind, "paragraph");
assert.equal(richTextToPlain("Premier.\n\nSecond."), "Premier.\n\nSecond.");
});
test("un saut de ligne simple reste un saut de ligne", () => {
const blocks = parseRichText("Une ligne\nune autre");
assert.equal(blocks.length, 1);
assert.deepEqual(
blocks[0].kind === "paragraph" ? blocks[0].children.map((c) => c.kind) : [],
["text", "break", "text"],
);
assert.ok(richTextToEmailHtml("Une ligne\nune autre").includes("
"));
});
test("gras et italique sont reconnus, et imbriqués", () => {
assert.ok(richTextToEmailHtml("**gras**").includes("doux"));
assert.ok(richTextToEmailHtml("**du *doux* dedans**").includes("doux"));
});
test("une étoile non appariée reste littérale", () => {
assert.equal(richTextToPlain("**gras sans fin"), "**gras sans fin");
assert.equal(richTextToPlain("2 * 3 = 6"), "2 * 3 = 6");
assert.ok(!richTextToEmailHtml("**gras sans fin").includes(" {
const blocks = parseRichText("- un\n- deux\n\n1. a\n2. b");
assert.equal(blocks.length, 2);
assert.equal(blocks[0].kind, "bullets");
assert.equal(blocks[0].kind === "bullets" ? blocks[0].items.length : 0, 2);
assert.equal(blocks[1].kind, "ordered");
const html = richTextToEmailHtml("- un\n- deux");
assert.equal(html.match(/ {
const blocks = parseRichText("*insistons* sur ce point");
assert.equal(blocks[0].kind, "paragraph");
});
test("un sous-titre devient un bloc à part", () => {
const blocks = parseRichText("## Ordre du jour\nPremier point");
assert.equal(blocks.length, 2);
assert.equal(blocks[0].kind, "heading");
assert.ok(richTextToEmailHtml("## Ordre du jour").includes("Georgia"));
});
test("un lien http(s) est rendu, en nouvel onglet et sans fuite d'ouvreur", () => {
const nodes = serialize("[le site](https://pleinr.fr/promotions)");
assert.ok(nodes.includes("https://pleinr.fr/promotions"));
assert.ok(nodes.includes("noopener noreferrer"));
assert.ok(nodes.includes('"target":"_blank"'));
assert.ok(richTextToEmailHtml("[le site](https://pleinr.fr)").includes('href="https://pleinr.fr/"'));
});
test("un lien javascript: ou data: perd sa cible et ne garde que son libellé", () => {
for (const hostile of [
"[clique ici](javascript:alert(1))",
"[clique ici](data:text/html,)",
"[clique ici](vbscript:msgbox)",
"[clique ici]( JavaScript:alert(1) )",
]) {
const html = richTextToEmailHtml(hostile);
assert.ok(!html.includes(" {
const html = richTextToEmailHtml("[a]b](https://pleinr.fr)");
assert.ok(!html.includes(" {
const payloads = [
"",
'
',
"