scenarios.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * Module-split scenarios for the Statum client.
  3. *
  4. * Each scenario runs in a FRESH QuickJS context (run.sh invokes this script
  5. * once per scenario) and loads a specific subset of the module files in a
  6. * specific order, proving:
  7. *
  8. * 1 core-alone - window.statum, seam, overrides, driver-less render
  9. * 2 core+document - render driver installed, handler order pinned
  10. * 3 core+doc+markdown - full order, statum.markdown API, test hatch
  11. * 4 core+markdown - cherry-pick without the walk (crash-free)
  12. * 5 document alone - guarded IIFE: console.error, nothing attached
  13. * 6 double register - warns, order unchanged
  14. * 7 unknown after - warns, registration position kept
  15. *
  16. * Usage: qjs tests/split/scenarios.js <1|2|3|4|5|6|7>
  17. */
  18. import * as std from 'std';
  19. import { installShim } from './shim.js';
  20. var ROOT = '/workspace/task-214/Statum';
  21. var scenario = scriptArgs.length > 1 ? scriptArgs[1] : '';
  22. function fail(msg) {
  23. std.err.puts('FAIL [' + scenario + '] ' + msg + '\n');
  24. std.exit(1);
  25. }
  26. function check(cond, msg) {
  27. if (!cond) fail(msg);
  28. }
  29. function load(name) {
  30. (0, eval)(std.loadFile(ROOT + '/js/' + name));
  31. }
  32. function handlerNames() {
  33. return globalThis.statum._bridge.attributeHandlers.map(function (h) { return h.name; });
  34. }
  35. installShim();
  36. var log = globalThis.__shim.log;
  37. if (scenario === '1') {
  38. load('statum-core.js');
  39. var s = globalThis.statum;
  40. check(s, 'window.statum is not defined');
  41. check(s.version === '0.1.0', 'version missing');
  42. check(typeof s.config === 'object' && s.config.entrypointUrl === '/_statum/entrypoint',
  43. 'config missing');
  44. check(typeof s.registerAttribute === 'function', 'registerAttribute is not a function');
  45. check(s._bridge, '_bridge is not present');
  46. check(typeof s.Error === 'function', 'statum.Error missing');
  47. check(log.errors.length === 0, 'unexpected console.error at load: ' + JSON.stringify(log.errors));
  48. check(log.warns.length === 0, 'unexpected console.warn at load: ' + JSON.stringify(log.warns));
  49. // The three override pairs round-trip a function and null out non-functions.
  50. var fn = function () {};
  51. var pairs = ['onConfirmHandler', 'onNotifyHandler', 'onErrorHandler'];
  52. pairs.forEach(function (p) {
  53. s[p] = fn;
  54. check(s[p] === fn, p + ' did not round-trip the function');
  55. s[p] = 42;
  56. check(s[p] === null, p + ' did not null out a non-function');
  57. s[p] = 'nope';
  58. check(s[p] === null, p + ' did not null out a non-function (string)');
  59. });
  60. // No render driver installed: a render cycle must not crash.
  61. check(s._bridge.attributeHandlers.length === 0, 'handlers registered without modules');
  62. s._bridge.renderAll();
  63. check(log.errors.length === 0, 'renderAll without a driver errored: ' + JSON.stringify(log.errors));
  64. } else if (scenario === '2') {
  65. load('statum-core.js');
  66. load('statum-document.js');
  67. var s = globalThis.statum;
  68. var names = handlerNames();
  69. check(JSON.stringify(names) === JSON.stringify(
  70. ['stm-class.', 'stm-attribute.', 'stm-disabled', 'stm-text', 'stm-html']),
  71. 'handler order is ' + JSON.stringify(names));
  72. // The driver is installed: renderAll walks document.body and applies a
  73. // binding. Scope comes from the (empty) store, so use a literal expression.
  74. var el = globalThis.__shim.makeElement('p');
  75. el.setAttribute('stm-text', "'driven'");
  76. globalThis.__shim.body.appendChild(el);
  77. s._bridge.renderAll();
  78. check(el.textContent === 'driven',
  79. 'render driver did not apply stm-text (got ' + JSON.stringify(el.textContent) + ')');
  80. check(log.errors.length === 0, 'unexpected console.error: ' + JSON.stringify(log.errors));
  81. } else if (scenario === '3') {
  82. load('statum-core.js');
  83. load('statum-document.js');
  84. load('statum-markdown.js');
  85. var s = globalThis.statum;
  86. var names = handlerNames();
  87. check(JSON.stringify(names) === JSON.stringify(
  88. ['stm-class.', 'stm-attribute.', 'stm-disabled', 'stm-text', 'stm-html',
  89. 'stm-markdown', 'stm-h-demotion']),
  90. 'full handler order is ' + JSON.stringify(names));
  91. // "directly after" from the ordered list above.
  92. check(names.indexOf('stm-markdown') === names.indexOf('stm-html') + 1,
  93. 'stm-markdown is not directly after stm-html');
  94. check(names.indexOf('stm-h-demotion') === names.indexOf('stm-markdown') + 1,
  95. 'stm-h-demotion is not directly after stm-markdown');
  96. // Programmatic API shares the binding's engine. The engine renders blocks
  97. // with a trailing newline (CommonMark serialisation), so '# x' -> '<h1>x</h1>\n'.
  98. check(s.markdown, 'statum.markdown is not defined');
  99. check(s.markdown.render('# x') === '<h1>x</h1>\n',
  100. 'markdown.render("# x") = ' + JSON.stringify(s.markdown.render('# x')));
  101. check(typeof s.markdown.renderTo === 'function', 'markdown.renderTo is not a function');
  102. var el = globalThis.__shim.makeElement('div');
  103. s.markdown.renderTo(el, '# y');
  104. check(el.innerHTML === '<h1>y</h1>\n',
  105. 'renderTo innerHTML = ' + JSON.stringify(el.innerHTML));
  106. // The binding path and the programmatic path agree. (Attribute values are
  107. // expressions, hence the quoted string; an unparseable value yields
  108. // undefined and leaves the element untouched by design.)
  109. el.innerHTML = '';
  110. el.setAttribute('stm-markdown', "'# y'");
  111. globalThis.__shim.body.appendChild(el);
  112. // Drive the walk through the driver via a fresh render cycle.
  113. var bridge = s._bridge;
  114. bridge.renderAll();
  115. check(el.innerHTML === '<h1>y</h1>\n',
  116. 'stm-markdown binding innerHTML = ' + JSON.stringify(el.innerHTML));
  117. // Test hatch (the shim sets __STATUM_TEST__).
  118. check(typeof s._testMarkdown === 'function', '_testMarkdown is not exposed');
  119. check(log.errors.length === 0, 'unexpected console.error: ' + JSON.stringify(log.errors));
  120. } else if (scenario === '4') {
  121. // Cherry-pick: markdown without the walk module. 'after: stm-html' cannot
  122. // resolve (document is absent) so it warns and keeps registration order —
  123. // that is the designed degradation, not a failure.
  124. load('statum-core.js');
  125. load('statum-markdown.js');
  126. var s = globalThis.statum;
  127. check(s.markdown.render('# x') === '<h1>x</h1>\n', 'markdown.render failed without statum-document.js');
  128. var names = handlerNames();
  129. check(names.indexOf('stm-markdown') !== -1 && names.indexOf('stm-h-demotion') !== -1,
  130. 'handlers not registered: ' + JSON.stringify(names));
  131. check(names.indexOf('stm-h-demotion') === names.indexOf('stm-markdown') + 1,
  132. 'demotion constraint broke without the walk: ' + JSON.stringify(names));
  133. check(log.errors.length === 0, 'unexpected console.error: ' + JSON.stringify(log.errors));
  134. check(log.warns.length === 1 && log.warns[0].indexOf('stm-html') !== -1,
  135. 'expected one unresolvable-constraint warning, got ' + JSON.stringify(log.warns));
  136. // A render cycle with no driver still runs.
  137. s._bridge.renderAll();
  138. check(log.errors.length === 0, 'renderAll errored without a driver: ' + JSON.stringify(log.errors));
  139. } else if (scenario === '5') {
  140. // document without core: the guarded IIFE must record one console.error,
  141. // not throw, and attach nothing.
  142. load('statum-document.js');
  143. check(log.errors.length === 1, 'expected exactly one console.error, got ' + JSON.stringify(log.errors));
  144. check(log.errors[0].indexOf('statum-document.js requires statum-core.js') !== -1,
  145. 'error message: ' + JSON.stringify(log.errors[0]));
  146. check(!globalThis.statum, 'document attached window.statum without core');
  147. // Same for markdown.
  148. log.errors.length = 0;
  149. load('statum-markdown.js');
  150. check(log.errors.length === 1, 'expected one console.error from markdown, got ' + JSON.stringify(log.errors));
  151. check(log.errors[0].indexOf('statum-markdown.js requires statum-core.js') !== -1,
  152. 'error message: ' + JSON.stringify(log.errors[0]));
  153. check(!globalThis.statum, 'markdown attached window.statum without core');
  154. } else if (scenario === '6') {
  155. load('statum-core.js');
  156. load('statum-document.js');
  157. var before = handlerNames();
  158. globalThis.statum.registerAttribute('stm-text', function () {});
  159. check(log.warns.length === 1, 'double registration did not warn: ' + JSON.stringify(log.warns));
  160. check(log.warns[0].indexOf('already registered') !== -1, 'warn text: ' + JSON.stringify(log.warns[0]));
  161. var after = handlerNames();
  162. check(JSON.stringify(before) === JSON.stringify(after),
  163. 'order changed on double registration: ' + JSON.stringify(after));
  164. } else if (scenario === '7') {
  165. load('statum-core.js');
  166. load('statum-document.js');
  167. var before = handlerNames();
  168. globalThis.statum.registerAttribute('stm-prefill', function () {}, { after: 'stm-nope' });
  169. check(log.warns.length === 1, 'unresolvable constraint did not warn: ' + JSON.stringify(log.warns));
  170. check(log.warns[0].indexOf('stm-nope') !== -1, 'warn names the missing anchor: ' + JSON.stringify(log.warns[0]));
  171. var after = handlerNames();
  172. check(after.length === before.length + 1, 'handler was not registered');
  173. check(after[after.length - 1] === 'stm-prefill',
  174. 'handler did not keep registration position: ' + JSON.stringify(after));
  175. } else {
  176. fail('unknown scenario: ' + scenario);
  177. }
  178. print('scenario ' + scenario + ': OK');