source: Klonkt/test/migrate-page.test.js@ 7e474f7

main
Last change on this file since 7e474f7 was 47f1183, checked in by Robin <roboburr@…>, 4 weeks ago

Een stappenplan op de migrate-pagina, en de volgorde klopte niet

Robin: misschien moeten we bij migrate een stappenplan beschrijven, waaronder
dus ook dit (de Delete-ronde voor je een domein opzegt).

Bij het uitschrijven bleek de pagina zelf een verkeerde volgorde te suggereren.
Ik noemde het aankondigen "de laatste stap", maar ingestFromSource weigert met
not_moved_here zolang movedTo niet naar ons wijst. De ophaalknop werkt dus PAS
nadat de Move gedaan is. Aankondigen is stap 2, niet de afsluiter.

Met een zip mag het juist andersom: een bestand vraagt niemand toestemming. Die
twee volgordes naast elkaar zijn precies het soort ding waar je halverwege op
vastloopt zonder te snappen waarom, dus staan ze er nu uitgeschreven.

DE PAGINA DRAAIT OP BEIDE INSTANTIES. Stap 1, 3 en 4 horen op de nieuwe kant,
stap 2 en 5 op de oude. Een vast "hier" is dus op de helft van de schermen
onzin. Het plan draait mee met waar je kijkt, en op de vertrekkende kant staat
er een regel boven dat je naar de instantie kijkt die weggaat.

Die omkering had ik bij het bouwen precies verkeerd om: beide labels werden
"hier". Zag ik pas door hem te renderen, niet door hem te lezen.

STAP 5 heeft geen knop, en dat staat erbij. Verwijder je oude berichten voor je
het domein opzegt, zodat boosts van anderen netjes verdwijnen in plaats van te
blijven staan als kapotte kaart; haal daarna de alias weg, want die wijst naar
een adres dat je niet meer beheert. Een stappenplan dat een knop belooft die er
niet is, is erger dan geen stappenplan.

Changed files:
src/routes/admin-migrate.js

  • stappen(): afleiden wat af is uit aliassen, moved_to, de moves-collectie en de vertaaltabel; meegegeven aan alle drie de renders

src/views/pages/admin-migrate.ejs

  • het stappenplan bovenaan, met vinkjes en een waar-label per stap
  • de labels draaien om vanaf de vertrekkende kant

src/services/i18n.js

  • mig.plan_* in nl/en/de
  • mig.move_title is "Stap 2" en niet meer "Laatste stap"
  • de waarschuwing zegt nu dat dit EERST moet bij de ophaalknop

test/migrate-page.test.js

  • het plan staat boven de knoppen die het beschrijft
  • de perspectiefwissel, met de eis dat de twee labels nooit gelijk zijn
  • geen "laatste stap" meer in een van de drie talen
  • stap 5 noemt de Delete-ronde, de alias en dat er geen knop is

remarks: gerenderd vanaf beide kanten om het te controleren. Suite 1013 groen,
tegenproef op de perspectiefwissel.

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

  • Property mode set to 100644
File size: 6.3 KB
Line 
1// Alle migratie-opties op één pagina (Robin, 14-8), en wat daarbij stuk kon.
2//
3// Aliassen en verhuizen stonden op de site-bewerkpagina, tussen de kleuren en
4// de feedinstellingen. Dat is de verkeerde plek: het zijn stap 1 en stap 4 van
5// een verhuizing, en het scherm eromheen gaat over hoe je site eruitziet.
6//
7// De verplaatsing had een stille valkuil: de opslagroute van dat scherm schreef
8// ap_aliases uit het formulier. Verdwijnt het veld, dan is de waarde leeg, en
9// dan wist je je claim op je oude account door je accentkleur te wijzigen. De
10// Move weigert daarna met no_backreference en je snapt niet waarom.
11import { test } from 'node:test';
12import assert from 'node:assert/strict';
13import fs from 'fs';
14
15process.env.DATABASE_PATH = ':memory:';
16process.env.PUBLIC_BASE_URL = 'https://ik.example';
17
18const dbMod = await import('../src/config/database.js');
19const db = dbMod.default;
20{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
21
22test('het site-bewerkscherm draagt geen alias- of verhuisformulier meer', () => {
23 const ejs = fs.readFileSync('src/views/pages/admin-site-edit.ejs', 'utf8');
24 assert.ok(!/name="ap_aliases"/.test(ejs), 'het aliasveld hoort bij Migreren');
25 assert.ok(!/name="move_target"/.test(ejs), 'en de verhuisknop ook');
26 assert.match(ejs, /\/admin\/migrate/, 'maar er staat wel een wegwijzer, anders zoekt iemand zich rot');
27});
28
29test('de migrate-pagina draagt ze allebei wel', () => {
30 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
31 assert.match(ejs, /name="ap_aliases"/);
32 assert.match(ejs, /name="move_target"/);
33 // De verhuisknop post naar de BESTAANDE route: een tweede implementatie van
34 // een onomkeerbare actie is precies wat je niet wilt.
35 assert.match(ejs, /action="\/admin\/sites\/<%= site\.slug %>\/move"/);
36 assert.match(ejs, /name="next" value="\/admin\/migrate"/, 'en komt terug waar je vandaan kwam');
37});
38
39test('de volgorde op de pagina volgt de verhuizing: claimen eerst, aankondigen laatst', () => {
40 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
41 const alias = ejs.indexOf('name="ap_aliases"');
42 const halen = ejs.indexOf('/admin/migrate/pull');
43 const move = ejs.indexOf('name="move_target"');
44 assert.ok(alias > -1 && halen > -1 && move > -1);
45 assert.ok(alias < halen, 'claimen staat boven ophalen: zonder claim geeft de bron niets');
46 assert.ok(halen < move, 'aankondigen staat onderaan: dat is de onomkeerbare stap');
47});
48
49test('een alias overleeft het opslaan van je uiterlijk', async () => {
50 // De valkuil. Geen HTTP nodig: dit gaat om de regel dat een ontbrekend veld
51 // "niet aanraken" betekent en niet "leegmaken".
52 db.prepare("INSERT INTO users (id,username,email,password_hash,role) VALUES ('u1','u','u@t','x','god')").run();
53 db.prepare("INSERT INTO sites (id,slug,title,owner_id,ap_aliases) VALUES ('s1','ik','Ik','u1',?)")
54 .run(JSON.stringify(['https://oud.example/ap/users/robo']));
55
56 const site = db.prepare("SELECT id, ap_aliases FROM sites WHERE slug = 'ik'").get();
57 const formulierZonderVeld = { title: 'Ik', accent: '#c33' }; // zoals het nu binnenkomt
58 const apAliasesJson = Object.prototype.hasOwnProperty.call(formulierZonderVeld, 'ap_aliases')
59 ? null : (site.ap_aliases || null);
60 db.prepare('UPDATE sites SET ap_aliases = ? WHERE slug = ?').run(apAliasesJson, 'ik');
61
62 const na = db.prepare("SELECT ap_aliases FROM sites WHERE slug = 'ik'").get();
63 assert.deepEqual(JSON.parse(na.ap_aliases), ['https://oud.example/ap/users/robo'],
64 'je claim op je oude account mag niet sneuvelen op een kleurwijziging');
65});
66
67// ── Het stappenplan ───────────────────────────────────────────────
68
69test('het stappenplan zet de volgorde vast die niemand raadt', () => {
70 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
71 const plan = ejs.indexOf('mig-steps');
72 const alias = ejs.indexOf('name="ap_aliases"');
73 assert.ok(plan > -1, 'er hoort een stappenplan te staan');
74 assert.ok(plan < alias, 'en bovenaan, voor de knoppen die het beschrijft');
75});
76
77test('"hier" draait om als je vanaf de vertrekkende kant kijkt', async () => {
78 // Deze pagina draait op BEIDE instanties. Stap 1, 3 en 4 horen op de nieuwe,
79 // stap 2 en 5 op de oude. Een vaste tekst is dus op de helft van de schermen
80 // onzin. Ik had ze bij het bouwen precies verkeerd om: beide labels werden
81 // "hier", en dat zag ik pas door het te renderen.
82 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
83 const m = /var _hier = stap\.isOud \? t\('([a-z.\_]+)'\)\s*:\s*t\('([a-z.\_]+)'\)/.exec(ejs);
84 const d = /var _daar = stap\.isOud \? t\('([a-z.\_]+)'\)\s*:\s*t\('([a-z.\_]+)'\)/.exec(ejs);
85 assert.ok(m && d, 'de perspectief-variabelen horen er te zijn');
86 assert.equal(m[1], 'mig.plan_new', 'op de oude kant wijst stap 1/3/4 naar de NIEUWE');
87 assert.equal(m[2], 'mig.plan_here', 'op de nieuwe kant is dat gewoon hier');
88 assert.equal(d[1], 'mig.plan_here', 'en het aankondigen gebeurt dan HIER');
89 assert.equal(d[2], 'mig.plan_old', 'terwijl je vanaf de nieuwe kant naar de oude wijst');
90 assert.notEqual(m[1], d[1], 'de twee mogen nooit hetzelfde label krijgen');
91});
92
93test('het aankondigen heet geen "laatste stap" meer', async () => {
94 // Het was er wel een in mijn hoofd, maar niet in de code: de ophaalknop
95 // weigert met not_moved_here zolang de Move niet gedaan is. Aankondigen is
96 // dus stap 2, niet de afsluiter.
97 const i18n = await import('../src/services/i18n.js');
98 for (const l of ['nl', 'en', 'de']) {
99 const t = i18n.t(l, 'mig.move_title');
100 assert.doesNotMatch(t, /laatste|last step|letzter/i, `${l}: ${t}`);
101 assert.match(t, /2/, `${l}: het is stap 2, en dat hoort er te staan`);
102 }
103});
104
105test('stap 5 vertelt wat er moet gebeuren voor je het domein loslaat', async () => {
106 // Er is geen knop voor, en dat staat er ook bij. Een stappenplan dat een stap
107 // beschrijft die niet bestaat is erger dan geen stappenplan.
108 const i18n = await import('../src/services/i18n.js');
109 const nl = i18n.t('nl', 'mig.plan_5_why');
110 assert.match(nl, /verwijder/i, 'de Delete-ronde');
111 assert.match(nl, /alias/i, 'en de alias weghalen');
112 assert.match(nl, /nog geen knop/i, 'en eerlijk dat het handwerk is');
113});
Note: See TracBrowser for help on using the repository browser.