Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/verify_guidei18n.mjs

5.2 KiB, 1 run

created by r2519314175:455, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// The translated guide page: does it wear the app's palette (which it now gets
2// from the app's own stylesheet rather than a copy), and does a change of
3// language actually move the reader to the translated page?
4import os from 'node:os';
5import path from 'node:path';
6import { pathToFileURL } from 'node:url';
7const PW = path.join(os.homedir(), '.red-pw/node_modules/playwright-core/index.mjs');
8const { chromium } = await import(pathToFileURL(PW).href);
9const CHROME = `${process.env.HOME}/.cache/ms-playwright/chromium-1229/chrome-linux64/chrome`;
10const SP = new URL('shots/', import.meta.url).pathname;
11// The world's dev server -- see dev/world.sh. Kept inline rather than imported,
12// so this stays standalone and does not load the harness.
13const APP = process.env.DAIMOND_APP || `http://localhost:${process.env.DAIMOND_PORT || 8777}`;
14
15const browser = await chromium.launch({ executablePath: CHROME });
16const page = await browser.newPage({ viewport: { width: 900, height: 800 } });
17let bad = 0;
18const check = (ok, what) => { console.log(`${ok ? 'PASS' : 'FAIL'} ${what}`); if (!ok) bad++; };
19
20// Standalone, no framer: frame.js must still dress it from the OS preference.
21await page.goto(`${APP}/guide/de/index.html`, { waitUntil: 'networkidle' });
22const st = await page.evaluate(() => {
23 const r = document.documentElement;
24 const cs = getComputedStyle(document.body);
25 return {
26 lang: r.lang, loc: r.getAttribute('data-guide-locale'),
27 locales: r.getAttribute('data-guide-locales'),
28 theme: r.getAttribute('data-theme'), ink: r.getAttribute('data-ink'),
29 bg: cs.backgroundColor, fg: cs.color,
30 h1: (document.querySelector('h1') || {}).textContent,
31 };
32});
33console.log(JSON.stringify(st, null, 1));
34check(st.lang === 'de' && st.loc === 'de', 'the page declares itself German');
35check(!!st.theme && !!st.ink, `frame.js dressed it standalone (${st.theme}/${st.ink})`);
36check(st.bg !== 'rgba(0, 0, 0, 0)' && st.fg !== 'rgb(0, 0, 0)',
37 `it wears a real palette from the app's stylesheet (bg=${st.bg} fg=${st.fg})`);
38check(/[äöüßA-Za-z]/.test(st.h1 || '') && !/Getting started/.test(st.h1 || ''),
39 `the heading is translated: ${JSON.stringify(st.h1)}`);
40
41// A palette pushed in over the channel the app uses.
42await page.evaluate(() => window.postMessage({ daimondGuide: 'style', theme: 'linen', scale: 1.2 }, '*'));
43await page.waitForTimeout(200);
44const dressed = await page.evaluate(() => {
45 const r = document.documentElement;
46 return { theme: r.getAttribute('data-theme'), tone: r.getAttribute('data-tone'),
47 ink: r.getAttribute('data-ink'), scale: r.style.getPropertyValue('--fs-scale') };
48});
49check(dressed.theme === 'linen' && dressed.tone === 'light' && dressed.ink === 'dark' && dressed.scale === '1.2',
50 `a palette arriving over the channel is worn whole: ${JSON.stringify(dressed)}`);
51
52// A palette the guide has never heard of must be ignored, not half-applied.
53await page.evaluate(() => window.postMessage({ daimondGuide: 'style', theme: 'not-a-palette' }, '*'));
54await page.waitForTimeout(150);
55const kept = await page.evaluate(() => document.documentElement.getAttribute('data-theme'));
56check(kept === 'linen', `an unknown palette leaves the current one alone (${kept})`);
57
58// The language change: English page, told the app is now German.
59await page.goto(`${APP}/guide/index.html`, { waitUntil: 'networkidle' });
60await page.evaluate(() => window.postMessage({ daimondGuide: 'style', locale: 'de' }, '*'));
61await page.waitForTimeout(600);
62check(/\/guide\/de\/index\.html$/.test(page.url()), `a German app moves the reader to the German page (${page.url()})`);
63
64// And back.
65await page.evaluate(() => window.postMessage({ daimondGuide: 'style', locale: 'en' }, '*'));
66await page.waitForTimeout(600);
67check(/\/guide\/index\.html$/.test(page.url()), `and back to English (${page.url()})`);
68
69// A language the guide does not have must NOT move them anywhere. Every
70// locale the app ships is now translated, so this uses one it does not: the
71// guard is on the page's own list of languages, not on the app's.
72await page.evaluate(() => window.postMessage({ daimondGuide: 'style', locale: 'nl' }, '*'));
73await page.waitForTimeout(500);
74check(/\/guide\/index\.html$/.test(page.url()), `a language the guide does not have leaves them put (${page.url()})`);
75// And a locale-shaped string that is really a path must be refused outright.
76await page.evaluate(() => window.postMessage({ daimondGuide: 'style', locale: '../../etc' }, '*'));
77await page.waitForTimeout(400);
78check(/\/guide\/index\.html$/.test(page.url()), `a path dressed as a locale is refused (${page.url()})`);
79
80// Every link on the German page must resolve.
81await page.goto(`${APP}/guide/de/index.html`, { waitUntil: 'networkidle' });
82const links = await page.$$eval('a[href]', as => as.map(a => a.href).filter(h => h.startsWith('http')));
83const dead = [];
84for (const href of [...new Set(links)]) {
85 const r = await page.request.get(href);
86 if (!r.ok()) dead.push(`${href} -> ${r.status()}`);
87}
88check(dead.length === 0, `every link on the translated page resolves${dead.length ? `: ${dead.join(', ')}` : ` (${new Set(links).size} checked)`}`);
89
90await page.screenshot({ path: `${SP}/guide-de-i18n.png`, fullPage: false });
91console.log(bad ? `\n${bad} FAILED` : '\nALL PASS');
92await browser.close();
93process.exit(bad ? 1 : 0);