source: Klonkt/test/ap-note-track-links.test.js@ 861c228

main
Last change on this file since 861c228 was e62eee4, checked in by Robin <roboburr@…>, 5 weeks ago

Elke chip in het lijf krijgt zijn eigen URL (shaer-38y)

Een lezer kreeg een vetgedrukte opsomming van titels met een enkele "listen
on"-link naar de post als geheel. Vijf namen waar je niets mee kon. Elke track
heeft op de postpagina al een anker -- #track-<id>, of hij nu direct is
ingesloten, in een album zit of in een playlist -- dus elke naam kan naar
precies het nummer wijzen waar hij bij hoort.

De opsomming loopt nu in EEN scan over alle drie de vormen, in de volgorde van
de post. Dat repareert er meteen iets bij: een playlist-insluiting leverde
helemaal geen namen op, want alleen track en album werden gelezen. Een
albumnaam zonder tracks blijft gewone tekst -- geen link naar een anker dat er
niet is.

De listen-link naar de pagina als geheel blijft staan; die nodigt uit naar de
speler, en dat is iets anders dan naar een nummer springen.

DIT LOST ZICHZELF NIET OP VIA DE SELF-HEAL. Die heelt ap_timeline -- de cache
van notes die wij ONTVANGEN -- en niet wat wij versturen. Onze eigen Notes
worden bij elk verzoek vers uit posts.content gebouwd, dus wie hem nu ophaalt
krijgt meteen de nieuwe vorm. Alleen kopieen die elders al gecachet zijn blijven
oud tot er een Update langskomt, en daar reikt geen self-heal bij.

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

  • Property mode set to 100644
File size: 3.7 KB
Line 
1// De chips in het lijf van een Note krijgen een eigen URL (shaer-38y).
2//
3// Eerder was dit een vetgedrukte opsomming van titels met een enkele "listen
4// on"-link naar de post als geheel: vijf namen waar een lezer niets mee kon.
5// Elke track heeft op de postpagina al een anker (#track-<id>) -- direct
6// ingesloten, in een album of in een playlist -- dus elke naam kan naar precies
7// het nummer wijzen waar hij bij hoort.
8import { test } from 'node:test';
9import assert from 'node:assert/strict';
10
11process.env.DATABASE_PATH = ':memory:';
12process.env.PUBLIC_BASE_URL = 'https://test.example';
13
14const dbMod = await import('../src/config/database.js');
15const db = dbMod.default;
16dbMod.initializeDatabase();
17const AP = (await import('../src/services/ActivityPubService.js')).default;
18
19const BASE = 'https://test.example';
20
21db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
22 .run('u1', 'robin', 'r@t', 'x', 'god');
23db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)')
24 .run('s1', 'band', 'De Band', 'u1');
25
26const insM = db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1000)');
27const insT = db.prepare('INSERT INTO audio_tracks (id, site_id, title, album, duration, media_id, fedi_open) VALUES (?,?,?,?,?,?,?)');
28insM.run('m1', 's1', 'een.mp3', 'audio/een.mp3', 'audio/mpeg');
29insM.run('m2', 's1', 'twee.mp3', 'audio/twee.mp3', 'audio/mpeg');
30insM.run('m3', 's1', 'drie.mp3', 'audio/drie.mp3', 'audio/mpeg');
31insT.run('t1', 's1', 'Eerste', 'De Plaat', 200, 'm1', 1);
32insT.run('t2', 's1', 'Tweede', 'De Plaat', 180, 'm2', 1);
33insT.run('t3', 's1', 'Derde', null, 160, 'm3', 1);
34
35db.prepare("INSERT INTO playlists (id, site_id, title, kind) VALUES ('lijst','s1','De Lijst','playlist')").run();
36db.prepare("INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES ('lijst','t3',0)").run();
37
38const site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
39const note = (content, id = 'p1') =>
40 AP.buildNote(BASE, site, { id, slug: id, title: 'T', content, status: 'published' });
41
42test('een losse track wordt een link naar zijn eigen anker', () => {
43 const c = note('[[track:t1]]').content;
44 assert.ok(c.includes(`<a href="${BASE}/p1#track-t1">Eerste</a>`), c.slice(-300));
45});
46
47test('meerdere tracks: elk zijn eigen link, in de volgorde van de post', () => {
48 const c = note('[[track:t2]] en [[track:t1]]', 'p2').content;
49 const namen = [...c.matchAll(/#track-(t\d)">([^<]+)</g)].map((m) => [m[1], m[2]]);
50 assert.deepEqual(namen, [['t2', 'Tweede'], ['t1', 'Eerste']]);
51});
52
53test('een album-insluiting levert links per track, niet een albumnaam', () => {
54 const c = note('[[album:De Plaat]]', 'p3').content;
55 assert.ok(c.includes(`#track-t1">Eerste</a>`));
56 assert.ok(c.includes(`#track-t2">Tweede</a>`));
57});
58
59test('een playlist ook -- die gaf eerder helemaal geen namen', () => {
60 const c = note('[[playlist:lijst]]', 'p4').content;
61 assert.ok(c.includes(`#track-t3">Derde</a>`), 'de tracks van de lijst staan er nu bij');
62});
63
64test('een albumnaam zonder tracks blijft gewone tekst', () => {
65 const c = note('[[album:Bestaat Niet]]', 'p5').content;
66 assert.ok(c.includes('Bestaat Niet'));
67 assert.ok(!/#track-[^"]*">Bestaat Niet/.test(c), 'geen link naar een anker dat er niet is');
68});
69
70test('dezelfde track twee keer levert een link, niet twee', () => {
71 const c = note('[[track:t1]] en nog eens [[track:t1]]', 'p6').content;
72 assert.equal([...c.matchAll(/#track-t1"/g)].length, 1);
73});
74
75test('en de listen-link naar de post zelf blijft staan', () => {
76 const c = note('[[track:t1]]', 'p7').content;
77 assert.ok(/listen on/.test(c), 'de uitnodiging naar de pagina als geheel verdwijnt niet');
78});
Note: See TracBrowser for help on using the repository browser.