updates
All checks were successful
Zone / build (push) Successful in 13s

This commit is contained in:
2026-06-04 14:18:21 +01:00
parent 43135bcec9
commit 569c9853bf
11 changed files with 1841 additions and 1163 deletions

146
test/dashboard.test.mjs Normal file → Executable file
View File

@@ -3,17 +3,16 @@ import assert from 'node:assert/strict';
import test from 'node:test';
const html = await readFile(new URL('../index.html', import.meta.url), 'utf8');
const manifest = JSON.parse(
await readFile(new URL('../data/manifest.json', import.meta.url), 'utf8')
);
const dashboardJs = await readFile(new URL('../js/dashboard.js', import.meta.url), 'utf8');
function idsInMarkup() {
return [...html.matchAll(/\sid=["']([^"']+)["']/g)].map(match => match[1]);
}
function elementsWithClass(className) {
const pattern = new RegExp(`<[^>]*class=["'][^"']*\\b${className}\\b[^"']*["'][^>]*>`, 'g');
return [...html.matchAll(pattern)].map(match => match[0]);
}
test('dashboard keeps the required interactive element ids unique', () => {
test('dashboard keeps the required shell element ids unique', () => {
const ids = idsInMarkup();
const requiredIds = [
'clockDisplay',
@@ -25,8 +24,8 @@ test('dashboard keeps the required interactive element ids unique', () => {
'serverDot',
'webDot',
'webStatus',
'roamDot',
'roamStatus',
'emacsDot',
'emacsStatus',
'uptimeStat',
'uptimeDays',
'uptimeHours',
@@ -35,6 +34,9 @@ test('dashboard keeps the required interactive element ids unique', () => {
'uptimePct',
'uptimeFill',
'pingHistory',
'quickLinks',
'widgetGrid',
'buildGrid',
'dailyMessage',
'countdownLabel',
'countdownDisplay',
@@ -42,21 +44,6 @@ test('dashboard keeps the required interactive element ids unique', () => {
'cd-hours',
'cd-mins',
'cd-secs',
'buildBtn',
'killWebBtn',
'buildResult',
'webLastRunMeta',
'logOutput',
'orgRoamBtn',
'orgRoamBtn2',
'combinedBtn',
'killRoamBtn',
'pipelineSteps',
'pipeStep1',
'pipeStep2',
'orgRoamResult',
'roamLastRunMeta',
'orgRoamLogs',
'modalOverlay',
'modalTitle',
'modalBody',
@@ -72,24 +59,39 @@ test('dashboard keeps the required interactive element ids unique', () => {
assert.deepEqual(duplicates, []);
});
test('website and roam logs remain directly accessible from toggle buttons', () => {
const ids = new Set(idsInMarkup());
const toggleButtons = elementsWithClass('toggleLogs');
assert.equal(toggleButtons.length, 2);
for (const button of toggleButtons) {
const target = button.match(/data-target=["']([^"']+)["']/)?.[1];
assert.ok(target, `toggle missing data-target: ${button}`);
assert.ok(ids.has(target), `toggle target #${target} is missing`);
}
assert.match(html, /data-target=["']logOutput["']/);
assert.match(html, /data-target=["']orgRoamLogs["']/);
assert.match(html, /id=["']logOutput["'][^>]*class=["'][^"']*\blog-box\b[^"']*\bhidden\b/);
assert.match(html, /id=["']orgRoamLogs["'][^>]*class=["'][^"']*\blog-box\b[^"']*\bhidden\b/);
test('manifest v2 defines integrations and widgets', () => {
assert.equal(manifest.version, 2);
assert.ok(manifest.integrations?.authoring);
assert.ok(Array.isArray(manifest.widgets) && manifest.widgets.length >= 3);
assert.ok(manifest.widgets.some(w => w.id === 'scripts-health'));
});
test('build controls use the expected API endpoints and streams', () => {
test('manifest defines unique link and build ids', () => {
const linkIds = manifest.links.map(l => l.id);
const buildIds = manifest.builds.map(b => b.id);
assert.equal(new Set(linkIds).size, linkIds.length);
assert.equal(new Set(buildIds).size, buildIds.length);
assert.ok(manifest.links.some(l => l.id === 'timesheet' && l.href === '/time-tracking/'));
assert.ok(manifest.builds.some(b => b.id === 'org_web'));
assert.ok(manifest.builds.some(b => b.id === 'emacs'));
});
test('manifest build cards expose stable dom ids', () => {
for (const build of manifest.builds) {
assert.ok(build.id, `build needs id`);
if (build.id === 'org_web') {
assert.equal(build.start.path, '/api/build-web');
assert.equal(build.logs.path, '/api/build-web/logs');
assert.equal(build.lastRunKey, 'org_web');
}
if (build.id === 'emacs') {
assert.equal(build.start.path, '/api/rerun-emacs');
assert.equal(build.lastRunKey, 'emacs');
}
}
});
test('dashboard module references manifest-driven API endpoints', () => {
const expectedEndpoints = [
'/api/health',
'/api/uptime',
@@ -97,48 +99,56 @@ test('build controls use the expected API endpoints and streams', () => {
'/api/build-web',
'/api/build-web/logs',
'/api/build-web/status',
'/api/build-roam',
'/api/build-roam/logs',
'/api/build-roam/status',
'/api/rerun-emacs',
'/api/rerun-emacs/logs',
'/api/rerun-emacs/status',
'/api/run-combined',
'/api/run-combined/logs',
'/api/run-combined/status'
'/api/rerun-emacs/status'
];
for (const endpoint of expectedEndpoints) {
assert.ok(html.includes(endpoint), `missing endpoint ${endpoint}`);
}
assert.match(html, /new EventSource\(url\)/);
assert.match(html, /addEventListener\(['"]log['"]/);
assert.match(html, /addEventListener\(['"]done['"]/);
});
test('primary buttons are wired to their handlers', () => {
const expectedWiring = [
["buildBtn", "runWebBuild"],
["orgRoamBtn", "runOrgRoamBuild"],
["orgRoamBtn2", "rerunEmacs"],
["combinedBtn", "runCombined"]
];
for (const [id, handler] of expectedWiring) {
assert.match(
html,
new RegExp(`getElementById\\(['"]${id}['"]\\)\\.addEventListener\\(['"]click['"], ${handler}\\)`)
assert.ok(
manifest.builds.some(b =>
b.start?.path === endpoint ||
b.status?.path === endpoint ||
b.logs?.path === endpoint
) ||
Object.values(manifest.status).includes(endpoint) ||
dashboardJs.includes(endpoint),
`missing endpoint ${endpoint}`
);
}
assert.match(dashboardJs, /new EventSource\(url\)/);
assert.match(dashboardJs, /addEventListener\(['"]log['"]/);
assert.match(dashboardJs, /\/api\/zone\/manifest/);
assert.match(dashboardJs, /data\/manifest\.json/);
assert.match(dashboardJs, /\/api\/zone\/status/);
assert.match(dashboardJs, /renderWidgets/);
assert.doesNotMatch(dashboardJs, /build-roam/);
assert.doesNotMatch(dashboardJs, /run-combined/);
});
test('retro layout classes and accessible structure are present', () => {
for (const className of ['topbar', 'dashboard-grid', 'rail', 'panel', 'run-grid', 'log-tools']) {
test('layout classes and accessible structure are present', () => {
for (const className of ['topbar', 'dashboard-grid', 'rail', 'panel', 'run-grid']) {
assert.match(html, new RegExp(`class=["'][^"']*\\b${className}\\b`), `missing .${className}`);
}
assert.match(dashboardJs, /log-tools/);
assert.match(html, /<main class=["']page["']/);
assert.match(html, /aria-label=["']Build runs["']/);
assert.match(html, /role=["']dialog["']/);
});
test('dashboard loads external stylesheets and manifest module', () => {
assert.match(html, /href=["']css\/shared\.css["']/);
assert.match(html, /href=["']css\/dashboard\.css["']/);
assert.match(html, /type=["']module["'][^>]*src=["']js\/dashboard\.js(\?[^"']*)?["']/);
});
test('gated manifest schema has required keys', () => {
for (const key of ['version', 'dashboard', 'links', 'builds', 'status']) {
assert.ok(key in manifest, `manifest missing ${key}`);
}
assert.equal(typeof manifest.dashboard.title, 'string');
assert.ok(Array.isArray(manifest.links) && manifest.links.length > 0);
assert.ok(Array.isArray(manifest.builds) && manifest.builds.length > 0);
});