test: lotus decorations, call caps, crypto, featureCheck, typing, markdown (+34)

Subagent batch (no bugs found) + markdown:
- lotus/avatarDecorations (8): decorationUrl, CDN shape, ALL_DECORATIONS
  flattening, data invariants (unique category ids + slugs, slug charset).
- plugins/call/utils (7): getCallCapabilities — static caps + room/user/device
  scoped state-keys.
- utils/matrix-crypto (3): verifiedDevice via a stubbed CryptoApi.
- utils/featureCheck (3): checkIndexedDBSupport success/error/throw paths.
- state/typingMembers (8): add/dedup-by-latest-ts/per-room-scope/delete reducer
  via a jotai store (enableMapSet, mirroring app startup).
- plugins/markdown/utils (5): inline + block escape/unescape round-trips.

Full suite now 231 tests, all passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 14:32:53 -04:00
parent 9f4516c6a8
commit 6e59395fb8
6 changed files with 385 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { MatrixCapabilities } from 'matrix-widget-api';
import { getCallCapabilities } from './utils';
const ROOM = '!room:server';
const USER = '@user:server';
const DEVICE = 'DEVICE1';
test('getCallCapabilities returns a non-empty Set', () => {
const caps = getCallCapabilities(ROOM, USER, DEVICE);
assert.ok(caps instanceof Set);
assert.ok(caps.size > 0);
});
test('includes the static MatrixCapabilities', () => {
const caps = getCallCapabilities(ROOM, USER, DEVICE);
assert.ok(caps.has(MatrixCapabilities.Screenshots));
assert.ok(caps.has(MatrixCapabilities.AlwaysOnScreen));
assert.ok(caps.has(MatrixCapabilities.MSC4039UploadFile));
assert.ok(caps.has(MatrixCapabilities.MSC4039DownloadFile));
assert.ok(caps.has(MatrixCapabilities.MSC3846TurnServers));
assert.ok(caps.has(MatrixCapabilities.MSC4157SendDelayedEvent));
assert.ok(caps.has(MatrixCapabilities.MSC4157UpdateDelayedEvent));
});
test('includes the room-scoped timeline and state capabilities', () => {
const caps = getCallCapabilities(ROOM, USER, DEVICE);
assert.ok(caps.has(`org.matrix.msc2762.timeline:${ROOM}`));
assert.ok(caps.has(`org.matrix.msc2762.state:${ROOM}`));
});
test('room scoping changes with the roomId', () => {
const a = getCallCapabilities('!a:server', USER, DEVICE);
const b = getCallCapabilities('!b:server', USER, DEVICE);
assert.ok(a.has('org.matrix.msc2762.timeline:!a:server'));
assert.ok(!a.has('org.matrix.msc2762.timeline:!b:server'));
assert.ok(b.has('org.matrix.msc2762.timeline:!b:server'));
});
test('includes send capability for the user-scoped call.member state event', () => {
const caps = getCallCapabilities(ROOM, USER, DEVICE);
const sendStateMember = [...caps].filter(
(c) =>
c.includes('send') && c.includes('state') && c.includes('org.matrix.msc3401.call.member'),
);
// five distinct state-keys are registered for the call.member send capability
assert.equal(sendStateMember.length, 5);
// the raw user id and the underscore-prefixed device-scoped key both appear
const joined = sendStateMember.join('\n');
assert.ok(joined.includes(USER));
assert.ok(joined.includes(`_${USER}_${DEVICE}_m.call`));
});
test('registers both send and receive for each room-event type', () => {
const caps = getCallCapabilities(ROOM, USER, DEVICE);
[
'io.element.call.encryption_keys',
'org.matrix.rageshake_request',
'io.element.call.reaction',
'org.matrix.msc4075.rtc.notification',
'org.matrix.msc4310.rtc.decline',
].forEach((type) => {
const matches = [...caps].filter((c) => c.includes(type));
// one send + one receive room-event capability each
assert.ok(matches.length >= 2, `missing capability for ${type}`);
});
});
test('user/device scoping flows into the device-keyed state keys', () => {
const caps = getCallCapabilities(ROOM, '@bob:srv', 'DEV2');
const all = [...caps].join('\n');
assert.ok(all.includes('@bob:srv_DEV2'));
assert.ok(all.includes('_@bob:srv_DEV2_m.call'));
});
+46
View File
@@ -0,0 +1,46 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import {
escapeMarkdownInlineSequences,
unescapeMarkdownInlineSequences,
escapeMarkdownBlockSequences,
unescapeMarkdownBlockSequences,
} from './utils';
const identity = (t: string): string => t;
test('escapeMarkdownInlineSequences backslash-escapes inline markdown chars', () => {
assert.equal(escapeMarkdownInlineSequences('a*b'), 'a\\*b');
assert.equal(escapeMarkdownInlineSequences('under_score'), 'under\\_score');
// plain text without markdown chars is unchanged
assert.equal(escapeMarkdownInlineSequences('plain text'), 'plain text');
});
test('inline escape/unescape round-trips', () => {
for (const s of ['a*b*c', 'under_score', 'plain', 'mix *a* _b_']) {
assert.equal(unescapeMarkdownInlineSequences(escapeMarkdownInlineSequences(s)), s);
}
});
test('escapeMarkdownBlockSequences escapes leading block markers', () => {
assert.equal(escapeMarkdownBlockSequences('# heading', identity), '\\# heading');
assert.equal(escapeMarkdownBlockSequences('> quote', identity), '\\> quote');
});
test('block unescape passes non-escaped text through processPart', () => {
assert.equal(unescapeMarkdownBlockSequences('plain', identity), 'plain');
// a custom processPart is applied to the (non-escaped) text
assert.equal(
unescapeMarkdownBlockSequences('plain', (t) => t.toUpperCase()),
'PLAIN',
);
});
test('block escape/unescape round-trips', () => {
for (const s of ['# h', '> quote', 'plain line']) {
assert.equal(
unescapeMarkdownBlockSequences(escapeMarkdownBlockSequences(s, identity), identity),
s,
);
}
});