Changeset 9eb6e1b in Klonkt for test


Ignore:
Timestamp:
09/30/2026 06:41:45 AM (4 hours ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
86cfa1a
Parents:
3ceaef6
Message:

Een fediverse-post ziet eruit als een post, ook in de zoekbalk

Plak je een link in de zoekbalk, dan laadt de uitklaplijst nu de post zelf
in plaats van te zoeken naar de tekst van de link. Dezelfde kaart staat op de
zoekpagina en op /authorize_interaction, dus één render op drie plekken.

DE KAART. Wat eromheen stond is teruggebracht tot een kop met gezicht, de
inhoud, en één rij knoppen: Reageer, Like, Boost en het origineel. Weg zijn de
zin "je reageert op", de aparte knop naar het origineel, de twee grote pillen,
"of reageer:" en het antwoordvenster dat altijd openstond. Antwoorden klapt
open als je erom vraagt; melden staat onderaan en niet in de weg.

Foto's staan in een raster met vaste rijhoogtes. Met verhoudingen groeide het
mee met de breedte, en werd op een desktop de eerste van drie foto's zo hoog
dat de knoppen onder de vouw van de uitklaplijst verdwenen.

EEN INHOUDSWAARSCHUWING WORDT GERESPECTEERD. De oude render toonde alles open,
ook wat de schrijver achter een waarschuwing had gezet.

MOBIEL. Elke knop minstens 44 punten. Op een smal scherm vallen de woorden weg
en blijven de tekens staan, gelijk verdeeld; de woorden blijven bestaan voor
wie het scherm voorgelezen krijgt. Nagemeten op 375 pixels: geen horizontale
overloop, kleinste tikdoel 44, de knoppen in beeld zonder te scrollen.

DE PREVIEW IS COMPACT. Waarderen en boosten kan ter plekke; antwoorden en
melden gaan naar de volle pagina, met ?reply=1 of ?report=1 zodat dat deel
daar al openstaat. Een editor in een uitklaplijst is op een telefoon geen plek
om te schrijven. Zonder JS doet dezelfde link hetzelfde.

/search/remote levert het fragment, achter dezelfde grens als de volle
pagina: alleen wie de site beheert laat ophalen. Die grens weegt hier zwaarder,
want deze route wordt bij elke toetsaanslag geraakt; zonder hem is hij een
haalservice die iedereen deze server op adressen naar keuze laat afsturen.
Voor een bezoeker 204, en dan valt de zoekbalk terug op de gewone suggesties.
Elke toetsaanslag krijgt een volgnummer, zodat een traag antwoord op een oude
vraag niet overschrijft wat je inmiddels hebt getypt.

Waarderen en boosten ter plekke verhuisde van authorize-interaction.js naar
chrome.js: de kaart staat nu op elke pagina, via de zoekbalk. De zoekpagina
stuurde je bij een like eerder weg naar /authorize_interaction.

Onderweg: /authorize_interaction haalt nu ook ondertekend op, net als de
zoekbalk. Anders zegt die pagina "niet gevonden" over een post voor volgers die
de preview wel liet zien.

Getoetst: de preview met de post, ondertekend; wat er in de compacte kaart wel
en niet staat; dat een bezoeker via de preview niets laat ophalen; dat een
zoekterm niets ophaalt; de waarschuwing; het venster op verzoek. Tegenproef:
rechtencheck eruit, waarschuwing genegeerd, compact genegeerd en anoniem
ophalen laten elk hun eigen toets omvallen. Volledige suite 1283 groen.

Het gedrag in chrome.js is in de browser nagelopen met een nagebootste
server: herkennen, laden, invoegen, de volgorde bij een traag antwoord, de
terugval bij 204, waarderen en boosten heen en terug, en het antwoordvenster
open en dicht met de focus op de goede plek.

shaer-utpi

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • test/search-fediverse.test.js

    r3ceaef6 r9eb6e1b  
    3737// teller: "is er überhaupt opgehaald" is de helft van wat deze toets bewaakt.
    3838let gevraagd = [];
    39 AP.resolveRemoteNote = async (uri) => {
     39let opties = [];
     40AP.resolveRemoteNote = async (uri, opts) => {
    4041  gevraagd.push(['note', uri]);
     42  opties.push(opts || {});
    4143  if (!uri.includes('/notes/')) return null;
    4244  return {
    … …  
    4547    actor_handle: '@oma@elders.test', actor_name: 'Oma', actor_icon: '',
    4648    content: '<p>Een bericht van ver weg</p>', images: [], media: [], poll: null,
    47     preview: 'Een bericht van ver weg', threadInboxes: [], localPostId: '', sensitive: false, cw: '',
     49    preview: 'Een bericht van ver weg', threadInboxes: [], localPostId: '',
     50    sensitive: uri.includes('/cw'), cw: uri.includes('/cw') ? 'Spinnen' : '',
    4851  };
    4952};
    … …  
    176179  // En de opmaak reist mee: die staat sinds deze ronde in een eigen partial,
    177180  // en een render zonder opmaak ziet er kapot uit zonder dat er iets faalt.
    178   assert.ok(html.includes('.auth-interact-preview'), 'de opmaak van het blok ontbreekt');
     181  assert.ok(html.includes('.rp-card'), 'de opmaak van de kaart ontbreekt');
    179182});
    180183
    … …  
    188191  assert.ok(html.includes('@oma@elders.test'));
    189192});
     193
     194// ── De live-preview onder de zoekbalk ───────────────────────────────────────
     195//
     196// Plak je een link, dan laadt de uitklaplijst de post zelf. Deze route wordt bij
     197// elke toetsaanslag geraakt, en dat maakt de rechtencheck hier zwaarder dan op
     198// de pagina: zonder hem is dit een haalservice waarmee iedereen deze server op
     199// adressen naar keuze laat afsturen.
     200
     201const preview = async (q) => {
     202  gevraagd = []; opties = [];
     203  const r = await fetch(`http://127.0.0.1:${poort}/search/remote?q=${encodeURIComponent(q)}`, { signal: AbortSignal.timeout(10000) });
     204  return { status: r.status, html: await r.text(), cache: r.headers.get('cache-control') };
     205};
     206
     207test('de preview laadt de post zelf, ondertekend als de site', async () => {
     208  const { status, html, cache } = await preview('https://elders.test/notes/1');
     209  assert.equal(status, 200);
     210  assert.ok(html.includes('Een bericht van ver weg'), 'de inhoud hoort in de preview te staan');
     211  assert.ok(html.includes('@oma@elders.test'));
     212  assert.equal(opties[0].asSlug, 'robo', 'een post voor volgers weigert een anonieme GET');
     213  // Wat iemand ziet hangt van zijn rechten en reacties af.
     214  assert.match(cache || '', /no-store/);
     215});
     216
     217test('in de preview kun je waarderen en boosten, antwoorden gaat naar de volle pagina', async () => {
     218  const { html } = await preview('https://elders.test/notes/1');
     219  for (const actie of ['/authorize_interaction/like', '/authorize_interaction/boost']) {
     220    assert.ok(html.includes(`action="${actie}"`), `${actie} hoort in de preview te staan`);
     221  }
     222  // Een editor in een uitklaplijst is op een telefoon geen plek om te
     223  // schrijven: de antwoordknop wijst naar de volle pagina, met het venster open.
     224  assert.ok(!html.includes('action="/authorize_interaction"'), 'geen antwoordvenster in de preview');
     225  assert.ok(!html.includes('action="/authorize_interaction/report"'), 'melden gebeurt op de volle pagina');
     226  assert.ok(html.includes('/authorize_interaction?uri=https%3A%2F%2Felders.test%2Fnotes%2F1&amp;reply=1'),
     227    'de antwoordknop moet naar de volle pagina met het venster al open');
     228});
     229
     230test('een profiel in de preview geeft de volgknop', async () => {
     231  const { status, html } = await preview('https://elders.test/u/oma');
     232  assert.equal(status, 200);
     233  assert.ok(html.includes('action="/authorize_interaction/follow"'));
     234});
     235
     236test('een bezoeker laat via de preview niets ophalen', async () => {
     237  ingelogd = false;
     238  try {
     239    const { status, html } = await preview('https://elders.test/notes/1');
     240    // 204 en geen 403: voor een bezoeker is een geplakte link gewoon een
     241    // zoekterm, en de zoekbalk valt dan terug op de gewone suggesties.
     242    assert.equal(status, 204);
     243    assert.deepEqual(gevraagd, [], 'zonder rechten hoort er geen enkel verzoek uit te gaan');
     244    assert.equal(html, '');
     245  } finally {
     246    ingelogd = true;
     247  }
     248});
     249
     250test('een zoekterm in de preview haalt niets op', async () => {
     251  const { status } = await preview('soundfabrics.nl');
     252  assert.equal(status, 204);
     253  assert.deepEqual(gevraagd, []);
     254});
     255
     256test('een inhoudswaarschuwing blijft dicht tot je ervoor kiest', async () => {
     257  // De oude render toonde alles open, ook wat de schrijver achter een
     258  // waarschuwing had gezet.
     259  const { html } = await preview('https://elders.test/notes/cw');
     260  const cw = html.indexOf('<details class="rp-cw">');
     261  assert.ok(cw >= 0, 'de post hoort achter een waarschuwing te staan');
     262  assert.ok(html.indexOf('Spinnen', cw) > cw, 'met de tekst van de waarschuwing erop');
     263  assert.ok(html.indexOf('Een bericht van ver weg') > cw, 'en de inhoud erachter, niet ervoor');
     264});
     265
     266test('de interactiepagina opent het antwoordvenster op verzoek, en haalt ondertekend op', async () => {
     267  const pagina = async (extra) => {
     268    opties = [];
     269    const r = await fetch(
     270      `http://127.0.0.1:${poort2}/authorize_interaction?uri=${encodeURIComponent('https://elders.test/notes/1')}${extra}`,
     271      { signal: AbortSignal.timeout(10000) },
     272    );
     273    return r.text();
     274  };
     275  const dicht = await pagina('');
     276  assert.match(dicht, /<section class="rp-reply" id="rp-reply" hidden>/, 'zonder verzoek is het venster dicht');
     277  assert.equal(opties[0].asSlug, 'robo', 'ook hier ondertekend, anders "niet gevonden" voor een volgerspost');
     278  const open = await pagina('&reply=1');
     279  assert.match(open, /<section class="rp-reply" id="rp-reply">/, 'met ?reply=1 staat het open, ook zonder JS');
     280  assert.ok(open.includes('action="/authorize_interaction"'), 'en het venster heeft zijn formulier');
     281});
     282
Note: See TracChangeset for help on using the changeset viewer.