/**
* 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, seam, overrides, driver-less render
* 2 core+document - render driver installed, handler order pinned
* 3 core+doc+markdown - full order, statum.markdown API, test hatch
* 4 core+markdown - cherry-pick without the walk (crash-free)
* 5 document alone - guarded IIFE: console.error, nothing attached
* 6 double register - warns, order unchanged
* 7 unknown after - warns, registration position kept
*
* Usage: qjs tests/split/scenarios.js <1|2|3|4|5|6|7>
*/
import * as std from 'std';
import { installShim } from './shim.js';
var ROOT = '/workspace/task-214/Statum';
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; });
}
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 === 'function', 'registerAttribute is not a function');
check(s._bridge, '_bridge is not present');
check(typeof s.Error === 'function', 'statum.Error missing');
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.
check(s._bridge.attributeHandlers.length === 0, 'handlers registered without modules');
s._bridge.renderAll();
check(log.errors.length === 0, 'renderAll without a driver errored: ' + JSON.stringify(log.errors));
} else if (scenario === '2') {
load('statum-core.js');
load('statum-document.js');
var s = globalThis.statum;
var names = handlerNames();
check(JSON.stringify(names) === JSON.stringify(
['stm-class.', 'stm-attribute.', 'stm-disabled', 'stm-text', 'stm-html']),
'handler order is ' + JSON.stringify(names));
// 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 === '3') {
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-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' -> '
x
\n'.
check(s.markdown, 'statum.markdown is not defined');
check(s.markdown.render('# x') === 'x
\n',
'markdown.render("# x") = ' + JSON.stringify(s.markdown.render('# x')));
check(typeof s.markdown.renderTo === 'function', 'markdown.renderTo is not a function');
var el = globalThis.__shim.makeElement('div');
s.markdown.renderTo(el, '# y');
check(el.innerHTML === 'y
\n',
'renderTo innerHTML = ' + JSON.stringify(el.innerHTML));
// The binding path and the programmatic path agree. (Attribute values are
// expressions, hence the quoted string; an unparseable value yields
// undefined and leaves the element untouched by design.)
el.innerHTML = '';
el.setAttribute('stm-markdown', "'# y'");
globalThis.__shim.body.appendChild(el);
// Drive the walk through the driver via a fresh render cycle.
var bridge = s._bridge;
bridge.renderAll();
check(el.innerHTML === 'y
\n',
'stm-markdown binding innerHTML = ' + JSON.stringify(el.innerHTML));
// Test hatch (the shim sets __STATUM_TEST__).
check(typeof s._testMarkdown === 'function', '_testMarkdown is not exposed');
check(log.errors.length === 0, 'unexpected console.error: ' + JSON.stringify(log.errors));
} else if (scenario === '4') {
// Cherry-pick: markdown without the walk module. 'after: stm-html' cannot
// resolve (document is absent) so it warns and keeps registration order —
// that is the designed degradation, not a failure.
load('statum-core.js');
load('statum-markdown.js');
var s = globalThis.statum;
check(s.markdown.render('# x') === 'x
\n', 'markdown.render failed without statum-document.js');
var names = handlerNames();
check(names.indexOf('stm-markdown') !== -1 && names.indexOf('stm-h-demotion') !== -1,
'handlers not registered: ' + JSON.stringify(names));
check(names.indexOf('stm-h-demotion') === names.indexOf('stm-markdown') + 1,
'demotion constraint broke without the walk: ' + JSON.stringify(names));
check(log.errors.length === 0, 'unexpected console.error: ' + JSON.stringify(log.errors));
check(log.warns.length === 1 && log.warns[0].indexOf('stm-html') !== -1,
'expected one unresolvable-constraint warning, got ' + JSON.stringify(log.warns));
// A render cycle with no driver still runs.
s._bridge.renderAll();
check(log.errors.length === 0, 'renderAll errored without a driver: ' + JSON.stringify(log.errors));
} else if (scenario === '5') {
// document without core: the guarded IIFE must record one console.error,
// not throw, and attach nothing.
load('statum-document.js');
check(log.errors.length === 1, 'expected exactly one console.error, got ' + JSON.stringify(log.errors));
check(log.errors[0].indexOf('statum-document.js requires statum-core.js') !== -1,
'error message: ' + JSON.stringify(log.errors[0]));
check(!globalThis.statum, 'document attached window.statum without core');
// Same for markdown.
log.errors.length = 0;
load('statum-markdown.js');
check(log.errors.length === 1, 'expected one console.error from markdown, got ' + JSON.stringify(log.errors));
check(log.errors[0].indexOf('statum-markdown.js requires statum-core.js') !== -1,
'error message: ' + JSON.stringify(log.errors[0]));
check(!globalThis.statum, 'markdown attached window.statum without core');
} else if (scenario === '6') {
load('statum-core.js');
load('statum-document.js');
var before = handlerNames();
globalThis.statum.registerAttribute('stm-text', function () {});
check(log.warns.length === 1, 'double registration did not warn: ' + JSON.stringify(log.warns));
check(log.warns[0].indexOf('already registered') !== -1, 'warn text: ' + JSON.stringify(log.warns[0]));
var after = handlerNames();
check(JSON.stringify(before) === JSON.stringify(after),
'order changed on double registration: ' + JSON.stringify(after));
} else if (scenario === '7') {
load('statum-core.js');
load('statum-document.js');
var before = handlerNames();
globalThis.statum.registerAttribute('stm-prefill', function () {}, { after: 'stm-nope' });
check(log.warns.length === 1, 'unresolvable constraint did not warn: ' + JSON.stringify(log.warns));
check(log.warns[0].indexOf('stm-nope') !== -1, 'warn names the missing anchor: ' + JSON.stringify(log.warns[0]));
var after = handlerNames();
check(after.length === before.length + 1, 'handler was not registered');
check(after[after.length - 1] === 'stm-prefill',
'handler did not keep registration position: ' + JSON.stringify(after));
} else {
fail('unknown scenario: ' + scenario);
}
print('scenario ' + scenario + ': OK');