- emojibase (~965 KB) is now fully lazy: plugins/emoji.ts loads compact data + shortcode maps via a memoized dynamic import (rejections reset the memo so a mid-deploy chunk 404 can retry); reaction labels degrade to the raw glyph until loaded. Consumers get FRESH array references on load (the module arrays populate in place — same-ref state updates would skip re-render and leave emoji search empty; reviewer-caught). Verified out of the eager graph. - Service worker precaches hashed assets (workbox precacheAndRoute, 82 entries ~10.8 MB incl. the crypto wasm): repeat visits stop re-downloading the app. index.html is NOT precached — navigations stay network-first so deploys are picked up immediately; the media-auth fetch handler is untouched. - ReactPrism: curated 21-language set — chunk 574 KB → 71 KB. - Timeline inline images get loading="lazy". - Removed dead dompurify (+types); sanitize-html is the real sanitizer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
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, loadEmojiData } 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;
|
|
|
|
// Emoji data is now loaded lazily; populate `emojis` before the round trips.
|
|
await loadEmojiData();
|
|
|
|
// 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);
|
|
});
|