igneum/tools/scene/shard-words.test.mjs

54 lines
5.3 KiB
JavaScript

// node --test tools/scene/shard-words.test.mjs (7 October 2026: "why does the animation on site say no shard plan yet?")
// IgneumDag.shardWords(block, nowMs) is the one source of the shard sentence the scene's tooltip, the site's /live
// inspector and the app's inspector print. The empty case says what the block's own facts say: a block under 10 s old
// with no shards is loading its plan, an excluded block has nothing to prove, a pending block is not yet ordered, a
// blue block off the selected chain gets no plan; a planned shard with no prover is awaiting one, with the block's age;
// proving, verified and paid as before.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, existsSync } 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 NOW = 1_791_378_800_000;
// the site deploys from master alone and a release-* branch carries site/ frozen as it was cut, so the site inspector's
// check binds on master (or SCENE_SYNC_SCOPE=site) and is a diagnostic on a release branch
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';
const appBinds = existsSync(join(root, 'app/igneum-app/ui/live-dag.js')); // the app lives on the release branches
const block = (over) => ({ hash: 'ab12', ts: NOW - 42_000, color: 'blue', chain: true, miner: 'k1', parents: [], blue_score: 1, daa: 1, shards: [], locked: false, final: false, proven: false, ...over });
test('the four empty cases (known-failed on 2.0.5): loading under 10 s, excluded, not yet ordered, off the selected chain; an old chain block with no plan says how long', () => {
assert.ok(Dag && typeof Dag.shardWords === 'function', 'IgneumDag.shardWords exists');
assert.equal(Dag.shardWords(block({ ts: NOW - 4_000 }), NOW).summary, 'shard plan loading');
assert.equal(Dag.shardWords(block({ color: 'red', chain: false }), NOW).summary, 'excluded, nothing to prove');
assert.equal(Dag.shardWords(block({ color: 'pending', chain: false }), NOW).summary, 'not yet ordered');
assert.equal(Dag.shardWords(block({ chain: false }), NOW).summary, 'off the selected chain, no shards planned');
assert.equal(Dag.shardWords(block(), NOW).summary, 'no shard plan after 42 s');
for (const s of [Dag.shardWords(block({ ts: NOW - 4_000 }), NOW), Dag.shardWords(block({ color: 'red' }), NOW)]) assert.deepEqual(s.items, []);
assert.equal(/no shard plan yet/.test(src), false, 'the old words are gone from the scene');
});
test('a planned shard with no prover is awaiting one, with the block age; assigned, proving, verified and paid as before', (t) => {
const sh = [{ i: 0, n: 4, state: 'planned', prover: null }, { i: 1, n: 4, state: 'planned', prover: 'faa34a1a' }, { i: 2, n: 4, state: 'proving', prover: 'b2' }, { i: 3, n: 4, state: 'paid', prover: 'c3', payout: 4.5532 }];
const w = Dag.shardWords(block({ shards: sh }), NOW);
assert.deepEqual(w.items.map((x) => x.word), ['awaiting a prover · 42 s', 'assigned to faa34a1a', 'proving by b2', 'paid 4.5532 IGN']);
assert.equal(w.summary, '1 paid, 1 proving, 1 assigned, 1 awaiting a prover · 42 s');
const old = Dag.shardWords(block({ ts: NOW - 400_000, shards: [{ state: 'planned', prover: null }] }), NOW);
assert.equal(old.items[0].word, 'awaiting a prover · 7 min'); assert.equal(old.summary, '1 awaiting a prover · 7 min');
const done = Dag.shardWords(block({ shards: [{ state: 'verified' }, { state: 'paid', payout: 1 }] }), NOW);
assert.equal(done.summary, '1 paid, 1 verified'); assert.deepEqual(done.items.map((x) => x.word), ['verified', 'paid 1 IGN']);
// the tooltip line uses it (words() is an instance method, so the source line is the check)
assert.match(src, /'Shards: '\+shardWords\(b,nowMs\)\.summary/, 'the tooltip takes the shared sentence');
// both inspectors use it: no hand-written shard sentence left
const live = readFileSync(join(root, 'site/live.html'), 'utf8'), siteOk = !/no shard plan yet/i.test(live) && /IgneumDag\.shardWords\(/.test(live);
if (siteBinds) assert.ok(siteOk, 'the site inspector still carries its own shard sentence');
else if (!siteOk) t.diagnostic('site/live.html on this release branch is the frozen cut; the inspector check binds on master, where the site deploys from');
if (appBinds) { const app = readFileSync(join(root, 'app/igneum-app/ui/app.js'), 'utf8'); assert.equal(/no shard plan yet/i.test(app), false, 'the app inspector'); assert.match(app, /IgneumDag\.shardWords\(/, 'the app inspector takes the shared sentence'); }
else t.diagnostic('no app scene copy in this tree (master): the app half binds on the release branches');
});