atagen

malice

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

Blame: web/test/notification-model.test.ts

db4e552594cbatagen2026-10-26 04:43 UTC1
import assert from 'node:assert/strict';
db4e552594cbatagen2026-10-26 04:43 UTC2
import { test } from 'node:test';
db4e552594cbatagen2026-10-26 04:43 UTC3
import {
db4e552594cbatagen2026-10-26 04:43 UTC4
  arrivalIn,
db4e552594cbatagen2026-10-26 04:43 UTC5
  badgeTotal,
db4e552594cbatagen2026-10-26 04:43 UTC6
  isRoomWatched,
db4e552594cbatagen2026-10-26 04:43 UTC7
  notifyingCount,
db4e552594cbatagen2026-10-26 04:43 UTC8
  planNotification,
db4e552594cbatagen2026-10-26 04:43 UTC9
  sourceFromTimeline,
db4e552594cbatagen2026-10-26 04:43 UTC10
  type NotificationSource
db4e552594cbatagen2026-10-26 04:43 UTC11
} from '../src/lib/notification-model.ts';
db4e552594cbatagen2026-10-26 04:43 UTC12
import type { RoomSummary, TimelineItem } from '../src/lib/types.ts';
db4e552594cbatagen2026-10-26 04:43 UTC13
db4e552594cbatagen2026-10-26 04:43 UTC14
const NOW = 1_000_000_000;
db4e552594cbatagen2026-10-26 04:43 UTC15
db4e552594cbatagen2026-10-26 04:43 UTC16
function room(extra: Partial<RoomSummary> = {}): RoomSummary {
db4e552594cbatagen2026-10-26 04:43 UTC17
  return {
db4e552594cbatagen2026-10-26 04:43 UTC18
    room_id: '!r:x',
db4e552594cbatagen2026-10-26 04:43 UTC19
    name: 'General',
db4e552594cbatagen2026-10-26 04:43 UTC20
    membership: 'Joined',
db4e552594cbatagen2026-10-26 04:43 UTC21
    unread_messages: 0,
db4e552594cbatagen2026-10-26 04:43 UTC22
    unread_mentions: 0,
db4e552594cbatagen2026-10-26 04:43 UTC23
    unread_notifications: 0,
db4e552594cbatagen2026-10-26 04:43 UTC24
    space: false,
db4e552594cbatagen2026-10-26 04:43 UTC25
    direct: false,
db4e552594cbatagen2026-10-26 04:43 UTC26
    notification_mode: null,
db4e552594cbatagen2026-10-26 04:43 UTC27
    latest_event_timestamp_ms: NOW - 1000,
db4e552594cbatagen2026-10-26 04:43 UTC28
    avatar: 'mxc://x/room',
db4e552594cbatagen2026-10-26 04:43 UTC29
    ...extra
db4e552594cbatagen2026-10-26 04:43 UTC30
  } as RoomSummary;
db4e552594cbatagen2026-10-26 04:43 UTC31
}
db4e552594cbatagen2026-10-26 04:43 UTC32
db4e552594cbatagen2026-10-26 04:43 UTC33
function source(extra: Partial<NotificationSource> = {}): NotificationSource {
db4e552594cbatagen2026-10-26 04:43 UTC34
  return { senderId: '@bob:x', senderName: 'Bob', senderAvatar: 'mxc://x/bob', kind: 'Text', body: 'hi', undecryptable: false, highlight: false, ...extra };
db4e552594cbatagen2026-10-26 04:43 UTC35
}
db4e552594cbatagen2026-10-26 04:43 UTC36
db4e552594cbatagen2026-10-26 04:43 UTC37
test('counts only notifying unread per mode', () => {
db4e552594cbatagen2026-10-26 04:43 UTC38
  assert.equal(notifyingCount(room({ unread_notifications: 0, unread_messages: 9 })), 9);
db4e552594cbatagen2026-10-26 04:43 UTC39
  assert.equal(notifyingCount(room({ direct: true, unread_messages: 2, unread_mentions: 1 })), 3);
db4e552594cbatagen2026-10-26 04:43 UTC40
  assert.equal(notifyingCount(room({ notification_mode: 'Mute', unread_mentions: 4 })), 0);
db4e552594cbatagen2026-10-26 04:43 UTC41
  assert.equal(notifyingCount(room({ notification_mode: 'MentionsAndKeywordsOnly', unread_notifications: 5, unread_mentions: 1 })), 1);
db4e552594cbatagen2026-10-26 04:43 UTC42
  assert.equal(notifyingCount(room({ space: true, unread_mentions: 1 })), 0);
db4e552594cbatagen2026-10-26 04:43 UTC43
  assert.equal(notifyingCount(room({ membership: 'Invited', unread_mentions: 1 })), 0);
db4e552594cbatagen2026-10-26 04:43 UTC44
});
db4e552594cbatagen2026-10-26 04:43 UTC45
db4e552594cbatagen2026-10-26 04:43 UTC46
test('badge sums notifying counts across rooms', () => {
db4e552594cbatagen2026-10-26 04:43 UTC47
  const rooms = [room({ unread_messages: 2 }), room({ direct: true, unread_messages: 1 }), room({ notification_mode: 'Mute', unread_messages: 9 })];
db4e552594cbatagen2026-10-26 04:43 UTC48
  assert.equal(badgeTotal(rooms), 3);
db4e552594cbatagen2026-10-26 04:43 UTC49
  assert.equal(badgeTotal([]), 0);
db4e552594cbatagen2026-10-26 04:43 UTC50
});
db4e552594cbatagen2026-10-26 04:43 UTC51
db4e552594cbatagen2026-10-26 04:43 UTC52
test('arrival needs a known previous room and a count increase', () => {
db4e552594cbatagen2026-10-26 04:43 UTC53
  const before = room({ unread_messages: 1 });
db4e552594cbatagen2026-10-26 04:43 UTC54
  assert.equal(arrivalIn(undefined, before, NOW), null);
db4e552594cbatagen2026-10-26 04:43 UTC55
  assert.equal(arrivalIn(before, before, NOW), null);
db4e552594cbatagen2026-10-26 04:43 UTC56
  assert.equal(arrivalIn(before, room({ unread_messages: 0 }), NOW), null);
db4e552594cbatagen2026-10-26 04:43 UTC57
  assert.deepEqual(arrivalIn(before, room({ unread_messages: 2 }), NOW), { mention: false });
db4e552594cbatagen2026-10-26 04:43 UTC58
  assert.deepEqual(arrivalIn(before, room({ unread_messages: 2, unread_mentions: 1 }), NOW), { mention: true });
db4e552594cbatagen2026-10-26 04:43 UTC59
});
db4e552594cbatagen2026-10-26 04:43 UTC60
db4e552594cbatagen2026-10-26 04:43 UTC61
test('arrival ignores stale backfill', () => {
db4e552594cbatagen2026-10-26 04:43 UTC62
  const next = room({ unread_messages: 1, latest_event_timestamp_ms: NOW - 10 * 60 * 1000 });
db4e552594cbatagen2026-10-26 04:43 UTC63
  assert.equal(arrivalIn(room(), next, NOW), null);
db4e552594cbatagen2026-10-26 04:43 UTC64
});
db4e552594cbatagen2026-10-26 04:43 UTC65
db4e552594cbatagen2026-10-26 04:43 UTC66
test('only the focused open room is watched', () => {
db4e552594cbatagen2026-10-26 04:43 UTC67
  assert.equal(isRoomWatched('!a', '!a', true, true), true);
db4e552594cbatagen2026-10-26 04:43 UTC68
  assert.equal(isRoomWatched('!a', '!a', false, true), false);
db4e552594cbatagen2026-10-26 04:43 UTC69
  assert.equal(isRoomWatched('!a', '!a', true, false), false);
db4e552594cbatagen2026-10-26 04:43 UTC70
  assert.equal(isRoomWatched('!a', '!b', true, true), false);
db4e552594cbatagen2026-10-26 04:43 UTC71
});
db4e552594cbatagen2026-10-26 04:43 UTC72
db4e552594cbatagen2026-10-26 04:43 UTC73
test('group notification prefixes the sender and tags per room', () => {
db4e552594cbatagen2026-10-26 04:43 UTC74
  const plan = planNotification(room(), source(), { mention: false });
db4e552594cbatagen2026-10-26 04:43 UTC75
  assert.equal(plan.title, 'General');
db4e552594cbatagen2026-10-26 04:43 UTC76
  assert.equal(plan.body, 'Bob: hi');
db4e552594cbatagen2026-10-26 04:43 UTC77
  assert.equal(plan.tag, 'room:!r:x');
db4e552594cbatagen2026-10-26 04:43 UTC78
  assert.equal(plan.renotify, false);
db4e552594cbatagen2026-10-26 04:43 UTC79
  assert.equal(plan.iconMxc, 'mxc://x/room');
db4e552594cbatagen2026-10-26 04:43 UTC80
});
db4e552594cbatagen2026-10-26 04:43 UTC81
db4e552594cbatagen2026-10-26 04:43 UTC82
test('direct notification titles the sender and uses their avatar', () => {
db4e552594cbatagen2026-10-26 04:43 UTC83
  const plan = planNotification(room({ direct: true, name: 'Bob' }), source(), { mention: false });
db4e552594cbatagen2026-10-26 04:43 UTC84
  assert.equal(plan.title, 'Bob');
db4e552594cbatagen2026-10-26 04:43 UTC85
  assert.equal(plan.body, 'hi');
db4e552594cbatagen2026-10-26 04:43 UTC86
  assert.equal(plan.iconMxc, 'mxc://x/bob');
db4e552594cbatagen2026-10-26 04:43 UTC87
});
db4e552594cbatagen2026-10-26 04:43 UTC88
db4e552594cbatagen2026-10-26 04:43 UTC89
test('mentions and keyword highlights renotify', () => {
db4e552594cbatagen2026-10-26 04:43 UTC90
  assert.equal(planNotification(room(), source(), { mention: true }).renotify, true);
db4e552594cbatagen2026-10-26 04:43 UTC91
  assert.equal(planNotification(room(), source({ highlight: true }), { mention: false }).renotify, true);
db4e552594cbatagen2026-10-26 04:43 UTC92
});
db4e552594cbatagen2026-10-26 04:43 UTC93
db4e552594cbatagen2026-10-26 04:43 UTC94
test('undecryptable and missing content get a generic body', () => {
db4e552594cbatagen2026-10-26 04:43 UTC95
  assert.equal(planNotification(room(), source({ undecryptable: true, body: null }), { mention: false }).body, 'Bob: New messages');
db4e552594cbatagen2026-10-26 04:43 UTC96
  assert.equal(planNotification(room({ direct: true }), null, { mention: false }).body, 'New message');
db4e552594cbatagen2026-10-26 04:43 UTC97
});
db4e552594cbatagen2026-10-26 04:43 UTC98
db4e552594cbatagen2026-10-26 04:43 UTC99
test('media and emotes get readable previews', () => {
db4e552594cbatagen2026-10-26 04:43 UTC100
  assert.equal(planNotification(room({ direct: true }), source({ kind: 'Image', body: 'cat.png' }), { mention: false }).body, 'Sent an image');
db4e552594cbatagen2026-10-26 04:43 UTC101
  assert.equal(planNotification(room(), source({ kind: 'Emote', body: 'waves' }), { mention: false }).body, 'Bob waves');
db4e552594cbatagen2026-10-26 04:43 UTC102
});
db4e552594cbatagen2026-10-26 04:43 UTC103
db4e552594cbatagen2026-10-26 04:43 UTC104
test('falls back to the user id local part when no display name is known', () => {
db4e552594cbatagen2026-10-26 04:43 UTC105
  assert.equal(planNotification(room(), source({ senderName: null }), { mention: false }).body, 'bob: hi');
db4e552594cbatagen2026-10-26 04:43 UTC106
});
db4e552594cbatagen2026-10-26 04:43 UTC107
db4e552594cbatagen2026-10-26 04:43 UTC108
test('timeline source skips own newest and non-message items', () => {
db4e552594cbatagen2026-10-26 04:43 UTC109
  const message = (own: boolean, body: string): TimelineItem =>
db4e552594cbatagen2026-10-26 04:43 UTC110
    ({
db4e552594cbatagen2026-10-26 04:43 UTC111
      kind: 'event',
db4e552594cbatagen2026-10-26 04:43 UTC112
      event: {
db4e552594cbatagen2026-10-26 04:43 UTC113
        own,
db4e552594cbatagen2026-10-26 04:43 UTC114
        sender: { user_id: '@bob:x', display_name: 'Bob', avatar: null, name_ambiguous: false },
db4e552594cbatagen2026-10-26 04:43 UTC115
        content: { kind: 'Message', message: { kind: 'Text', body } }
db4e552594cbatagen2026-10-26 04:43 UTC116
      }
db4e552594cbatagen2026-10-26 04:43 UTC117
    }) as TimelineItem;
db4e552594cbatagen2026-10-26 04:43 UTC118
  const membership = { kind: 'event', event: { own: false, sender: { user_id: '@c:x' }, content: { kind: 'RoomCreated' } } } as TimelineItem;
db4e552594cbatagen2026-10-26 04:43 UTC119
  assert.equal(sourceFromTimeline([message(false, 'a'), membership])?.body, 'a');
db4e552594cbatagen2026-10-26 04:43 UTC120
  assert.equal(sourceFromTimeline([message(false, 'a'), message(true, 'b')]), null);
db4e552594cbatagen2026-10-26 04:43 UTC121
  assert.equal(sourceFromTimeline(undefined), null);
db4e552594cbatagen2026-10-26 04:43 UTC122
});