igneum/tools/scene/legend.test.mjs

59 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// node --test tools/scene/legend.test.mjs (key-22, the project lead's screenshot of the app's chain key against /live, 7 October 2026)
// The key is ONE export: IgneumDag.legend({ mine }) in scene/live-dag.js (entries, order, token names, shapes); the app's
// chain card and the site's /live key render from it, so they cannot drift. This test fails when either key's entries,
// order or tokens differ from the export, and when a colour the scene draws on blocks has no key entry.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { execSync } from 'node:child_process';
const here = dirname(fileURLToPath(import.meta.url)), root = join(here, '../..');
const src = readFileSync(join(root, 'scene/live-dag.js'), 'utf8');
const win = { document: { hidden: false, addEventListener() {}, createElement: () => ({ style: {} }) }, addEventListener() {}, matchMedia: () => ({ matches: false, addEventListener() {} }), getComputedStyle: () => ({ getPropertyValue: () => '' }), devicePixelRatio: 1, innerWidth: 1280 };
new Function('window', 'document', src)(win, win.document);
const Dag = win.IgneumDag;
const SITE_ORDER = ['Pending', 'Included', 'Excluded', 'Selected chain', 'Proven', 'Locked checkpoint'];
// the entries a page's markup carries today, from its literal spans (a hard-coded key) or none (a rendered key)
const literal = (html, tag) => [...html.matchAll(new RegExp('<span><i class="' + tag + '[^>]*></i>([^<]+)</span>', 'g'))].map((m) => m[1].trim());
test('the export: Pending first, Included, Excluded, Selected chain, Proven, Locked checkpoint; the app adds Your blocks first; every entry names its token and shape (known-failed on 0.3.22)', () => {
assert.ok(Dag && typeof Dag.legend === 'function', 'IgneumDag.legend exists');
const site = Dag.legend({ mine: false }), app = Dag.legend({ mine: true });
assert.deepEqual(site.map((e) => e.label), SITE_ORDER);
assert.deepEqual(app.map((e) => e.label), ['Your blocks'].concat(SITE_ORDER));
for (const e of app) { assert.match(e.token, /^--[a-z-]+$/, e.label + ' token'); assert.ok(['square', 'circle', 'tick', 'ringed'].includes(e.shape), e.label + ' shape'); assert.ok(e.id); }
assert.equal(app[0].token, '--molten'); assert.equal(app[0].shape, 'ringed');
assert.equal(site.find((e) => e.label === 'Included').token, '--included', 'the key draws Included in the colour the scene draws it');
assert.equal(site.find((e) => e.label === 'Excluded').token, '--excluded');
assert.equal(site.find((e) => e.label === 'Locked checkpoint').shape, 'circle');
assert.equal(site.find((e) => e.label === 'Proven').shape, 'tick');
assert.equal(typeof Dag.renderLegend, 'function', 'one renderer for both pages');
});
test('every colour the scene draws on a block or a band has a key entry', () => {
const tokens = new Set(Dag.legend({ mine: true }).map((e) => e.token));
// the renderer's palette read: col={ember:c('--ember',…), …, blue:c('--included',…), red:c('--excluded',…)}
const read = [...src.matchAll(/c\('(--[a-z0-9-]+)'/g)].map((m) => m[1]);
const drawn = ['--included', '--excluded', '--ember', '--molten', '--bone', '--ash'];
for (const t of drawn) { assert.ok(read.includes(t), 'the scene reads ' + t); assert.ok(tokens.has(t), 'the key has an entry in ' + t); }
});
// the site tree on a release-* branch is frozen and deploys from master alone (tools/scene/sync.mjs's scope rule), so the
// site half of the check binds on master (or with SCENE_SYNC_SCOPE=site) and is a diagnostic elsewhere
const branch = (() => { try { return execSync('git rev-parse --abbrev-ref HEAD', { cwd: root, stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim(); } catch (e) { return ''; } })();
const siteBinds = process.env.SCENE_SYNC_SCOPE === 'site' || branch === 'master';
test('the app key and the site key are the export, not a hand-written list: no literal entries left, and any literal list that remains equals the export in labels and order', (t) => {
const app = readFileSync(join(root, 'app/igneum-app/ui/index.html'), 'utf8'), site = readFileSync(join(root, 'site/live.html'), 'utf8');
const appLit = literal(app, 'lg'), siteLit = literal(site, 'lgi');
assert.ok(/data-legend="mine"/.test(app), 'the app legend renders from IgneumDag.legend({ mine: true })');
assert.deepEqual(appLit, [], 'hand-written entries in the app key: ' + appLit.join(', '));
const siteOk = siteLit.length ? JSON.stringify(siteLit) === JSON.stringify(SITE_ORDER) : /data-legend/.test(site);
if (siteBinds) assert.ok(siteOk, 'the site key differs from the export: ' + (siteLit.length ? siteLit.join(', ') : 'no data-legend hook'));
else if (!siteOk) t.diagnostic('site/live.html still carries a hand-written key (' + siteLit.join(', ') + '); it binds on master once the site lane renders it from IgneumDag.renderLegend');
// the app's source line stays
assert.match(readFileSync(join(root, 'app/igneum-app/ui/app.js'), 'utf8'), /From Igneum’s observer\. Your blocks are ringed\./);
// the copies are the source (the sync gate says the same; this fails first when a legend edit misses the sync)
assert.equal(readFileSync(join(root, 'app/igneum-app/ui/live-dag.js'), 'utf8'), src, 'the app copy of live-dag.js is the scene source');
});