eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// Media, Links & Docs: the pure extractor + in-memory index
2// (web/static/threadmedia.js). Node-run: node test/threadmedia.test.mjs
3import assert from "node:assert/strict";
4import { extractEntries, bucketByMonth, monthLabel, ThreadMediaIndex, filterEntries } from "../web/static/threadmedia.js";
5
6let failures = 0;
7async function test(name, fn) {
8 try {
9 await fn();
10 console.log("PASS " + name);
11 } catch (e) {
12 console.log("FAIL " + name + " — " + (e && e.message));
13 failures++;
14 }
15}
16
17const meta = { id: 42, ts: 1754000000000 };
18
19await test("classifies image, gif, video as media; other files as docs", () => {
20 const out = extractEntries({ uid: "u1", name: "Ada", text: "", ts: 1754000000000, attachments: [
21 { id: "b1", type: "image/jpeg", w: 100, h: 50 },
22 { id: "b2", type: "image/gif" },
23 { id: "b3", type: "video/mp4", poster: "p3", dur: 12 },
24 { id: "b4", type: "application/pdf", name: "notes.pdf", size: 900 },
25 ] }, meta);
26 assert.equal(out.media.length, 3);
27 assert.equal(out.docs.length, 1);
28 assert.equal(out.docs[0].att.name, "notes.pdf");
29 assert.equal(out.media[0].sid, 42);
30});
31
32await test("extracts EVERY link, not just the first", () => {
33 const out = extractEntries({ uid: "u1", name: "Ada", ts: 1, text: "see https://a.example and https://b.example" }, meta);
34 assert.deepEqual(out.links.map((l) => l.url), ["https://a.example", "https://b.example"]);
35});
36
37await test("links inside fenced code blocks don't extract (matches firstLink)", () => {
38 const out = extractEntries({ uid: "u1", name: "A", ts: 1, text: "```\nhttps://code.example\n```\nhttps://real.example" }, meta);
39 assert.equal(out.links.length, 1);
40 assert.equal(out.links[0].url, "https://real.example");
41});
42
43await test("app events, reactions, and entry-less messages return null", () => {
44 assert.equal(extractEntries({ type: "todo.add", text: "https://x.example" }, meta), null);
45 assert.equal(extractEntries({ uid: "u", name: "A", text: "plain words", ts: 1 }, meta), null);
46 assert.equal(extractEntries(null, meta), null);
47});
48
49await test("attachment without a blob id is skipped", () => {
50 assert.equal(extractEntries({ uid: "u", name: "A", text: "", ts: 1, attachments: [{ type: "image/png" }] }, meta), null);
51});
52
53await test("bucketByMonth groups consecutive same-month entries, keeps order", () => {
54 const aug = Date.UTC(2026, 7, 10), jul = Date.UTC(2026, 6, 1);
55 const buckets = bucketByMonth([
56 { sid: 3, ts: aug }, { sid: 2, ts: aug }, { sid: 1, ts: jul },
57 ]);
58 assert.equal(buckets.length, 2);
59 assert.equal(buckets[0].items.length, 2);
60 assert.equal(buckets[0].label, monthLabel(aug));
61 assert.equal(buckets[1].items[0].sid, 1);
62});
63
64await test("index folds rows once, sorts newest-first, filters hidden senders at query time", async () => {
65 const rows = [
66 { id: 1, created_at: 100, blob: "x" },
67 { id: 2, created_at: 200, blob: "y" },
68 { id: 2, created_at: 200, blob: "y" }, // duplicate fold — seen-set dedupes
69 { id: 3, created_at: 300, blob: "z", tombstone_of: 1 }, // tombstone row — skipped
70 ];
71 const payloads = {
72 1: { uid: "muted", name: "M", ts: 100000, text: "", attachments: [{ id: "a", type: "image/png" }] },
73 2: { uid: "ok", name: "O", ts: 200000, text: "", attachments: [{ id: "b", type: "image/png" }] },
74 };
75 const idx = new ThreadMediaIndex(async (row) => payloads[row.id] || null);
76 await idx.ensureThread("t1", rows);
77 await idx.ensureThread("t1", rows); // idempotent
78 assert.deepEqual(idx.tab("t1", "media", { hiddenUids: new Set() }).map((e) => e.sid), [2, 1]);
79 assert.deepEqual(idx.tab("t1", "media", { hiddenUids: new Set(["muted"]) }).map((e) => e.sid), [2]);
80 assert.equal(idx.counts("t1", { hiddenUids: new Set(["muted"]) }).media, 1);
81});
82
83await test("invalidate drops a message's entries and lets it re-fold (edit path)", async () => {
84 const payloads = { 5: { uid: "u", name: "A", ts: 1, text: "https://old.example" } };
85 const idx = new ThreadMediaIndex(async (row) => payloads[row.id]);
86 await idx.ensureThread("t1", [{ id: 5, created_at: 1, blob: "x" }]);
87 assert.equal(idx.tab("t1", "links", { hiddenUids: new Set() })[0].url, "https://old.example");
88 payloads[5] = { uid: "u", name: "A", ts: 1, text: "https://new.example" };
89 idx.invalidate("t1", 5);
90 await idx.ensureThread("t1", [{ id: 5, created_at: 1, blob: "x" }]);
91 assert.deepEqual(idx.tab("t1", "links", { hiddenUids: new Set() }).map((l) => l.url), ["https://new.example"]);
92});
93
94await test("decrypt failure on one row doesn't poison the rest", async () => {
95 const idx = new ThreadMediaIndex(async (row) => {
96 if (row.id === 1) throw new Error("bad key");
97 return { uid: "u", name: "A", ts: 1, text: "", attachments: [{ id: "b", type: "image/png" }] };
98 });
99 await idx.ensureThread("t1", [{ id: 1, created_at: 1, blob: "x" }, { id: 2, created_at: 2, blob: "y" }]);
100 assert.equal(idx.tab("t1", "media", { hiddenUids: new Set() }).length, 1);
101});
102
103await test("filterEntries matches sender, URL, fetched unfurl title, and filename — case-insensitively", () => {
104 const entries = [
105 { sid: 1, name: "Ada", url: "https://example.com/Report" },
106 { sid: 2, name: "Bob", url: "https://news.example/x" },
107 { sid: 3, name: "Cy", att: { id: "d", name: "Budget-2026.xlsx" } },
108 { sid: 4, name: "Ada", att: { id: "p", type: "image/png" } },
109 ];
110 const titles = { "https://news.example/x": "Rain expected Friday" };
111 const titleFor = (u) => titles[u] || "";
112 const ids = (q) => filterEntries(entries, q, titleFor).map((e) => e.sid);
113 assert.deepEqual(ids(""), [1, 2, 3, 4], "blank query = everything");
114 assert.deepEqual(ids(" "), [1, 2, 3, 4]);
115 assert.deepEqual(ids("ada"), [1, 4], "sender name, any tab");
116 assert.deepEqual(ids("REPORT"), [1], "URL substring, case-insensitive");
117 assert.deepEqual(ids("friday"), [2], "an already-fetched unfurl title");
118 assert.deepEqual(ids("budget"), [3], "doc filename");
119 assert.deepEqual(ids("zzz"), []);
120 assert.deepEqual(filterEntries(entries, "friday").map((e) => e.sid), [], "no titleFor → titles don't match");
121});
122
123process.exit(failures ? 1 : 0);
124