test: markdown parser subsystem (58) + custom-emoji readers (32)

Via subagents, probe-verified against real output, no bugs:
- markdown: internal/utils (11), inline/runner (7), inline/parser (21 — bold/
  italic/underline/strike/code/spoiler/link, nesting, precedence, URL lookbehind),
  block/parser (19 — headings/code-fences/quotes/lists/<br>/escapes). Closes the
  biggest coverage hole (core message rendering).
- custom-emoji: PackMetaReader (6), PackImageReader (7), PackImagesReader (4),
  utils equality+makeImagePacks (5), recent-emoji promote/increment/100-cap (10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 14:52:48 -04:00
co-authored by Claude Opus 4.8
parent 160c09e525
commit 230ef8ed7c
9 changed files with 719 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
import { addRecentEmoji, getRecentEmojis, IRecentEmojiContent } from './recent-emoji';
import { AccountDataEvent } from '../../types/matrix/accountData';
import { emojis } from './emoji';
// A Map-backed MatrixClient stub supporting get/setAccountData.
const createMx = () => {
const store = new Map<string, unknown>();
const mx = {
getAccountData: (type: string): MatrixEvent | undefined => {
if (!store.has(type)) return undefined;
const content = store.get(type);
return { getContent: () => content } as unknown as MatrixEvent;
},
setAccountData: (type: string, content: unknown) => {
store.set(type, content);
return Promise.resolve({});
},
};
return { mx: mx as unknown as MatrixClient, store };
};
const getStored = (store: Map<string, unknown>): IRecentEmojiContent['recent_emoji'] =>
(store.get(AccountDataEvent.ElementRecentEmoji) as IRecentEmojiContent | undefined)?.recent_emoji;
// Pick two real unicode emojis to drive add->get round trips.
const u1 = emojis[0].unicode;
const u2 = emojis[1].unicode;
test('getRecentEmojis returns [] when there is no account data', () => {
const { mx } = createMx();
assert.deepEqual(getRecentEmojis(mx), []);
});
test('getRecentEmojis returns [] when content is not an array', () => {
const { mx, store } = createMx();
store.set(AccountDataEvent.ElementRecentEmoji, { recent_emoji: 'nope' });
assert.deepEqual(getRecentEmojis(mx), []);
});
test('addRecentEmoji creates an entry with count 1', () => {
const { mx, store } = createMx();
addRecentEmoji(mx, u1);
assert.deepEqual(getStored(store), [[u1, 1]]);
});
test('adding the same emoji again increments its count and keeps it at front', () => {
const { mx, store } = createMx();
addRecentEmoji(mx, u1);
addRecentEmoji(mx, u1);
assert.deepEqual(getStored(store), [[u1, 2]]);
});
test('a newly added emoji is promoted to the front', () => {
const { mx, store } = createMx();
addRecentEmoji(mx, u1);
addRecentEmoji(mx, u2);
assert.deepEqual(getStored(store), [
[u2, 1],
[u1, 1],
]);
});
test('re-adding an existing emoji promotes it to front and increments', () => {
const { mx, store } = createMx();
addRecentEmoji(mx, u1);
addRecentEmoji(mx, u2);
addRecentEmoji(mx, u1);
assert.deepEqual(getStored(store), [
[u1, 2],
[u2, 1],
]);
});
test('list is capped at 100 entries', () => {
const { mx, store } = createMx();
for (let i = 0; i < 120; i += 1) {
addRecentEmoji(mx, `:emoji-${i}:`);
}
const stored = getStored(store)!;
assert.equal(stored.length, 100);
// most recently added is at the front
assert.equal(stored[0][0], ':emoji-119:');
});
test('getRecentEmojis resolves stored unicodes to known emoji objects', () => {
const { mx } = createMx();
addRecentEmoji(mx, u1);
addRecentEmoji(mx, u2);
const recent = getRecentEmojis(mx);
assert.equal(recent.length, 2);
assert.equal(recent[0].unicode, u2);
assert.equal(recent[1].unicode, u1);
});
test('getRecentEmojis skips unicodes with no matching emoji definition', () => {
const { mx } = createMx();
addRecentEmoji(mx, ':not-a-real-emoji:');
addRecentEmoji(mx, u1);
const recent = getRecentEmojis(mx);
assert.equal(recent.length, 1);
assert.equal(recent[0].unicode, u1);
});
test('getRecentEmojis respects the limit argument (slice before resolve)', () => {
const { mx } = createMx();
addRecentEmoji(mx, u1);
addRecentEmoji(mx, u2);
const recent = getRecentEmojis(mx, 1);
assert.equal(recent.length, 1);
assert.equal(recent[0].unicode, u2);
});