| 1 | import assert from 'node:assert/strict'; |
| 2 | import { test } from 'node:test'; |
| 3 | import { contextWithProfiles, roomsLackingProfiles, withProfiles } from '../src/lib/mention-profiles.ts'; |
| 4 | import type { MemberSummary, Sender, TimelineItem } from '../src/lib/types.ts'; |
| 5 | |
| 6 | const bare = (user_id: string): Sender => ({ user_id, display_name: null, avatar: null, name_ambiguous: false }); |
| 7 | |
| 8 | function message(sender: Sender, replySender: Sender | null = null): TimelineItem { |
| 9 | return { |
| 10 | kind: 'event', |
| 11 | event: { |
| 12 | item_id: `i-${sender.user_id}`, |
| 13 | event_id: `$e-${sender.user_id}`, |
| 14 | transaction_id: null, |
| 15 | sender, |
| 16 | timestamp_ms: 1, |
| 17 | own: false, |
| 18 | send_state: { kind: 'Sent' }, |
| 19 | content: { kind: 'Redacted' }, |
| 20 | reply_to: replySender ? { event_id: '$target', details: { kind: 'Ready', sender: replySender, content: { kind: 'Redacted' }, timestamp_ms: 0 } } : null, |
| 21 | thread_root: null, |
| 22 | thread_summary: null, |
| 23 | reactions: [], |
| 24 | read_by: [], |
| 25 | editable: false |
| 26 | } |
| 27 | }; |
| 28 | } |
| 29 | |
| 30 | function member(user_id: string, display_name: string | null, avatar: string | null = null): MemberSummary { |
| 31 | return { user_id, name: display_name ?? user_id, display_name, avatar, membership: 'Join', own: false, name_ambiguous: false, ignored: false, service: false }; |
| 32 | } |
| 33 | |
| 34 | const members = { '@carol:x': member('@carol:x', 'Carol', 'mxc://x/c'), '@dave:x': member('@dave:x', 'Dave') }; |
| 35 | |
| 36 | test('lists only rooms with a sender or reply sender missing both name and avatar', () => { |
| 37 | const named = { ...bare('@carol:x'), display_name: 'Carol' }; |
| 38 | const rooms = roomsLackingProfiles([ |
| 39 | { roomId: '!a:x', item: message(named) }, |
| 40 | { roomId: '!b:x', item: message(bare('@carol:x')) }, |
| 41 | { roomId: '!c:x', item: message(named, bare('@dave:x')) }, |
| 42 | { roomId: '!d:x', item: { kind: 'readMarker' } } |
| 43 | ]); |
| 44 | assert.deepEqual([...rooms].sort(), ['!b:x', '!c:x']); |
| 45 | }); |
| 46 | |
| 47 | test('fills the sender and reply sender from the room members', () => { |
| 48 | const filled = withProfiles(message(bare('@carol:x'), bare('@dave:x')), members); |
| 49 | assert.equal(filled.kind, 'event'); |
| 50 | if (filled.kind !== 'event') return; |
| 51 | assert.equal(filled.event.sender.display_name, 'Carol'); |
| 52 | assert.equal(filled.event.sender.avatar, 'mxc://x/c'); |
| 53 | assert.equal(filled.event.reply_to?.details.kind === 'Ready' && filled.event.reply_to.details.sender.display_name, 'Dave'); |
| 54 | }); |
| 55 | |
| 56 | test('keeps an engine profile and the same item when nothing changes', () => { |
| 57 | const named = message({ ...bare('@carol:x'), display_name: 'Carol from the engine' }); |
| 58 | assert.equal(withProfiles(named, members), named); |
| 59 | const unknown = message(bare('@erin:x')); |
| 60 | assert.equal(withProfiles(unknown, members), unknown); |
| 61 | const context = { events_before: [unknown], event: named, events_after: [], start_token: null, end_token: null }; |
| 62 | assert.equal(contextWithProfiles(context, undefined), context); |
| 63 | }); |