orbit/dev/tutorial.test.mjs

136 lines
6.8 KiB
JavaScript

/**
* TutorialGuide test (dev tool, run with Node — no browser):
*
* node dev/tutorial.test.mjs
*
* Pins the starter quest's POINTING FINGER beat table
* (js/quests/TutorialGuide.js — the pure core of the opening-arc
* guidance, applied per frame by GameScene._applyTutorialHighlight onto
* ActionBar.setHighlight / ResearchWindow.setHighlight):
* - the welcome sequence acknowledged (oac-intro → oac-tether →
* oac-scan all on the run's comms played-ledger) and SCAN not yet
* pressed → 'scan';
* - OAC's 'research a Tether Level 2 upgrade' line (oac-upgrade) heard
* → 'research' (console closed) / 'console' (console open);
* - the goal node's research in flight / done → REST (null);
* - missing records and the data's master switches degrade to rest;
* - the data the beats stand on (data/comms.json's newGame/firstScan
* triggers) is what the module's constants name.
* The rim's drawing is the surface's job (ActionBar's update gate —
* dev/actionbar.test.mjs pins the standby hiding).
*/
import fs from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import assert from 'node:assert/strict';
import { tutorialTarget, WELCOME_COMMS, UPGRADE_COMM } from '../js/quests/TutorialGuide.js';
const dataDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'data');
const comms = JSON.parse(fs.readFileSync(join(dataDir, 'comms.json'), 'utf8'));
const quests = JSON.parse(fs.readFileSync(join(dataDir, 'quests.json'), 'utf8'));
// ---------------------------------------------------------------------------
// The data the beats stand on — the module's constants name REAL comms,
// in the triggers' real order (a typo here would be a beat that never
// arms — the finger would rest where it should be pointing).
// ---------------------------------------------------------------------------
{
const byTrigger = (t) => comms.comms.filter((c) => c.trigger === t).map((c) => c.id);
assert.deepEqual(
WELCOME_COMMS,
byTrigger('newGame'),
'WELCOME_COMMS = the newGame sequence, in order',
);
assert.ok(byTrigger('firstScan').includes(UPGRADE_COMM), 'UPGRADE_COMM is a firstScan comm');
assert.equal(UPGRADE_COMM, 'oac-upgrade', 'the scan reaction is oac-upgrade');
// The starter quest's research check is the arc's goal — the tutorial's
// console beat (category + node) points at the same node.
const starter = quests.quests.find((q) => q.starter === true);
const researchCheck = starter.checks.find((c) => c.type === 'research');
assert.equal(quests.tutorial.console.category, researchCheck.category, 'tutorial category = the starter check\u2019s category');
assert.equal(quests.tutorial.console.node, researchCheck.node, 'tutorial node = the starter check\u2019s node');
assert.ok(quests.tutorial.scan.slot, 'the scan beat names a slot');
assert.ok(quests.tutorial.research.slot, 'the research beat names a slot');
}
// A run's records: the starter granted + the played-ledger as an id set.
const records = (...ids) => ({
starterGranted: true,
hasPlayed: (id) => ids.includes(id),
});
// ---------------------------------------------------------------------------
// Fresh run — the comms own the screen (the welcome is mid-sequence).
// ---------------------------------------------------------------------------
assert.equal(tutorialTarget({}), null, 'no records → rest');
assert.equal(tutorialTarget(records()), null, 'a fresh ledger → rest (the comms own the screen)');
assert.equal(tutorialTarget(records('oac-intro')), null, 'welcome beat one playing → rest');
assert.equal(tutorialTarget(records('oac-intro', 'oac-tether')), null, 'welcome beat two playing → rest');
// ---------------------------------------------------------------------------
// Beat 1 — the welcome is acknowledged: the deck's SCAN slot is the finger.
// ---------------------------------------------------------------------------
assert.equal(tutorialTarget(records('oac-intro', 'oac-tether', 'oac-scan')), 'scan', 'welcome ack\u2019d, SCAN unpressed → scan');
// The player pressed SCAN (the scene's one-shot): the beat is past — the
// finger rests until oac-upgrade starts (which re-arms it, below).
assert.equal(
tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan'), scanPressed: true }),
null,
'welcome ack\u2019d, SCAN already pressed → rest (the comm is coming)',
);
// ---------------------------------------------------------------------------
// Beat 2 — oac-upgrade is on the ledger (the 'research a Tether Level 2'
// line): the deck's RESEARCH slot (console closed) or the console itself
// (console open — the tab's rim, then the node's, by the window).
// ---------------------------------------------------------------------------
assert.equal(
tutorialTarget(records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade')),
'research',
'oac-upgrade heard, console closed → research',
);
assert.equal(
tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade'), consoleOpen: true }),
'console',
'oac-upgrade heard, console open → console',
);
assert.equal(
tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade'), consoleOpen: true, scanPressed: true }),
'console',
'the scan one-shot is irrelevant once the upgrade line is heard',
);
// The scan beat's slot and the research beat's slot are the data's own
// names (GameScene reads them — the pure table just picks the beat).
assert.notEqual(quests.tutorial.scan.slot, quests.tutorial.research.slot, 'the two deck beats point at different slots');
// ---------------------------------------------------------------------------
// REST — the arc's goal is underway / done: the finger steps off.
// ---------------------------------------------------------------------------
assert.equal(
tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan', 'oac-upgrade'), nodeResolving: true }),
null,
'the goal research in flight → rest',
);
assert.equal(tutorialTarget({ ...records(), nodeResolving: true }), null, 'goal done on a fresh ledger → rest');
// ---------------------------------------------------------------------------
// Degradation — missing records / the data's master switches → rest.
// ---------------------------------------------------------------------------
assert.equal(
tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan'), starterGranted: false }),
null,
'the starter quest not granted → rest',
);
assert.equal(
tutorialTarget({ starterGranted: true }),
null,
'no played-ledger to read → rest (never a throw)',
);
assert.equal(
tutorialTarget({ ...records('oac-intro', 'oac-tether', 'oac-scan'), enabled: false }),
null,
'the data\u2019s master switch off → rest',
);
console.log('all TutorialGuide checks passed \u2714');