7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 1 | import assert from 'node:assert/strict'; |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 2 | import { test } from 'node:test'; |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 3 | import { roomThreads, threadMatches } from '../src/lib/thread-index.ts'; |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 4 | import type { TimelineEvent, TimelineItem } from '../src/lib/types.ts'; |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 5 | |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 6 | function root(id: string, body: string, replies: number, latest: number, extra: Partial<TimelineEvent> = {}): TimelineItem { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 7 | return { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 8 | kind: 'event', |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 9 | event: { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 10 | item_id: id, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 11 | event_id: id, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 12 | timestamp_ms: 1, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 13 | sender: { user_id: '@a:x', display_name: 'Alice', avatar: null, name_ambiguous: false }, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 14 | content: { kind: 'Message', message: { kind: 'Text', body, media: null } }, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 15 | thread_root: null, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 16 | thread_summary: replies ? { reply_count: replies, latest_event_id: null, latest_event_timestamp_ms: latest } : null, |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 17 | ...extra |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 18 | } as TimelineEvent |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 19 | }; |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 20 | } |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 21 | |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 22 | test('threads sort by latest activity with pins first', () => { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 23 | const items = [root('$a', 'old thread', 2, 100), root('$b', 'new thread', 1, 300), root('$c', 'no replies', 0, 0), root('$d', 'pinned', 4, 50)]; |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 24 | const list = roomThreads('!r', items, ['$d']); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 25 | assert.deepEqual(list.map((t) => t.root_event_id), ['$d', '$b', '$a']); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 26 | assert.equal(list[0].pinned, true); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 27 | }); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 28 | |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 29 | test('pinned threads outside the loaded timeline still get a row', () => { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 30 | const list = roomThreads('!r', [], ['$gone']); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 31 | assert.equal(list.length, 1); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 32 | assert.equal(list[0].root_event_id, '$gone'); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 33 | assert.equal(list[0].preview, ''); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 34 | }); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 35 | |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 36 | test('replies inside a thread are not treated as roots', () => { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 37 | const reply = root('$r', 'reply', 3, 10, { thread_root: '$a' }); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 38 | assert.equal(roomThreads('!r', [reply], []).length, 0); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 39 | }); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 40 | |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 41 | test('search matches preview text and sender', () => { |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 42 | const [entry] = roomThreads('!r', [root('$a', 'Deploy plan for Friday', 2, 10)], []); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 43 | assert.ok(threadMatches(entry, 'deploy')); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 44 | assert.ok(threadMatches(entry, 'alice')); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 45 | assert.equal(threadMatches(entry, 'lunch'), false); |
7ebc2472a9e3 | atagen | 2026-10-25 09:53 UTC | 46 | }); |