source: Klonkt/test/note-render.test.js@ 7d01696

main
Last change on this file since 7d01696 was 3597763, checked in by Robin Genis <roboburr@…>, 6 weeks ago

Web-timeline: custom-emoji + embedded quote-kaart (afgekeken van Shaer)

De Shaer-apps renderen FEP-9098 custom-emoji's en de FEP-044f quote-kaart
inline; de Klonkt web-timeline toonde nog letterlijke :shortcodes: en geen
quote. Nu rendert de web-nieuwsfeed dezelfde dingen server-side: emoji's in
content, auteur-naam en booster-naam, plus de geneste quote-kaart (avatar +
naam + content + thumbnail). De byline (avatar/naam/handle) en de boost-icon
(SVG, themebaar) had de web-UI al.

De data was al aanwezig op de timeline-rijen (emoji_json, author_emoji_json,
reblog_emoji_json, quote_json); dit is puur de render-kant.

New file:
src/services/NoteRender.js

  • pure helpers: emojiMap (beide vormen), emojiHtml (tag-bewust, skip code/pre), emojiName (escape + emoji), parseQuote

src/views/partials/quote-card.ejs

  • geneste quote-kaart (mirror van de Shaer QuoteCard)

test/note-render.test.js

  • 7 tests voor de render-helpers

Changed files:
src/middleware/render.js

  • emojiHtml/emojiName/noteQuote als template-helpers in de render-locals

src/views/partials/tl-item.ejs

  • content, auteur-naam en booster-naam via de emoji-helpers; quote-kaart erna

src/views/pages/news.ejs

  • CSS: img.emoji (inline, geen media-styling) + .tl-quote* (themebaar)

remarks: 187 tests groen (was 180). Visueel geverifieerd via een standalone
render van tl-item met echte emoji-urls. Volgende surfaces (messages, profiel,
cirkel) kunnen dezelfde helpers hergebruiken.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 2.5 KB
Line 
1// NoteRender: web-side FEP-9098 emoji + FEP-044f quote rendering (mirrors Shaer).
2import { test } from 'node:test';
3import assert from 'node:assert/strict';
4import { emojiMap, emojiHtml, emojiName, parseQuote, escapeHtml } from '../src/services/NoteRender.js';
5
6test('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
15test('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
24test('emojiHtml with no emojis returns the content unchanged', () => {
25 assert.equal(emojiHtml('<p>plain</p>', null), '<p>plain</p>');
26});
27
28test('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('&lt;b&gt;')); // 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
35test('emojiName without emojis just escapes', () => {
36 assert.equal(emojiName('A & B <x>', null), 'A &amp; B &lt;x&gt;');
37});
38
39test('escapeHtml escapes the five entities', () => {
40 assert.equal(escapeHtml(`&<>"'`), '&amp;&lt;&gt;&quot;&#39;');
41});
42
43test('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});
Note: See TracBrowser for help on using the repository browser.