// Tests for text formatting (link detection, file sizes). node test/format.test.mjs import assert from "node:assert/strict"; import { tokenizeText, richTokens, blockTokens, firstLink, formatBytes, replySnippet, reactionLine, emojiOnlyCount, typingSummary, autoGroupTitle, nameList, membershipLine, youtubeId, changelogParagraphs, formatCountdown } from "../web/static/format.js"; import { emojify, emojiMatches } from "../web/static/emoji.js"; let failures = 0; async function test(name, fn) { try { await fn(); console.log("PASS " + name); } catch (e) { console.log("FAIL " + name + " β€” " + (e && e.message)); failures++; } } // Reconstructing the token texts must always equal the input (no data lost). function roundtrip(text) { return tokenizeText(text) .map((t) => t.text) .join(""); } await test("plain text has no links and round-trips", () => { const toks = tokenizeText("just a normal message"); assert.equal(toks.length, 1); assert.equal(toks[0].href, undefined); assert.equal(roundtrip("just a normal message"), "just a normal message"); }); await test("https URL becomes a link", () => { const toks = tokenizeText("see https://example.com/page now"); const link = toks.find((t) => t.href); assert.equal(link.text, "https://example.com/page"); assert.equal(link.href, "https://example.com/page"); }); await test("bare www. gets an https href", () => { const link = tokenizeText("go to www.example.com").find((t) => t.href); assert.equal(link.text, "www.example.com"); assert.equal(link.href, "https://www.example.com"); }); await test("trailing punctuation is not part of the link", () => { const toks = tokenizeText("look at https://example.com."); const link = toks.find((t) => t.href); assert.equal(link.href, "https://example.com"); assert.equal(roundtrip("look at https://example.com."), "look at https://example.com."); }); await test("a URL in parentheses keeps the paren out of the link", () => { const toks = tokenizeText("(https://example.com)"); const link = toks.find((t) => t.href); assert.equal(link.href, "https://example.com"); assert.equal(roundtrip("(https://example.com)"), "(https://example.com)"); }); await test("javascript: scheme is NOT linkified", () => { const toks = tokenizeText("javascript:alert(1)"); assert.ok(!toks.some((t) => t.href)); }); await test("multiple links in one message", () => { const links = tokenizeText("a https://one.com b http://two.com c").filter((t) => t.href); assert.equal(links.length, 2); }); await test("newlines and text are preserved around links", () => { const input = "line1\nhttps://x.com\nline3"; assert.equal(roundtrip(input), input); }); await test("bare domain (no scheme) becomes a link", () => { const link = tokenizeText("see rte.ie for news").find((t) => t.href); assert.equal(link.text, "rte.ie"); assert.equal(link.href, "https://rte.ie"); }); await test("bare multi-part domain links, and a bare domain with a path", () => { assert.equal(tokenizeText("on guardian.co.uk today").find((t) => t.href).href, "https://guardian.co.uk"); assert.equal(tokenizeText("rte.ie/news here").find((t) => t.href).href, "https://rte.ie/news"); }); await test("file names / abbreviations / versions are NOT linkified", () => { for (const s of ["node.js", "index.html", "main.go", "see e.g. this", "v1.2.3"]) { assert.ok(!tokenizeText(s).some((t) => t.href), s); assert.equal(roundtrip(s), s); } }); await test("an email's domain is not linkified", () => { assert.ok(!tokenizeText("ping paul@rte.ie now").some((t) => t.href)); }); await test("formatBytes is human readable", () => { assert.equal(formatBytes(500), "500 B"); assert.equal(formatBytes(1536), "1.5 KB"); assert.equal(formatBytes(5 * 1024 * 1024), "5.0 MB"); }); await test("replySnippet returns trimmed, single-line text", () => { assert.equal(replySnippet({ text: " hello world\n" }), "hello world"); }); await test("replySnippet truncates long text with an ellipsis", () => { const s = replySnippet({ text: "a".repeat(200) }, 10); assert.equal(s.length, 10); assert.ok(s.endsWith("…")); }); await test("replySnippet labels photo-only messages", () => { assert.equal(replySnippet({ text: "", attachments: [{ type: "image/png" }] }), "πŸ“· Photo"); }); await test("replySnippet labels non-image attachments by name", () => { assert.equal(replySnippet({ attachments: [{ type: "application/pdf", name: "spec.pdf" }] }), "πŸ“Ž spec.pdf"); }); await test("replySnippet caps an over-long attachment name", () => { const s = replySnippet({ attachments: [{ type: "application/pdf", name: "x".repeat(200) }] }, 20); assert.equal(s.length, 20); assert.ok(s.startsWith("πŸ“Ž ") && s.endsWith("…")); }); await test("replySnippet is empty for an empty message", () => { assert.equal(replySnippet({}), ""); assert.equal(replySnippet(null), ""); }); await test("reactionLine quotes what was reacted to", () => { assert.equal( reactionLine("James", { type: "reaction", emoji: "πŸ‘", on: true, targetText: "buy milk" }), "James reacted πŸ‘ to β€œbuy milk”", ); }); await test("reactionLine speaks for an un-react too", () => { // The list must say the same thing after a reload as it did live, and on // reload the un-react event is all it has. assert.equal( reactionLine("James", { type: "reaction", emoji: "❀️", on: false, targetText: "shipped it" }), "James removed ❀️ from β€œshipped it”", ); }); await test("reactionLine falls back to the bare form without a snippet", () => { // Reactions sent before targetText shipped β€” the primary path on rollout. assert.equal(reactionLine("James", { type: "reaction", emoji: "πŸ‘", on: true }), "James reacted πŸ‘"); assert.equal(reactionLine("James", { type: "reaction", emoji: "πŸ‘", on: false }), "James removed πŸ‘"); }); await test("reactionLine clamps a long quote", () => { const line = reactionLine("Jo", { emoji: "πŸ‘", on: true, targetText: "a".repeat(200) }, 10); assert.ok(line.startsWith("Jo reacted πŸ‘ to β€œ")); assert.ok(line.endsWith("…”")); }); await test("reactionLine takes the caller's actor, including β€œYou”", () => { assert.equal(reactionLine("You", { emoji: "πŸŽ‰", on: true, targetText: "hi" }), "You reacted πŸŽ‰ to β€œhi”"); assert.equal(reactionLine("", { emoji: "πŸŽ‰", on: true }), "Someone reacted πŸŽ‰"); }); await test("emojiOnlyCount counts 1–3 all-emoji messages", () => { assert.equal(emojiOnlyCount("πŸ‘"), 1); assert.equal(emojiOnlyCount("πŸ˜€πŸ˜‚"), 2); assert.equal(emojiOnlyCount("πŸŽ‰πŸŽ‰πŸŽ‰"), 3); }); await test("emojiOnlyCount treats a ZWJ family / skin tone as one emoji", () => { assert.equal(emojiOnlyCount("πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"), 1); assert.equal(emojiOnlyCount("πŸ‘πŸ½"), 1); }); await test("emojiOnlyCount handles a flag (regional indicators) as one emoji", () => { assert.equal(emojiOnlyCount("πŸ‡¬πŸ‡§"), 1); }); await test("emojiOnlyCount allows whitespace between emoji", () => { assert.equal(emojiOnlyCount("πŸ‘ πŸ‘"), 2); }); await test("emojiOnlyCount is 0 for more than 3 emoji", () => { assert.equal(emojiOnlyCount("πŸ‘πŸ‘πŸ‘πŸ‘"), 0); }); await test("emojiOnlyCount is 0 when any text accompanies the emoji", () => { assert.equal(emojiOnlyCount("hi πŸ‘"), 0); assert.equal(emojiOnlyCount("πŸ‘ nice"), 0); }); await test("emojiOnlyCount is 0 for empty or whitespace-only text", () => { assert.equal(emojiOnlyCount(""), 0); assert.equal(emojiOnlyCount(" "), 0); assert.equal(emojiOnlyCount(null), 0); }); // Reconstructing richTokens text must equal the input (no data lost) β€” markers // are consumed, so compare against the input with markers stripped. const richText = (s) => richTokens(s).map((t) => t.text).join(""); await test("richTokens: bold / italic / strike / code", () => { assert.deepEqual(richTokens("**big**"), [{ text: "big", mark: "b" }]); assert.deepEqual(richTokens("*em*"), [{ text: "em", mark: "i" }]); assert.deepEqual(richTokens("_em_"), [{ text: "em", mark: "i" }]); assert.deepEqual(richTokens("~no~"), [{ text: "no", mark: "s" }]); assert.deepEqual(richTokens("`x = 1`"), [{ text: "x = 1", mark: "code" }]); }); await test("richTokens: bold beats italic at a double asterisk", () => { assert.deepEqual(richTokens("**b**"), [{ text: "b", mark: "b" }]); }); await test("richTokens: triple asterisk is combined bold italic", () => { assert.deepEqual(richTokens("***both***"), [{ text: "both", mark: "bi" }]); assert.deepEqual(richTokens("a ***b c*** d"), [ { text: "a " }, { text: "b c", mark: "bi" }, { text: " d" }, ]); // Neighbours don't bleed: bi next to bold, and markers still can't span lines. assert.deepEqual(richTokens("***x*** **y**"), [ { text: "x", mark: "bi" }, { text: " " }, { text: "y", mark: "b" }, ]); assert.deepEqual(richTokens("***a\nb***"), [{ text: "***a\nb***" }]); }); await test("richTokens: mixes plain text, a mark, and a link", () => { const t = richTokens("hi *there* see https://x.com"); assert.deepEqual(t[0], { text: "hi " }); assert.deepEqual(t[1], { text: "there", mark: "i" }); assert.equal(t[2].text, " see "); assert.equal(t[t.length - 1].href, "https://x.com"); }); await test("richTokens: no formatting inside a URL", () => { const t = richTokens("https://x.com/a_b_c"); assert.equal(t.length, 1); assert.equal(t[0].href, "https://x.com/a_b_c"); assert.equal(t[0].mark, undefined); }); await test("richTokens: underscores mid-word do NOT italicize", () => { assert.deepEqual(richTokens("my_file_name"), [{ text: "my_file_name" }]); }); await test("richTokens: plain text passes through unmarked", () => { assert.deepEqual(richTokens("just text"), [{ text: "just text" }]); assert.equal(richText("a *b* c `d` e"), "a b c d e"); }); await test("blockTokens: plain text is one untouched p block", () => { assert.deepEqual(blockTokens("hello\n\nworld"), [{ type: "p", text: "hello\n\nworld" }]); assert.deepEqual(blockTokens(""), [{ type: "p", text: "" }]); assert.deepEqual(blockTokens(null), [{ type: "p", text: "" }]); }); await test("blockTokens: fenced code block, markers verbatim inside", () => { const blocks = blockTokens("look:\n```\nif (a > b) { *notBold* }\n```\nnice"); assert.deepEqual(blocks, [ { type: "p", text: "look:" }, { type: "code", text: "if (a > b) { *notBold* }", lang: "" }, { type: "p", text: "nice" }, ]); }); await test("blockTokens: fence captures a language tag", () => { const blocks = blockTokens("```js\nconst x = 1;\n```"); assert.deepEqual(blocks, [{ type: "code", text: "const x = 1;", lang: "js" }]); }); await test("blockTokens: unterminated fence runs to the end", () => { const blocks = blockTokens("```\nline one\nline two"); assert.deepEqual(blocks, [{ type: "code", text: "line one\nline two", lang: "" }]); }); await test("blockTokens: inline backticks on one line are not a fence", () => { const blocks = blockTokens("```js``` opens a fence"); assert.deepEqual(blocks, [{ type: "p", text: "```js``` opens a fence" }]); }); await test("blockTokens: consecutive > lines merge into one quote", () => { const blocks = blockTokens("> two roads diverged\n> in a yellow wood\nso true"); assert.deepEqual(blocks, [ { type: "quote", text: "two roads diverged\nin a yellow wood" }, { type: "p", text: "so true" }, ]); }); await test("blockTokens: bare > continues a quote as a blank line", () => { const blocks = blockTokens("> first\n>\n> second"); assert.deepEqual(blocks, [{ type: "quote", text: "first\n\nsecond" }]); }); await test("blockTokens: mid-line > is not a quote", () => { assert.deepEqual(blockTokens("5 > 3 and 2 > 1"), [{ type: "p", text: "5 > 3 and 2 > 1" }]); }); await test("blockTokens: blank lines around blocks are dropped", () => { const blocks = blockTokens("intro\n\n> quoted\n\noutro"); assert.deepEqual(blocks, [ { type: "p", text: "intro" }, { type: "quote", text: "quoted" }, { type: "p", text: "outro" }, ]); }); await test("firstLink finds a URL but ignores those inside code fences", () => { assert.equal(firstLink("```\nhttps://example.com/in-code\n```\nsee https://example.com/real"), "https://example.com/real"); assert.equal(firstLink("> see https://example.com/quoted"), "https://example.com/quoted"); assert.equal(firstLink("```\nhttps://example.com/only-code\n```"), null); }); await test("emojify: known shortcodes β†’ emoji, incl. +1 / -1", () => { assert.equal(emojify("nice :tada:"), "nice πŸŽ‰"); assert.equal(emojify(":+1: / :-1:"), "πŸ‘ / πŸ‘Ž"); assert.equal(emojify(":fire::fire:"), "πŸ”₯πŸ”₯"); }); await test("emojify: unknown shortcodes are left untouched", () => { assert.equal(emojify("a :notarealemoji: b"), "a :notarealemoji: b"); }); await test("emojify: a lone shortcode becomes a single emoji (jumbo-eligible)", () => { assert.equal(emojiOnlyCount(emojify(":tada:")), 1); }); await test("emojify: URLs and times are not mangled", () => { assert.equal(emojify("see https://x.com at 10:30"), "see https://x.com at 10:30"); }); await test("typingSummary is empty when no one is typing", () => { assert.equal(typingSummary([]), ""); assert.equal(typingSummary(null), ""); }); await test("typingSummary names one and two typers", () => { assert.equal(typingSummary(["Sophie"]), "Sophie is typing…"); assert.equal(typingSummary(["Sophie", "James"]), "Sophie and James are typing…"); }); await test("typingSummary collapses three or more typers", () => { assert.equal(typingSummary(["A", "B", "C"]), "Several people are typing…"); }); await test("typingSummary ignores empty names", () => { assert.equal(typingSummary(["Sophie", ""]), "Sophie is typing…"); }); await test("autoGroupTitle lists members and collapses past three", () => { assert.equal(autoGroupTitle([]), "Group"); assert.equal(autoGroupTitle(["Paul"]), "Paul"); assert.equal(autoGroupTitle(["Paul", "Ciara"]), "Paul & Ciara"); assert.equal(autoGroupTitle(["Paul", "Ciara", "Luan"]), "Paul, Ciara & Luan"); assert.equal(autoGroupTitle(["Paul", "Ciara", "Luan", "Mabel"]), "Paul, Ciara & 2 more"); // The worked example: two shown + the rest counted. const many = ["Paul", "Ciara", ...Array.from({ length: 24 }, (_, i) => "P" + i)]; assert.equal(autoGroupTitle(many), "Paul, Ciara & 24 more"); }); await test("autoGroupTitle ignores empty names", () => { assert.equal(autoGroupTitle(["Paul", "", "Ciara"]), "Paul & Ciara"); assert.equal(autoGroupTitle(null), "Group"); }); await test("nameList joins names naturally with 'and'", () => { assert.equal(nameList([]), ""); assert.equal(nameList(["Alice"]), "Alice"); assert.equal(nameList(["Alice", "Bob"]), "Alice and Bob"); assert.equal(nameList(["Alice", "Bob", "Dave"]), "Alice, Bob and Dave"); assert.equal(nameList(["Alice", "Bob", "Carol", "Dave"]), "Alice, Bob, Carol and Dave"); }); await test("nameList ignores empty names and non-arrays", () => { assert.equal(nameList(["Alice", "", "Bob"]), "Alice and Bob"); assert.equal(nameList(null), ""); }); await test("membershipLine renders joins from the viewer's point of view", () => { const names = { u1: "Paul", u2: "Alice", u3: "Bob" }; const label = (id) => names[id] || "Someone"; // A bystander sees everyone by name. assert.equal(membershipLine({ uid: "u1", join: ["u2", "u3"] }, "u9", label), "Paul added Alice and Bob"); // A newcomer sees "you" for themselves. assert.equal(membershipLine({ uid: "u1", join: ["u2", "u3"] }, "u3", label), "Paul added Alice and you"); assert.equal(membershipLine({ uid: "u1", join: ["u3"] }, "u3", label), "Paul added you"); // The person who did it sees "You". assert.equal(membershipLine({ uid: "u1", join: ["u2", "u3"] }, "u1", label), "You added Alice and Bob"); }); await test("membershipLine renders a self-serve join as 'joined', not 'added themselves'", () => { const names = { u1: "Paul" }; const label = (id) => names[id] || "Someone"; // Joining an open group via Discover: join = [the actor themselves]. assert.equal(membershipLine({ uid: "u1", join: ["u1"] }, "u9", label), "Paul joined"); assert.equal(membershipLine({ uid: "u1", join: ["u1"] }, "u1", label), "You joined"); // A uid-less legacy payload keeps the added-list rendering. assert.equal(membershipLine({ name: "Paul", join: ["u2"] }, "u9", label), "Paul added Someone"); }); await test("membershipLine renders removals from the viewer's point of view", () => { const names = { u1: "Paul", u2: "Alice", u3: "Bob" }; const label = (id) => names[id] || "Someone"; assert.equal(membershipLine({ uid: "u1", removed: ["u3"] }, "u9", label), "Paul removed Bob"); assert.equal(membershipLine({ uid: "u1", removed: ["u2", "u3"] }, "u1", label), "You removed Alice and Bob"); assert.equal(membershipLine({ uid: "u1", removed: ["u3"] }, "u3", label), "Paul removed you"); }); await test("membershipLine renders leaves and ignores non-membership payloads", () => { const label = (id) => ({ u1: "Paul" })[id] || "Someone"; assert.equal(membershipLine({ uid: "u1", left: true }, "u9", label), "Paul left"); assert.equal(membershipLine({ uid: "u1", left: true }, "u1", label), "You left"); // No uid β†’ fall back to the baked name. assert.equal(membershipLine({ name: "Paul", left: true }, "u9", label), "Paul left"); // Not a membership event. assert.equal(membershipLine({ uid: "u1", text: "hello" }, "u9", label), null); assert.equal(membershipLine({ uid: "u1", join: [] }, "u9", label), null); assert.equal(membershipLine({ uid: "u1", removed: [] }, "u9", label), null); assert.equal(membershipLine(null, "u9", label), null); }); await test("emojiMatches ranks prefix hits and finds :tada:", () => { const m = emojiMatches("tada"); assert.equal(m[0].emoji, "πŸŽ‰"); assert.equal(m[0].code, "tada"); assert.equal(emojiMatches("").length, 0); // no query β†’ no menu }); await test("emojiMatches de-dupes aliases to the same glyph", () => { const glyphs = emojiMatches("smi").map((m) => m.emoji); assert.equal(new Set(glyphs).size, glyphs.length); // no repeated emoji }); await test("emojify: shrug, facepalm, strong and lol shortcodes are known", () => { assert.equal(emojify(":shrug:"), "🀷"); assert.equal(emojify(":facepalm:"), "🀦"); assert.equal(emojify(":strong:"), "πŸ’ͺ"); assert.equal(emojify(":lol:"), "πŸ˜‚"); }); await test("youtubeId parses the common URL shapes", () => { assert.deepEqual(youtubeId("https://www.youtube.com/watch?v=dQw4w9WgXcQ"), { id: "dQw4w9WgXcQ", start: 0 }); assert.deepEqual(youtubeId("https://youtu.be/dQw4w9WgXcQ"), { id: "dQw4w9WgXcQ", start: 0 }); assert.deepEqual(youtubeId("https://m.youtube.com/watch?v=dQw4w9WgXcQ&pp=xyz"), { id: "dQw4w9WgXcQ", start: 0 }); assert.deepEqual(youtubeId("https://www.youtube.com/shorts/dQw4w9WgXcQ"), { id: "dQw4w9WgXcQ", start: 0 }); assert.deepEqual(youtubeId("https://www.youtube.com/live/dQw4w9WgXcQ"), { id: "dQw4w9WgXcQ", start: 0 }); assert.deepEqual(youtubeId("https://www.youtube.com/embed/dQw4w9WgXcQ"), { id: "dQw4w9WgXcQ", start: 0 }); }); await test("youtubeId parses start offsets", () => { assert.equal(youtubeId("https://youtu.be/dQw4w9WgXcQ?t=90").start, 90); assert.equal(youtubeId("https://youtu.be/dQw4w9WgXcQ?t=90s").start, 90); assert.equal(youtubeId("https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=1h2m3s").start, 3723); assert.equal(youtubeId("https://www.youtube.com/watch?v=dQw4w9WgXcQ&start=45").start, 45); }); await test("youtubeId rejects non-video and lookalike URLs", () => { assert.equal(youtubeId("https://www.youtube.com/@somechannel"), null); assert.equal(youtubeId("https://www.youtube.com/playlist?list=PLx"), null); assert.equal(youtubeId("https://notyoutube.com/watch?v=dQw4w9WgXcQ"), null); assert.equal(youtubeId("https://evil.com/youtube.com/watch?v=dQw4w9WgXcQ"), null); assert.equal(youtubeId("https://www.youtube.com/watch?v=too-short"), null); assert.equal(youtubeId("not a url"), null); assert.equal(youtubeId("ftp://youtu.be/dQw4w9WgXcQ"), null); }); // ── changelog cleaning (the Admin app's update stream) ── await test("changelogParagraphs drops the πŸ€– authorship marker", () => { assert.deepEqual(changelogParagraphs("πŸ€– Unread dots now sync across your devices."), [ "Unread dots now sync across your devices.", ]); // A person marker is stripped the same way. assert.deepEqual(changelogParagraphs("πŸ‘¨ Hand-written note."), ["Hand-written note."]); }); await test("changelogParagraphs collapses hard wraps but keeps paragraph breaks", () => { const note = "πŸ€– Your space can now keep itself up\nto date. Admins get a new\nUpdates section.\n\nUpdates take seconds."; assert.deepEqual(changelogParagraphs(note), [ "Your space can now keep itself up to date. Admins get a new Updates section.", "Updates take seconds.", ]); }); await test("changelogParagraphs handles no marker, blank, and non-string", () => { assert.deepEqual(changelogParagraphs("Plain note, no marker."), ["Plain note, no marker."]); assert.deepEqual(changelogParagraphs(" "), []); assert.deepEqual(changelogParagraphs(undefined), []); }); await test("emphasis markers around a URL never ride into the href", () => { // A bot writing "**github.com/apps/eleven-messenger**" gets a clean link, // stars left as literal text (bolding ACROSS a link needs the real Markdown // parser, tracked separately). const toks = tokenizeText("go to **github.com/apps/eleven-messenger** and install"); const link = toks.find((t) => t.href); assert.ok(link, "URL still linkifies next to emphasis markers"); assert.equal(link.text, "github.com/apps/eleven-messenger"); assert.ok(!link.href.includes("*"), "no emphasis markers in the href"); assert.equal(toks.map((t) => t.text).join(""), "go to **github.com/apps/eleven-messenger** and install"); const toks2 = tokenizeText("see https://docs.github.com/rest/issues#list**"); const link2 = toks2.find((t) => t.href); assert.equal(link2.text, "https://docs.github.com/rest/issues#list"); assert.ok(!link2.href.endsWith("*")); }); await test("formatCountdown: whole units, ceiled, never optimistic", () => { const MIN = 60000; assert.equal(formatCountdown(0), "under a minute"); assert.equal(formatCountdown(30 * 1000), "under a minute"); assert.equal(formatCountdown(MIN), "under a minute"); assert.equal(formatCountdown(MIN + 1), "2m"); // ceil: the label outlives the space, not vice versa assert.equal(formatCountdown(45 * MIN), "45m"); assert.equal(formatCountdown(59 * MIN), "59m"); assert.equal(formatCountdown(59 * MIN + 1), "1h"); // 59m + a tick ceils to the hour assert.equal(formatCountdown(60 * MIN), "1h"); assert.equal(formatCountdown(60 * MIN + 1), "1h 1m"); assert.equal(formatCountdown(23 * 60 * MIN + 40 * MIN), "23h 40m"); assert.equal(formatCountdown(24 * 60 * MIN), "24h"); }); await test("bare domains glued mid-token stay plain (the lookbehind rule)", () => { // Mid-word, email, and mid-hyphenated-token candidates never link β€” and a // hyphenated host still links whole, from its clean start. assert.ok(!tokenizeText("x_aaa.com").some((t) => t.href)); // Glued after a stray hyphen counts as mid-token too (deliberate: hyphens // are part of the candidate's own charset, so they bound it on both sides). assert.ok(!tokenizeText("see -a.com").some((t) => t.href)); const hy = tokenizeText("try my-site.com now").find((t) => t.href); assert.equal(hy && hy.text, "my-site.com"); assert.equal(roundtrip("x_aaa.com and my-site.com"), "x_aaa.com and my-site.com"); }); await test("tokenizing a huge unbroken paste is linear, not quadratic (the render-freeze regression)", () => { // A pasted key/hash/log line is one long run of word chars. The bare-domain // scan used to make a fresh full-run attempt at every position β€” O(nΒ²), // seconds of locked main thread per render at message-size (~45k chars). // The lookbehind fails mid-run starts in O(1); 256k chars must be instant. // The 2s bound is ~50Γ— the observed linear time and ~1/40th of quadratic β€” // generous for slow CI, unmissable if the regression returns. const runs = ["a".repeat(256000), "a-".repeat(128000), "5".repeat(256000)]; const t0 = performance.now(); for (const s of runs) { tokenizeText(s); richTokens(s); } const ms = performance.now() - t0; assert.ok(ms < 2000, `expected linear-time tokenize, took ${ms.toFixed(0)}ms`); }); console.log(failures === 0 ? "\nALL PASSED" : `\n${failures} FAILED`); process.exit(failures === 0 ? 0 : 1);