import { readAppFile } from '../test-support/i18n.js'; import { loadI18n } from '../test-support/i18n.js'; import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import path from 'node:path'; import test from 'node:test'; import vm from 'node:vm'; const root = path.resolve(import.meta.dirname, '..'); async function read(relativePath) { return readAppFile(relativePath); } async function clientSources() { const directory = path.join(root, 'public/js'); const names = await fs.readdir(directory); return Promise.all(names.filter(name => name.endsWith('.js')).map(name => read(`public/js/${name}`))); } test('every static HTML id is referenced by client JavaScript or accessible HTML relationships', async () => { const html = await read('public/index.html'); const scripts = (await clientSources()).join('\n'); const ids = [...html.matchAll(/\bid="([^"]+)"/g)].map(match => match[1]); const htmlReferences = new Set([...html.matchAll(/\b(?:aria-labelledby|aria-describedby|aria-controls|for)="([^"]+)"/g)] .flatMap(match => match[1].split(/\s+/))); assert.ok(ids.length > 0); for (const id of ids) { if (htmlReferences.has(id)) continue; assert.match(scripts, new RegExp(id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `unused id: ${id}`); } }); test('every static HTML class is used by CSS or authored JavaScript', async () => { const html = await read('public/index.html'); const css = `${await read('public/css/bootstrap.css')}\n${await read('public/css/app.css')}`; const scripts = (await clientSources()).join('\n'); const classes = [...html.matchAll(/\bclass="([^"]+)"/g)] .flatMap(match => match[1].split(/\s+/)) .filter(Boolean); for (const className of new Set(classes)) { assert.ok(css.includes(className) || scripts.includes(className), `unused class: ${className}`); } }); test('application actions use pointer events instead of click handlers', async () => { const scripts = (await Promise.all([ read('public/js/app.js'), read('public/js/slideshow.js'), read('public/js/bootstrap-loader.js') ])).join('\n'); assert.match(scripts, /pointerup/); assert.match(scripts, /keydown/); assert.doesNotMatch(scripts, /(?:\.on|addEventListener)\(\s*['"]click['"]/); }); test('mobile navigation footer contains a rotating SVG arrow and exact metre output', async () => { const html = await read('public/index.html'); const app = await read('public/js/app.js'); const css = await read('public/css/app.css'); assert.match(html, /