check:prettier was not part of my gate routine, so formatting drift accumulated across the session's touched files (and a few older ones). Run prettier --write to bring the repo back to 'All matched files use Prettier code style!'. Formatting only — no logic changes. tsc/tests/build all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
3.5 KiB
TypeScript
90 lines
3.5 KiB
TypeScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { MatrixEvent } from 'matrix-js-sdk';
|
|
import { myMainReceiptPresent, receiptIsMine, setMarkedUnread } from './markedUnread';
|
|
|
|
// MSC2867 mark-as-unread: reading a room (our own receipt) clears the flag, so
|
|
// `receiptIsMine` must detect only OUR receipt and ignore others'. And a write
|
|
// must land on BOTH the stable `m.marked_unread` and the unstable
|
|
// `com.famedly.marked_unread` key so it round-trips across servers/clients.
|
|
|
|
const ME = '@me:server';
|
|
const OTHER = '@friend:server';
|
|
|
|
const receiptEvent = (content: object): MatrixEvent =>
|
|
({ getContent: () => content }) as MatrixEvent;
|
|
|
|
test('receiptIsMine: true when the receipt content carries our user id', () => {
|
|
const event = receiptEvent({
|
|
$abc: { 'm.read': { [ME]: { ts: 1 } } },
|
|
});
|
|
assert.equal(receiptIsMine(event, ME), true);
|
|
});
|
|
|
|
test('receiptIsMine: false when only another user has a receipt', () => {
|
|
const event = receiptEvent({
|
|
$abc: { 'm.read': { [OTHER]: { ts: 1 } } },
|
|
});
|
|
assert.equal(receiptIsMine(event, ME), false);
|
|
});
|
|
|
|
test('receiptIsMine: tolerates empty / malformed content', () => {
|
|
assert.equal(receiptIsMine(receiptEvent({}), ME), false);
|
|
assert.equal(receiptIsMine(receiptEvent({ $x: {} }), ME), false);
|
|
});
|
|
|
|
// myMainReceiptPresent gates the auto-clear to main-timeline reads, so reading a
|
|
// single thread does not wipe the whole-room marked-unread flag.
|
|
test('myMainReceiptPresent: true for an unthreaded receipt (no thread_id)', () => {
|
|
const event = receiptEvent({ $abc: { 'm.read': { [ME]: { ts: 1 } } } });
|
|
assert.equal(myMainReceiptPresent(event, ME), true);
|
|
});
|
|
|
|
test('myMainReceiptPresent: true for a thread_id "main" receipt', () => {
|
|
const event = receiptEvent({ $abc: { 'm.read': { [ME]: { ts: 1, thread_id: 'main' } } } });
|
|
assert.equal(myMainReceiptPresent(event, ME), true);
|
|
});
|
|
|
|
test('myMainReceiptPresent: false for a thread-scoped receipt', () => {
|
|
const event = receiptEvent({
|
|
$abc: { 'm.read': { [ME]: { ts: 1, thread_id: '$root:server' } } },
|
|
});
|
|
assert.equal(myMainReceiptPresent(event, ME), false);
|
|
});
|
|
|
|
test('myMainReceiptPresent: false when only another user has a main receipt', () => {
|
|
const event = receiptEvent({ $abc: { 'm.read': { [OTHER]: { ts: 1 } } } });
|
|
assert.equal(myMainReceiptPresent(event, ME), false);
|
|
});
|
|
|
|
test('setMarkedUnread writes both the stable and unstable keys with the flag', async () => {
|
|
const calls: Array<{ type: string; content: unknown }> = [];
|
|
const mx = {
|
|
setRoomAccountData: (_roomId: string, type: string, content: unknown) => {
|
|
calls.push({ type, content });
|
|
return Promise.resolve();
|
|
},
|
|
} as any;
|
|
|
|
await setMarkedUnread(mx, '!room:server', true);
|
|
|
|
const types = calls.map((c) => c.type).sort();
|
|
assert.deepEqual(types, ['com.famedly.marked_unread', 'm.marked_unread']);
|
|
assert.ok(calls.every((c) => (c.content as { unread: boolean }).unread === true));
|
|
});
|
|
|
|
test('setMarkedUnread(false) clears both keys and does not reject if the unstable write fails', async () => {
|
|
const seen: string[] = [];
|
|
const mx = {
|
|
setRoomAccountData: (_roomId: string, type: string) => {
|
|
seen.push(type);
|
|
// Simulate an older server rejecting the unstable key — must not reject.
|
|
if (type === 'com.famedly.marked_unread') return Promise.reject(new Error('unknown type'));
|
|
return Promise.resolve();
|
|
},
|
|
} as any;
|
|
|
|
await assert.doesNotReject(() => setMarkedUnread(mx, '!room:server', false));
|
|
assert.ok(seen.includes('m.marked_unread'));
|
|
});
|