source: Klonkt/test/guardian-panel.test.js@ 70677e96

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

Guardian-PWA: een paneel per kind

/guardian was een platte lijst van zeven secties: de wards' berichten, de
volgverzoeken, de hulpvragen, adopteren, verzonden aanvragen, wards, meldingen.
Wie een kind wilde overzien moest tussen die secties heen en weer, en nergens
stond bij elkaar wat er over dat ene kind speelt. Een guardian denkt niet per
functie maar per kind.

Nu is de wards-lijst de ingang: elk kind is een regel die opent naar een paneel
met zijn instellingen, zijn volgverzoeken, zijn hulpvragen en zijn recente
berichten. De regel zelf draagt de tellers (hulpvragen, volgverzoeken), zodat
een dicht paneel nooit iets verbergt dat een antwoord nodig heeft; dat is
belangrijk voor de volgverzoeken, want daar zit de follow-gating in.

De hulpvragen blijven daarnaast bovenaan staan, over alle kinderen heen, want
dat is waar deze app voor bestaat en het moet opvallen zonder dat je eerst een
kind opent. Ze verschijnen dus bewust twee keer: bovenaan de recente, in het
paneel de volledige geschiedenis van dat kind.

De hulpvraag is geen alarm. Er wordt niemand automatisch gewaarschuwd; een
guardian kan hem stil oplossen. De kaart is daarom rustig gehouden.

Changed files:
src/views/pages/guardian.ejs

  • feed-section en follow-section weg als losse secties

src/assets/js/guardian.js

  • helpCard, feedCard en followCard losgetrokken zodat beide plekken ze delen
  • wardPanel: instellingen, volgverzoeken, hulpvragen, berichten, acties
  • openPanels onthoudt wat open staat, zodat verversen niet dichtklapt
  • feed en volgverzoeken vullen nu een cache en hertekenen de wards-lijst

src/assets/css/guardian.css

  • opmaak voor het paneel en de tellers op de regel

src/routes/guardian.js

  • authorUri en wardUri meegeven: de sleutels waarop gegroepeerd wordt
  • de nieuwe labels toegevoegd aan uiStrings

src/services/i18n.js

  • 39 strings voor het paneel in nl, en, de

New file:
test/guardian-panel.test.js

  • de client schrijft nergens naar een verdwenen sectie
  • elke paneelsectie heeft zijn groepeersleutel
  • elk label dat de client gebruikt wordt ook geserveerd

remarks: geverifieerd in de browser op een wegwerp-database met twee kinderen:
elk paneel toont alleen zijn eigen volgverzoek, hulpvraag en post, een geopend
paneel blijft open over een ververs-ronde heen, en op 375px loopt niets buiten
beeld. De afhandel-lifecycle (oppakken/afgehandeld, shaer-jxi) en luid publiek
als tweede gated feature (shaer-3kp) komen hierna in dit paneel.

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

  • Property mode set to 100644
File size: 2.8 KB
RevLine 
[70677e96]1// The Guardian PWA is built in the browser from a state blob, so the usual
2// tests cannot reach it. These two failure modes bit during the rebuild and are
3// cheap to guard: a renderer writing into a section that no longer exists, and
4// a grouping key quietly dropped from a route so every panel comes up empty.
5import { test } from 'node:test';
6import assert from 'node:assert/strict';
7import fs from 'fs';
8
9const read = (p) => fs.readFileSync(new URL(p, import.meta.url), 'utf8');
10const client = read('../src/assets/js/guardian.js');
11const page = read('../src/views/pages/guardian.ejs');
12const route = read('../src/routes/guardian.js');
13
14test('the client only touches element ids the page actually has', () => {
15 // g-fatal is the exception: the crash banner is created by the client itself
16 // (getElementById || createElement), precisely because the page may be too
17 // broken to have it.
18 const ids = new Set([...page.matchAll(/id="([^"]+)"/g)].map((m) => m[1])).add('g-fatal');
19 const used = [...client.matchAll(/getElementById\('([^']+)'\)/g)].map((m) => m[1]);
20 const missing = [...new Set(used)].filter((id) => !ids.has(id));
21 assert.deepEqual(missing, [], `guardian.js writes into ${missing.join(', ')}, which the page does not have`);
22});
23
24test('the sections that moved into the panels are gone from the page', () => {
25 for (const id of ['feed-section', 'follow-section', 'feed-list', 'follow-list']) {
26 assert.ok(!page.includes(`id="${id}"`), `${id} moved into the per-ward panel and must not be a section of its own`);
27 }
28});
29
30test('every panel section has something to group by', () => {
31 // A child's panel is filled by matching these against the ward's actor URI.
32 // Lose one and that section silently shows "nothing yet" for every child.
33 assert.match(route, /authorUri: p\.author_uri/, "the wards' posts");
34 assert.match(route, /wardUri: w\.uri/, 'follow requests on a local ward');
35 assert.match(route, /wardUri: rev\.ward_uri/, 'follow requests forwarded from a remote ward');
36 for (const key of ['authorUri', 'wardUri', 'actor_uri']) {
37 assert.ok(client.includes(key), `the panel groups on ${key}`);
38 }
39});
40
41test('the labels the panel renders are all served to it', () => {
42 // uiStrings() picks the keys by hand, so a label used in the client but not
43 // listed there renders as an empty string with no error anywhere. Two shapes
44 // count as served: an entry in the keys array, and a direct s.foo = ...
45 // assignment underneath it (wave/waved arrived that way).
46 const served = new Set([
47 ...[...route.matchAll(/'([a-z_]+)'/g)].map((m) => m[1]),
48 ...[...route.matchAll(/\bs\.([a-z_]+)\s*=/g)].map((m) => m[1]),
49 ]);
50 const used = [...new Set([...client.matchAll(/T\.([a-z_]+)/g)].map((m) => m[1]))];
51 const missing = used.filter((k) => !served.has(k));
52 assert.deepEqual(missing, [], `uiStrings() does not serve: ${missing.join(', ')}`);
53});
Note: See TracBrowser for help on using the repository browser.