source: Klonkt/test/migrate-page.test.js@ 2fbced5

main
Last change on this file since 2fbced5 was bfadc15, checked in by Robin <roboburr@…>, 4 weeks ago

De teksten op de migrate-pagina in mensentaal

Robin: er zijn miljoenen Klonkt-gebruikers, dus het moet gebruiksvriendelijk
worden gemaakt. Ik had die opmerking eerst gelezen als een schaalwaarschuwing
en zat prestaties te repareren. Dat leverde wel een echte fout op (7e474f7),
maar het was zijn vraag niet.

Wat er stond was voor mij geschreven, niet voor wie verhuist:

claimen, alias, actor-adres, instantie, AP-ids, fediverse,
"een bericht met dezelfde webadres-naam maar een ander id"

Achttien teksten herschreven in nl/en/de. "Je oude account claimen" werd "je
vorige account koppelen". De overschrijf-waarschuwing gaat niet meer over
unieke sleutels maar over wat je kwijtraakt. De foutmelding bij een ontbrekende
koppeling verwijst nu naar stap 1 op het scherm in plaats van naar een begrip.

DE VOORBEELDEN TONEN HANDLES. Er stond https://oude-klonkt.nl/ap/users/jij als
placeholder. Niemand kent zijn eigen actor-URL; mensen kennen
@robo@….

EN HET VELD GEEFT TERUG WAT JE GAF. Je typte een handle, wij slaan de actor-URL
op omdat het protocol daarop draait, en dan kreeg je een adres terug dat je
nooit had ingetypt. Nu tonen we de handle, en alleen als er echt een uit te
halen valt: een verkeerde handle is erger dan een lelijke URL. Opslaan leest
beide vormen, dus daar verandert niets.

Changed files:
src/services/i18n.js

  • 19 teksten herschreven in drie talen, jargon eruit

src/views/pages/admin-migrate.ejs

  • placeholders tonen @handle in plaats van een actor-URL

src/routes/admin-migrate.js

  • aliasTekst() toont handles, met de URL als terugval

test/migrate-page.test.js

  • een jargonwacht over 23 sleutels maal drie talen
  • de handle-heenweg en -terugweg
  • de stap-5-test toetst nu op BETEKENIS; hij brak omdat het woord "alias" verdween, en dat was precies de bedoeling

remarks: de indeling blijft zoals hij was (Robins keuze). Het grotere idee, een
vraag bovenaan of je hier binnenkomt of weggaat en dan alleen die helft tonen,
is niet gedaan. Suite 1016 groen.

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

  • Property mode set to 100644
File size: 8.4 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';
14const i18nMod = await import('../src/services/i18n.js');
15
16process.env.DATABASE_PATH = ':memory:';
17process.env.PUBLIC_BASE_URL = 'https://ik.example';
18
19const dbMod = await import('../src/config/database.js');
20const db = dbMod.default;
21{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
22
23test('het site-bewerkscherm draagt geen alias- of verhuisformulier meer', () => {
24 const ejs = fs.readFileSync('src/views/pages/admin-site-edit.ejs', 'utf8');
25 assert.ok(!/name="ap_aliases"/.test(ejs), 'het aliasveld hoort bij Migreren');
26 assert.ok(!/name="move_target"/.test(ejs), 'en de verhuisknop ook');
27 assert.match(ejs, /\/admin\/migrate/, 'maar er staat wel een wegwijzer, anders zoekt iemand zich rot');
28});
29
30test('de migrate-pagina draagt ze allebei wel', () => {
31 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
32 assert.match(ejs, /name="ap_aliases"/);
33 assert.match(ejs, /name="move_target"/);
34 // De verhuisknop post naar de BESTAANDE route: een tweede implementatie van
35 // een onomkeerbare actie is precies wat je niet wilt.
36 assert.match(ejs, /action="\/admin\/sites\/<%= site\.slug %>\/move"/);
37 assert.match(ejs, /name="next" value="\/admin\/migrate"/, 'en komt terug waar je vandaan kwam');
38});
39
40test('de volgorde op de pagina volgt de verhuizing: claimen eerst, aankondigen laatst', () => {
41 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
42 const alias = ejs.indexOf('name="ap_aliases"');
43 const halen = ejs.indexOf('/admin/migrate/pull');
44 const move = ejs.indexOf('name="move_target"');
45 assert.ok(alias > -1 && halen > -1 && move > -1);
46 assert.ok(alias < halen, 'claimen staat boven ophalen: zonder claim geeft de bron niets');
47 assert.ok(halen < move, 'aankondigen staat onderaan: dat is de onomkeerbare stap');
48});
49
50test('een alias overleeft het opslaan van je uiterlijk', async () => {
51 // De valkuil. Geen HTTP nodig: dit gaat om de regel dat een ontbrekend veld
52 // "niet aanraken" betekent en niet "leegmaken".
53 db.prepare("INSERT INTO users (id,username,email,password_hash,role) VALUES ('u1','u','u@t','x','god')").run();
54 db.prepare("INSERT INTO sites (id,slug,title,owner_id,ap_aliases) VALUES ('s1','ik','Ik','u1',?)")
55 .run(JSON.stringify(['https://oud.example/ap/users/robo']));
56
57 const site = db.prepare("SELECT id, ap_aliases FROM sites WHERE slug = 'ik'").get();
58 const formulierZonderVeld = { title: 'Ik', accent: '#c33' }; // zoals het nu binnenkomt
59 const apAliasesJson = Object.prototype.hasOwnProperty.call(formulierZonderVeld, 'ap_aliases')
60 ? null : (site.ap_aliases || null);
61 db.prepare('UPDATE sites SET ap_aliases = ? WHERE slug = ?').run(apAliasesJson, 'ik');
62
63 const na = db.prepare("SELECT ap_aliases FROM sites WHERE slug = 'ik'").get();
64 assert.deepEqual(JSON.parse(na.ap_aliases), ['https://oud.example/ap/users/robo'],
65 'je claim op je oude account mag niet sneuvelen op een kleurwijziging');
66});
67
68// ── Het stappenplan ───────────────────────────────────────────────
69
70test('het stappenplan zet de volgorde vast die niemand raadt', () => {
71 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
72 const plan = ejs.indexOf('mig-steps');
73 const alias = ejs.indexOf('name="ap_aliases"');
74 assert.ok(plan > -1, 'er hoort een stappenplan te staan');
75 assert.ok(plan < alias, 'en bovenaan, voor de knoppen die het beschrijft');
76});
77
78test('"hier" draait om als je vanaf de vertrekkende kant kijkt', async () => {
79 // Deze pagina draait op BEIDE instanties. Stap 1, 3 en 4 horen op de nieuwe,
80 // stap 2 en 5 op de oude. Een vaste tekst is dus op de helft van de schermen
81 // onzin. Ik had ze bij het bouwen precies verkeerd om: beide labels werden
82 // "hier", en dat zag ik pas door het te renderen.
83 const ejs = fs.readFileSync('src/views/pages/admin-migrate.ejs', 'utf8');
84 const m = /var _hier = stap\.isOud \? t\('([a-z.\_]+)'\)\s*:\s*t\('([a-z.\_]+)'\)/.exec(ejs);
85 const d = /var _daar = stap\.isOud \? t\('([a-z.\_]+)'\)\s*:\s*t\('([a-z.\_]+)'\)/.exec(ejs);
86 assert.ok(m && d, 'de perspectief-variabelen horen er te zijn');
87 assert.equal(m[1], 'mig.plan_new', 'op de oude kant wijst stap 1/3/4 naar de NIEUWE');
88 assert.equal(m[2], 'mig.plan_here', 'op de nieuwe kant is dat gewoon hier');
89 assert.equal(d[1], 'mig.plan_here', 'en het aankondigen gebeurt dan HIER');
90 assert.equal(d[2], 'mig.plan_old', 'terwijl je vanaf de nieuwe kant naar de oude wijst');
91 assert.notEqual(m[1], d[1], 'de twee mogen nooit hetzelfde label krijgen');
92});
93
94test('het aankondigen heet geen "laatste stap" meer', async () => {
95 // Het was er wel een in mijn hoofd, maar niet in de code: de ophaalknop
96 // weigert met not_moved_here zolang de Move niet gedaan is. Aankondigen is
97 // dus stap 2, niet de afsluiter.
98 const i18n = await import('../src/services/i18n.js');
99 for (const l of ['nl', 'en', 'de']) {
100 const t = i18n.t(l, 'mig.move_title');
101 assert.doesNotMatch(t, /laatste|last step|letzter/i, `${l}: ${t}`);
102 assert.match(t, /2/, `${l}: het is stap 2, en dat hoort er te staan`);
103 }
104});
105
106test('stap 5 vertelt wat er moet gebeuren voor je het domein loslaat', async () => {
107 // Er is geen knop voor, en dat staat er ook bij. Een stappenplan dat een stap
108 // beschrijft die niet bestaat is erger dan geen stappenplan.
109 const i18n = await import('../src/services/i18n.js');
110 // Op BETEKENIS toetsen, niet op een woord. Deze test brak toen "alias" uit de
111 // teksten verdween, en dat is precies de verkeerde reden om rood te worden:
112 // het jargon weghalen was de bedoeling.
113 const nl = i18n.t('nl', 'mig.plan_5_why');
114 assert.match(nl, /verwijder/i, 'de berichten opruimen');
115 assert.match(nl, /stap 1/i, 'en de koppeling met je vorige account weghalen');
116 assert.match(nl, /nog geen knop/i, 'en eerlijk dat het handwerk is');
117});
118
119test('de teksten praten geen jargon', () => {
120 // Robin: er zijn miljoenen Klonkt-gebruikers, dus het moet gebruiksvriendelijk.
121 // Woorden als alias, actor, instantie en fediverse zeggen een ontwikkelaar
122 // alles en een gebruiker niets. Deze test bewaakt dat ze niet terugsluipen.
123 const jargon = /\b(alias(sen)?|claimen|actor|instantie|AP-id|origin|slug|URI)\b/i;
124 const sleutels = [
125 'lead', 'alias_title', 'alias_hint', 'alias_label', 'alias_note', 'alias_btn',
126 'import_hint', 'file_label', 'overwrite_label', 'overwrite_hint', 'r_new_ids',
127 'pull_hint', 'pull_source', 'pull_source_hint', 'move_hint', 'move_warn',
128 'plan_hint', 'plan_1', 'plan_1_why', 'plan_5_why',
129 'e_no_backreference', 'e_not_moved_here', 'e_no_source',
130 ];
131 const fout = [];
132 for (const l of ['nl', 'en', 'de']) {
133 for (const k of sleutels) {
134 const t = i18nMod.t(l, `mig.${k}`);
135 const m = jargon.exec(t);
136 if (m) fout.push(`${l}/${k}: "${m[0]}"`);
137 }
138 }
139 assert.deepEqual(fout, [], `jargon teruggeslopen: ${fout.join(', ')}`);
140});
141
142test('een gekoppeld account komt terug als handle, niet als URL', async () => {
143 // Je typt @jij@mastodon.social en wij slaan de actor-URL op, want daar draait
144 // het protocol op. Zonder deze vertaling terug kreeg je een adres te zien dat
145 // je nooit hebt ingetypt en niet herkent.
146 const AP = await import('../src/services/ActivityPubService.js');
147 assert.equal(AP.deriveHandle('https://oud.example/ap/users/robo'), '@robo@oud.example');
148 // En als er geen fatsoenlijke handle uit te halen valt blijft de URL staan:
149 // een verkeerde handle is erger dan een lelijke URL.
150 const raar = AP.deriveHandle('https://oud.example/');
151 assert.ok(!/^@[^@\s]+@[^@\s]+$/.test(raar) || raar === '@oud.example@oud.example',
152 `onverwachte handle uit een pad zonder naam: ${raar}`);
153});
Note: See TracBrowser for help on using the repository browser.