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