/** * Module-split scenarios for the Statum client. * * Each scenario runs in a FRESH QuickJS context (run.sh invokes this script * once per scenario) and loads a specific subset of the module files in a * specific order, proving: * * 1 core-alone - window.statum (headless: state/directives/boot), * overrides, driver-less render, NO registerAttribute * 2 core headless boot- stubbed fetch -> entrypoint state applied, no walk * 3 core+document - registry installed, handler order pinned * (stm-prefill first), statum.document API, * statum.forms gone * 4 core+doc+markdown - full order, statum.markdown API, test hatch * 5 core+markdown - cherry-pick without the walk (crash-free; queued * registration) * 6 document alone - guarded IIFE: console.error, nothing attached * 7 double register - warns, order unchanged * 8 unknown anchor - warns, registration position kept * * Usage: qjs tests/split/scenarios.js <1..8> */ import * as std from 'std'; import { installShim } from './shim.js'; // Repo root, as a CWD-relative path. run.sh (like tests/markdown/run.sh) // always `cd`s to the repo root before invoking qjs, and QuickJS' // std.loadFile resolves relative paths against the process CWD — the // established in-repo idiom (see tests/markdown/spec-test.js). var ROOT = '.'; var scenario = scriptArgs.length > 1 ? scriptArgs[1] : ''; function fail(msg) { std.err.puts('FAIL [' + scenario + '] ' + msg + '\n'); std.exit(1); } function check(cond, msg) { if (!cond) fail(msg); } function load(name) { (0, eval)(std.loadFile(ROOT + '/js/' + name)); } function handlerNames() { return globalThis.statum._bridge.attributeHandlers.map(function (h) { return h.name; }); } function installEntryStub() { var snap = { slot: { key: 'k-s', type: 's', scope: 'page' }, public: { v: 42 } }; globalThis.__shim.setFetch(function () { return globalThis.__shim.okResponse([{ type: 'set', snapshot_frame: { content: JSON.stringify(snap), signature: 'sig-s-1' } }]); }); } async function spin(n) { for (var i = 0; i < (n || 30); i++) await Promise.resolve(); } installShim(); var log = globalThis.__shim.log; if (scenario === '1') { load('statum-core.js'); var s = globalThis.statum; check(s, 'window.statum is not defined'); check(s.version === '0.1.0', 'version missing'); check(typeof s.config === 'object' && s.config.entrypointUrl === '/_statum/entrypoint', 'config missing'); check(typeof s.registerAttribute === 'undefined', 'registerAttribute leaked into headless core (it is document API now)'); check(s._bridge, '_bridge is not present'); check(typeof s.Error === 'function', 'statum.Error missing'); check(typeof s._bridge.registerAttribute === 'function', 'bridge.registerAttribute (the pre-document queue) is not a function'); check(log.errors.length === 0, 'unexpected console.error at load: ' + JSON.stringify(log.errors)); check(log.warns.length === 0, 'unexpected console.warn at load: ' + JSON.stringify(log.warns)); // The three override pairs round-trip a function and null out non-functions. var fn = function () {}; var pairs = ['onConfirmHandler', 'onNotifyHandler', 'onErrorHandler']; pairs.forEach(function (p) { s[p] = fn; check(s[p] === fn, p + ' did not round-trip the function'); s[p] = 42; check(s[p] === null, p + ' did not null out a non-function'); s[p] = 'nope'; check(s[p] === null, p + ' did not null out a non-function (string)'); }); // No render driver installed: a render cycle must not crash, and the // pre-document registration queue stays empty. s._bridge.renderAll(); check(log.errors.length === 0, 'renderAll without a driver errored: ' + JSON.stringify(log.errors)); } else if (scenario === '2') { // Headless boot: stub the entrypoint fetch, init, and prove state was // applied with no DOM module loaded. load('statum-core.js'); var s = globalThis.statum; installEntryStub(); await s.init(); await spin(); check(s.read('s') && s.read('s').v === 42, 'headless init did not apply entrypoint state: ' + JSON.stringify(s.state())); check(s._bridge.entrypointLoaded(), 'entrypointLoaded is not exposed as true after boot'); check(log.errors.length === 0, 'headless boot errored: ' + JSON.stringify(log.errors)); check(log.warns.length === 0, 'headless boot warned: ' + JSON.stringify(log.warns)); } else if (scenario === '3') { load('statum-core.js'); load('statum-document.js'); var s = globalThis.statum; check(typeof s.registerAttribute === 'function', 'registerAttribute is not public on the document module'); var names = handlerNames(); check(JSON.stringify(names) === JSON.stringify( ['stm-prefill', 'stm-class.', 'stm-attribute.', 'stm-disabled', 'stm-text', 'stm-html']), 'handler order is ' + JSON.stringify(names)); check(s.document && typeof s.document.reset === 'function', 'statum.document.reset missing'); check(typeof s.document.markDirty === 'function' && typeof s.document.isDirty === 'function', 'statum.document.markDirty/isDirty missing'); check(s.forms === undefined, 'statum.forms must be gone'); check(s._bridge.formsDirty instanceof WeakSet, 'bridge.formsDirty missing'); // The driver is installed: renderAll walks document.body and applies a // binding. Scope comes from the (empty) store, so use a literal expression. var el = globalThis.__shim.makeElement('p'); el.setAttribute('stm-text', "'driven'"); globalThis.__shim.body.appendChild(el); s._bridge.renderAll(); check(el.textContent === 'driven', 'render driver did not apply stm-text (got ' + JSON.stringify(el.textContent) + ')'); check(log.errors.length === 0, 'unexpected console.error: ' + JSON.stringify(log.errors)); } else if (scenario === '4') { load('statum-core.js'); load('statum-document.js'); load('statum-markdown.js'); var s = globalThis.statum; var names = handlerNames(); check(JSON.stringify(names) === JSON.stringify( ['stm-prefill', 'stm-class.', 'stm-attribute.', 'stm-disabled', 'stm-text', 'stm-html', 'stm-markdown', 'stm-h-demotion']), 'full handler order is ' + JSON.stringify(names)); // "directly after" from the ordered list above. check(names.indexOf('stm-markdown') === names.indexOf('stm-html') + 1, 'stm-markdown is not directly after stm-html'); check(names.indexOf('stm-h-demotion') === names.indexOf('stm-markdown') + 1, 'stm-h-demotion is not directly after stm-markdown'); // Programmatic API shares the binding's engine. The engine renders blocks // with a trailing newline (CommonMark serialisation), so '# x' -> '