import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { join } from 'node:path'; import test from 'node:test'; import assert from 'node:assert/strict'; const repoRoot = fileURLToPath(new URL('..', import.meta.url)); const read = (...parts) => readFileSync(join(repoRoot, ...parts), 'utf8'); const CANONICAL = ['portable', 'config-server', 'public', 'index.html']; const SHIM = ['portable', 'advanced', 'Config.html']; const SYNCED = ['u-claw-app', 'resources', 'Config.html']; // There were three settings pages: this shim (748 lines), the served page, and a // copy inside the Electron app. The shim could never work — it calls // fetch('/api/config') with a relative path, which from file:// resolves to // file:///api/config and is blocked, so Save failed silently for anyone who // double-clicked it. One implementation now; this file is a redirect. test('portable/Config.html is a redirect shim, not a second implementation', () => { const shim = read(...SHIM); assert.ok(shim.split('\n').length < 200, 'the shim should stay small'); assert.doesNotMatch( shim, /fetch\(\s*['"`]\/api\//, 'a file:// page cannot call a relative API path — that was the original bug', ); assert.match(shim, /location\.replace/, 'the shim should redirect to the running server'); }); test('the shim probes the whole config-server port range', () => { const shim = read(...SHIM); const server = read('portable', 'config-server', 'server.js'); const start = Number(server.match(/PORT_RANGE_START\s*=\s*(\d+)/)?.[1]); const end = Number(server.match(/PORT_RANGE_END\s*=\s*(\d+)/)?.[1]); assert.ok(Number.isInteger(start) && Number.isInteger(end), 'server must declare its port range'); // Hardcoding 18788 is exactly what made Mac-Start.command open a dead page. assert.match(shim, new RegExp(`PORT_FROM\\s*=\\s*${start}`), `shim should probe from ${start}`); assert.match(shim, new RegExp(`PORT_TO\\s*=\\s*${end}`), `shim should probe to ${end}`); }); test('the Electron copy is generated from the canonical page, not hand-maintained', () => { const sync = read('u-claw-app', 'scripts', 'sync-lib.js'); assert.match( sync, /config-server['"],\s*['"]public['"],\s*['"]index\.html/, 'sync-lib.js must copy from config-server/public/index.html', ); assert.equal( read(...SYNCED), read(...CANONICAL), 'u-claw-app/resources/Config.html is out of sync — run node u-claw-app/scripts/sync-lib.js', ); }); test('the shim explains what to do when nothing is listening', () => { const shim = read(...SHIM); // House rule: what happened, why, and one thing to do next. for (const key of ['what', 'why', 'action']) { assert.match(shim, new RegExp(`data-msg="${key}"`), `the offline state needs a "${key}" line`); } for (const locale of ['en', "'zh-CN'"]) { assert.match(shim, new RegExp(locale.replace(/[-']/g, (c) => `\\${c}`)), `shim should carry ${locale} strings`); } });