atagen

malice

public
Star0
Watch0
7fdac85c2f804757f4fb01062f967251b5fff4b6
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/test/thread-index.test.ts

7ebc2472a9e3atagen2026-10-25 09:53 UTC1
import assert from 'node:assert/strict';
7ebc2472a9e3atagen2026-10-25 09:53 UTC2
import { test } from 'node:test';
7ebc2472a9e3atagen2026-10-25 09:53 UTC3
import { roomThreads, threadMatches } from '../src/lib/thread-index.ts';
7ebc2472a9e3atagen2026-10-25 09:53 UTC4
import type { TimelineEvent, TimelineItem } from '../src/lib/types.ts';
7ebc2472a9e3atagen2026-10-25 09:53 UTC5
7ebc2472a9e3atagen2026-10-25 09:53 UTC6
function root(id: string, body: string, replies: number, latest: number, extra: Partial<TimelineEvent> = {}): TimelineItem {
7ebc2472a9e3atagen2026-10-25 09:53 UTC7
  return {
7ebc2472a9e3atagen2026-10-25 09:53 UTC8
    kind: 'event',
7ebc2472a9e3atagen2026-10-25 09:53 UTC9
    event: {
7ebc2472a9e3atagen2026-10-25 09:53 UTC10
      item_id: id,
7ebc2472a9e3atagen2026-10-25 09:53 UTC11
      event_id: id,
7ebc2472a9e3atagen2026-10-25 09:53 UTC12
      timestamp_ms: 1,
7ebc2472a9e3atagen2026-10-25 09:53 UTC13
      sender: { user_id: '@a:x', display_name: 'Alice', avatar: null, name_ambiguous: false },
7ebc2472a9e3atagen2026-10-25 09:53 UTC14
      content: { kind: 'Message', message: { kind: 'Text', body, media: null } },
7ebc2472a9e3atagen2026-10-25 09:53 UTC15
      thread_root: null,
7ebc2472a9e3atagen2026-10-25 09:53 UTC16
      thread_summary: replies ? { reply_count: replies, latest_event_id: null, latest_event_timestamp_ms: latest } : null,
7ebc2472a9e3atagen2026-10-25 09:53 UTC17
      ...extra
7ebc2472a9e3atagen2026-10-25 09:53 UTC18
    } as TimelineEvent
7ebc2472a9e3atagen2026-10-25 09:53 UTC19
  };
7ebc2472a9e3atagen2026-10-25 09:53 UTC20
}
7ebc2472a9e3atagen2026-10-25 09:53 UTC21
7ebc2472a9e3atagen2026-10-25 09:53 UTC22
test('threads sort by latest activity with pins first', () => {
7ebc2472a9e3atagen2026-10-25 09:53 UTC23
  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)];
7ebc2472a9e3atagen2026-10-25 09:53 UTC24
  const list = roomThreads('!r', items, ['$d']);
7ebc2472a9e3atagen2026-10-25 09:53 UTC25
  assert.deepEqual(list.map((t) => t.root_event_id), ['$d', '$b', '$a']);
7ebc2472a9e3atagen2026-10-25 09:53 UTC26
  assert.equal(list[0].pinned, true);
7ebc2472a9e3atagen2026-10-25 09:53 UTC27
});
7ebc2472a9e3atagen2026-10-25 09:53 UTC28
7ebc2472a9e3atagen2026-10-25 09:53 UTC29
test('pinned threads outside the loaded timeline still get a row', () => {
7ebc2472a9e3atagen2026-10-25 09:53 UTC30
  const list = roomThreads('!r', [], ['$gone']);
7ebc2472a9e3atagen2026-10-25 09:53 UTC31
  assert.equal(list.length, 1);
7ebc2472a9e3atagen2026-10-25 09:53 UTC32
  assert.equal(list[0].root_event_id, '$gone');
7ebc2472a9e3atagen2026-10-25 09:53 UTC33
  assert.equal(list[0].preview, '');
7ebc2472a9e3atagen2026-10-25 09:53 UTC34
});
7ebc2472a9e3atagen2026-10-25 09:53 UTC35
7ebc2472a9e3atagen2026-10-25 09:53 UTC36
test('replies inside a thread are not treated as roots', () => {
7ebc2472a9e3atagen2026-10-25 09:53 UTC37
  const reply = root('$r', 'reply', 3, 10, { thread_root: '$a' });
7ebc2472a9e3atagen2026-10-25 09:53 UTC38
  assert.equal(roomThreads('!r', [reply], []).length, 0);
7ebc2472a9e3atagen2026-10-25 09:53 UTC39
});
7ebc2472a9e3atagen2026-10-25 09:53 UTC40
7ebc2472a9e3atagen2026-10-25 09:53 UTC41
test('search matches preview text and sender', () => {
7ebc2472a9e3atagen2026-10-25 09:53 UTC42
  const [entry] = roomThreads('!r', [root('$a', 'Deploy plan for Friday', 2, 10)], []);
7ebc2472a9e3atagen2026-10-25 09:53 UTC43
  assert.ok(threadMatches(entry, 'deploy'));
7ebc2472a9e3atagen2026-10-25 09:53 UTC44
  assert.ok(threadMatches(entry, 'alice'));
7ebc2472a9e3atagen2026-10-25 09:53 UTC45
  assert.equal(threadMatches(entry, 'lunch'), false);
7ebc2472a9e3atagen2026-10-25 09:53 UTC46
});