// Media, Links & Docs: the pure extractor + in-memory index // (web/static/threadmedia.js). Node-run: node test/threadmedia.test.mjs import assert from "node:assert/strict"; import { extractEntries, bucketByMonth, monthLabel, ThreadMediaIndex, filterEntries } from "../web/static/threadmedia.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++; } } const meta = { id: 42, ts: 1754000000000 }; await test("classifies image, gif, video as media; other files as docs", () => { const out = extractEntries({ uid: "u1", name: "Ada", text: "", ts: 1754000000000, attachments: [ { id: "b1", type: "image/jpeg", w: 100, h: 50 }, { id: "b2", type: "image/gif" }, { id: "b3", type: "video/mp4", poster: "p3", dur: 12 }, { id: "b4", type: "application/pdf", name: "notes.pdf", size: 900 }, ] }, meta); assert.equal(out.media.length, 3); assert.equal(out.docs.length, 1); assert.equal(out.docs[0].att.name, "notes.pdf"); assert.equal(out.media[0].sid, 42); }); await test("extracts EVERY link, not just the first", () => { const out = extractEntries({ uid: "u1", name: "Ada", ts: 1, text: "see https://a.example and https://b.example" }, meta); assert.deepEqual(out.links.map((l) => l.url), ["https://a.example", "https://b.example"]); }); await test("links inside fenced code blocks don't extract (matches firstLink)", () => { const out = extractEntries({ uid: "u1", name: "A", ts: 1, text: "```\nhttps://code.example\n```\nhttps://real.example" }, meta); assert.equal(out.links.length, 1); assert.equal(out.links[0].url, "https://real.example"); }); await test("app events, reactions, and entry-less messages return null", () => { assert.equal(extractEntries({ type: "todo.add", text: "https://x.example" }, meta), null); assert.equal(extractEntries({ uid: "u", name: "A", text: "plain words", ts: 1 }, meta), null); assert.equal(extractEntries(null, meta), null); }); await test("attachment without a blob id is skipped", () => { assert.equal(extractEntries({ uid: "u", name: "A", text: "", ts: 1, attachments: [{ type: "image/png" }] }, meta), null); }); await test("bucketByMonth groups consecutive same-month entries, keeps order", () => { const aug = Date.UTC(2026, 7, 10), jul = Date.UTC(2026, 6, 1); const buckets = bucketByMonth([ { sid: 3, ts: aug }, { sid: 2, ts: aug }, { sid: 1, ts: jul }, ]); assert.equal(buckets.length, 2); assert.equal(buckets[0].items.length, 2); assert.equal(buckets[0].label, monthLabel(aug)); assert.equal(buckets[1].items[0].sid, 1); }); await test("index folds rows once, sorts newest-first, filters hidden senders at query time", async () => { const rows = [ { id: 1, created_at: 100, blob: "x" }, { id: 2, created_at: 200, blob: "y" }, { id: 2, created_at: 200, blob: "y" }, // duplicate fold — seen-set dedupes { id: 3, created_at: 300, blob: "z", tombstone_of: 1 }, // tombstone row — skipped ]; const payloads = { 1: { uid: "muted", name: "M", ts: 100000, text: "", attachments: [{ id: "a", type: "image/png" }] }, 2: { uid: "ok", name: "O", ts: 200000, text: "", attachments: [{ id: "b", type: "image/png" }] }, }; const idx = new ThreadMediaIndex(async (row) => payloads[row.id] || null); await idx.ensureThread("t1", rows); await idx.ensureThread("t1", rows); // idempotent assert.deepEqual(idx.tab("t1", "media", { hiddenUids: new Set() }).map((e) => e.sid), [2, 1]); assert.deepEqual(idx.tab("t1", "media", { hiddenUids: new Set(["muted"]) }).map((e) => e.sid), [2]); assert.equal(idx.counts("t1", { hiddenUids: new Set(["muted"]) }).media, 1); }); await test("invalidate drops a message's entries and lets it re-fold (edit path)", async () => { const payloads = { 5: { uid: "u", name: "A", ts: 1, text: "https://old.example" } }; const idx = new ThreadMediaIndex(async (row) => payloads[row.id]); await idx.ensureThread("t1", [{ id: 5, created_at: 1, blob: "x" }]); assert.equal(idx.tab("t1", "links", { hiddenUids: new Set() })[0].url, "https://old.example"); payloads[5] = { uid: "u", name: "A", ts: 1, text: "https://new.example" }; idx.invalidate("t1", 5); await idx.ensureThread("t1", [{ id: 5, created_at: 1, blob: "x" }]); assert.deepEqual(idx.tab("t1", "links", { hiddenUids: new Set() }).map((l) => l.url), ["https://new.example"]); }); await test("decrypt failure on one row doesn't poison the rest", async () => { const idx = new ThreadMediaIndex(async (row) => { if (row.id === 1) throw new Error("bad key"); return { uid: "u", name: "A", ts: 1, text: "", attachments: [{ id: "b", type: "image/png" }] }; }); await idx.ensureThread("t1", [{ id: 1, created_at: 1, blob: "x" }, { id: 2, created_at: 2, blob: "y" }]); assert.equal(idx.tab("t1", "media", { hiddenUids: new Set() }).length, 1); }); await test("filterEntries matches sender, URL, fetched unfurl title, and filename — case-insensitively", () => { const entries = [ { sid: 1, name: "Ada", url: "https://example.com/Report" }, { sid: 2, name: "Bob", url: "https://news.example/x" }, { sid: 3, name: "Cy", att: { id: "d", name: "Budget-2026.xlsx" } }, { sid: 4, name: "Ada", att: { id: "p", type: "image/png" } }, ]; const titles = { "https://news.example/x": "Rain expected Friday" }; const titleFor = (u) => titles[u] || ""; const ids = (q) => filterEntries(entries, q, titleFor).map((e) => e.sid); assert.deepEqual(ids(""), [1, 2, 3, 4], "blank query = everything"); assert.deepEqual(ids(" "), [1, 2, 3, 4]); assert.deepEqual(ids("ada"), [1, 4], "sender name, any tab"); assert.deepEqual(ids("REPORT"), [1], "URL substring, case-insensitive"); assert.deepEqual(ids("friday"), [2], "an already-fetched unfurl title"); assert.deepEqual(ids("budget"), [3], "doc filename"); assert.deepEqual(ids("zzz"), []); assert.deepEqual(filterEntries(entries, "friday").map((e) => e.sid), [], "no titleFor → titles don't match"); }); process.exit(failures ? 1 : 0);