Files

59 lines
2.8 KiB
JavaScript

/* check-admin-nav.mjs — every admin page must be reachable from the nav.
*
* node tools/check-admin-nav.mjs
*
* The A3 test rig shipped unreachable: the nav was a hand-maintained array of
* filenames and adding testsheet.html to the folder did not add it to the bar.
* Now the bar and the hub are both generated from TOOLS in admin.js, and this
* checks the other direction — that the folder and the registry agree.
*/
import { readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { TOOLS, GROUPS, nav, href } from '../public/admin/admin.js';
const adminDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'public', 'admin');
/* Pages that are intentionally not tools: the hub itself and the login screen. */
const NOT_TOOLS = new Set(['index.html', 'login.html']);
const pages = readdirSync(adminDir).filter(f => f.endsWith('.html') && !NOT_TOOLS.has(f));
const ids = new Set(TOOLS.map(t => t.id));
let bad = 0;
const fail = m => { console.log(' FAIL ' + m); bad++; };
console.log('admin nav / page registry\n');
for (const f of pages) {
const id = f.replace(/\.html$/, '');
if (!ids.has(id)) fail(`${f} exists but is not in TOOLS — unreachable from the nav and the hub`);
}
for (const t of TOOLS) {
if (!pages.includes(`${t.id}.html`)) fail(`TOOLS lists "${t.id}" but ${t.id}.html does not exist`);
if (!t.name || !t.blurb) fail(`"${t.id}" is missing a name or blurb`);
if (!GROUPS.includes(t.group)) fail(`"${t.id}" is in group "${t.group}", which is not in GROUPS`);
}
/* No check that a label differs from its filename: "Layout" and "Playlist" are
the right names and happen to match. The labels that were actually wrong
("landing", "plan", "print") were wrong because they did not say what the
page does, which no rule here can tell. */
/* The bar must actually contain every tool, and mark exactly one active. */
const bar = nav('playlist');
for (const t of TOOLS) {
if (!bar.includes(href(t))) fail(`nav() output is missing a link to ${href(t)}`);
if (!bar.includes(`>${t.name}<`)) fail(`nav() output is missing the label "${t.name}"`);
}
const actives = (bar.match(/class="active"/g) || []).length;
if (actives !== 1) fail(`nav('playlist') marked ${actives} links active, expected 1`);
if ((nav('').match(/class="active"/g) || []).length !== 0) fail(`nav('') marked a link active`);
if (!bar.startsWith('<header')) fail('nav() root is not <header> — print.html hides "header" on print');
console.log(` ${pages.length} pages, ${TOOLS.length} registered, ${GROUPS.length} groups`);
for (const g of GROUPS) {
console.log(` ${g.padEnd(12)} ${TOOLS.filter(t => t.group === g).map(t => t.name).join(', ')}`);
}
console.log(bad ? `\n${bad} problem(s)` : '\nall pages reachable');
process.exit(bad ? 1 : 0);