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:
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user