| 1 | // NoteRender: web-side FEP-9098 emoji + FEP-044f quote rendering (mirrors Shaer).
|
|---|
| 2 | import { test } from 'node:test';
|
|---|
| 3 | import assert from 'node:assert/strict';
|
|---|
| 4 | import { emojiMap, emojiHtml, emojiName, parseQuote, escapeHtml } from '../src/services/NoteRender.js';
|
|---|
| 5 |
|
|---|
| 6 | test('emojiMap normalises both the tag-array and the map form', () => {
|
|---|
| 7 | const fromTags = emojiMap(JSON.stringify([{ type: 'Emoji', name: ':wave:', icon: { url: 'https://s/w.png' } }]));
|
|---|
| 8 | assert.equal(fromTags[':wave:'], 'https://s/w.png');
|
|---|
| 9 | const fromMap = emojiMap(JSON.stringify({ ':blob:': 'https://s/b.png' }));
|
|---|
| 10 | assert.equal(fromMap[':blob:'], 'https://s/b.png');
|
|---|
| 11 | assert.deepEqual(emojiMap(null), {});
|
|---|
| 12 | assert.deepEqual(emojiMap('not json'), {});
|
|---|
| 13 | });
|
|---|
| 14 |
|
|---|
| 15 | test('emojiHtml swaps known shortcodes for <img>, leaves tags/attributes/code alone', () => {
|
|---|
| 16 | const html = '<p>hi :wave: <a href="https://x/:wave:">link</a></p><code>:wave:</code>';
|
|---|
| 17 | const out = emojiHtml(html, JSON.stringify({ ':wave:': 'https://s/w.png' }));
|
|---|
| 18 | assert.ok(out.includes('<img class="emoji" src="https://s/w.png"')); // text shortcode replaced
|
|---|
| 19 | assert.ok(out.includes('href="https://x/:wave:"')); // attribute untouched
|
|---|
| 20 | assert.ok(out.includes('<code>:wave:</code>')); // code untouched
|
|---|
| 21 | assert.equal((out.match(/<img /g) || []).length, 1); // only the text one
|
|---|
| 22 | });
|
|---|
| 23 |
|
|---|
| 24 | test('emojiHtml with no emojis returns the content unchanged', () => {
|
|---|
| 25 | assert.equal(emojiHtml('<p>plain</p>', null), '<p>plain</p>');
|
|---|
| 26 | });
|
|---|
| 27 |
|
|---|
| 28 | test('emojiName escapes the name then renders its emoji', () => {
|
|---|
| 29 | const out = emojiName('Laura :bongoCat: <b>', JSON.stringify({ ':bongoCat:': 'https://s/c.png' }));
|
|---|
| 30 | assert.ok(out.includes('<b>')); // HTML-escaped
|
|---|
| 31 | assert.ok(out.includes('<img class="emoji" src="https://s/c.png"'));
|
|---|
| 32 | assert.ok(!out.includes('Laura :bongoCat:')); // literal shortcode consumed (still in alt=)
|
|---|
| 33 | });
|
|---|
| 34 |
|
|---|
| 35 | test('emojiName without emojis just escapes', () => {
|
|---|
| 36 | assert.equal(emojiName('A & B <x>', null), 'A & B <x>');
|
|---|
| 37 | });
|
|---|
| 38 |
|
|---|
| 39 | test('escapeHtml escapes the five entities', () => {
|
|---|
| 40 | assert.equal(escapeHtml(`&<>"'`), '&<>"'');
|
|---|
| 41 | });
|
|---|
| 42 |
|
|---|
| 43 | test('parseQuote returns the object only with a url', () => {
|
|---|
| 44 | assert.equal(parseQuote(JSON.stringify({ url: 'https://s/q', author: { name: 'A' } })).url, 'https://s/q');
|
|---|
| 45 | assert.equal(parseQuote(JSON.stringify({ author: { name: 'A' } })), null); // no url
|
|---|
| 46 | assert.equal(parseQuote(null), null);
|
|---|
| 47 | assert.equal(parseQuote('nope'), null);
|
|---|
| 48 | });
|
|---|