f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 1 | import assert from 'node:assert/strict'; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 2 | import { test } from 'node:test'; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 3 | import { buildGroups, lastOwnSent, lastSenderIs } from '../src/lib/timeline-groups.ts'; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 4 | import type { TimelineEvent, TimelineItem } from '../src/lib/types.ts'; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 5 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 6 | const MINUTE = 60 * 1000; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 7 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 8 | function event(id: string, sender: string, timestamp_ms: number, extra: Partial<TimelineEvent> = {}): TimelineItem { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 9 | return { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 10 | kind: 'event', |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 11 | event: { item_id: id, event_id: id, timestamp_ms, own: false, send_state: { kind: 'Sent' }, sender: { user_id: sender }, ...extra } as TimelineEvent |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 12 | }; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 13 | } |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 14 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 15 | test('a sender run shares one header until a gap or another sender', () => { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 16 | const groups = buildGroups([ |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 17 | event('a', '@x', 0), |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 18 | event('b', '@x', MINUTE), |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 19 | event('c', '@x', 10 * MINUTE), |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 20 | event('d', '@y', 11 * MINUTE), |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 21 | { kind: 'day', timestamp_ms: 12 * MINUTE }, |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 22 | event('e', '@y', 12 * MINUTE) |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 23 | ]); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 24 | assert.deepEqual(groups.map((g) => g.showSender), [true, false, true, true, true, true]); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 25 | }); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 26 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 27 | test('gap rows are keyed by their id so pagination keeps them mounted', () => { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 28 | const gap = { kind: 'historyGap', gap: { id: 'g1', older_boundary: null, newer_boundary: null, lifecycle: 'Idle', continuity: 'Unresolved', failure: null } } as TimelineItem; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 29 | assert.equal(buildGroups([event('a', '@x', 0), gap])[1].key, 'gap-g1'); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 30 | }); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 31 | |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 32 | test('the latest own sent event and the latest sender are found from the end', () => { |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 33 | const list = [event('a', '@me', 0, { own: true }), event('b', '@x', 1), event('c', '@me', 2, { own: true, send_state: { kind: 'Sending' } })]; |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 34 | assert.equal(lastOwnSent(list), 'a'); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 35 | assert.equal(lastSenderIs(list, '@me'), true); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 36 | assert.equal(lastSenderIs([], '@me'), false); |
f93c7e88ea05 | atagen | 2026-10-25 10:54 UTC | 37 | }); |