shim.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * DOM shim for the module-split tests.
  3. *
  4. * Extends the markdown harness shim with a CAPTURING console (so scenarios
  5. * can assert on the `[statum]` warnings/errors the seam emits) and a small
  6. * element tree builder. Like tests/markdown/shim.js, `document.readyState`
  7. * is 'loading' so statum-core.js only registers a DOMContentLoaded listener
  8. * instead of booting.
  9. */
  10. export function installShim() {
  11. var listeners = {};
  12. var log = { errors: [], warns: [], messages: [] };
  13. function makeElement(tag) {
  14. return {
  15. tagName: (tag || 'div').toUpperCase(),
  16. style: {}, dataset: {}, attributes: [],
  17. children: [],
  18. textContent: '', innerHTML: '',
  19. get firstChild() { return this.children[0] || null; },
  20. classList: { add: function () {}, remove: function () {}, contains: function () { return false; } },
  21. contains: function () { return false; },
  22. appendChild: function (c) { this.children.push(c); return c; },
  23. removeChild: function (c) {
  24. var i = this.children.indexOf(c);
  25. if (i >= 0) this.children.splice(i, 1);
  26. return c;
  27. },
  28. remove: function () {
  29. if (this.parentNode) this.parentNode.removeChild(this);
  30. },
  31. replaceWith: function () {},
  32. cloneNode: function () { return makeElement(this.tagName); },
  33. nextElementSibling: null,
  34. parentNode: null,
  35. addEventListener: function () {}, removeEventListener: function () {},
  36. setAttribute: function (n, v) {
  37. this.attributes = this.attributes.filter(function (a) { return a.name !== n; });
  38. this.attributes.push({ name: n, value: String(v) });
  39. },
  40. removeAttribute: function (n) {
  41. this.attributes = this.attributes.filter(function (a) { return a.name !== n; });
  42. },
  43. hasAttribute: function (n) {
  44. return this.attributes.some(function (a) { return a.name === n; });
  45. },
  46. getAttribute: function (n) {
  47. var hit = this.attributes.filter(function (a) { return a.name === n; })[0];
  48. return hit ? hit.value : null;
  49. },
  50. querySelectorAll: function () { return []; },
  51. querySelector: function () { return null; }
  52. };
  53. }
  54. globalThis.__shim = {
  55. log: log,
  56. makeElement: makeElement,
  57. body: makeElement('body')
  58. };
  59. globalThis.console = {
  60. log: function () { log.messages.push(['log', Array.prototype.slice.call(arguments)]); },
  61. info: function () { log.messages.push(['info', Array.prototype.slice.call(arguments)]); },
  62. debug: function () { log.messages.push(['debug', Array.prototype.slice.call(arguments)]); },
  63. warn: function () {
  64. log.warns.push(Array.prototype.slice.call(arguments).join(' '));
  65. log.messages.push(['warn', Array.prototype.slice.call(arguments)]);
  66. },
  67. error: function () {
  68. log.errors.push(Array.prototype.slice.call(arguments).join(' '));
  69. log.messages.push(['error', Array.prototype.slice.call(arguments)]);
  70. }
  71. };
  72. globalThis.document = {
  73. readyState: 'loading', cookie: '',
  74. head: makeElement('head'), body: globalThis.__shim.body,
  75. documentElement: makeElement('html'),
  76. addEventListener: function (t, f) { (listeners[t] = listeners[t] || []).push(f); },
  77. removeEventListener: function () {},
  78. getElementById: function () { return null; },
  79. querySelectorAll: function () { return []; },
  80. querySelector: function () { return null; },
  81. createElement: function (tag) { return makeElement(tag); },
  82. createTextNode: function (t) { return { nodeType: 3, textContent: t }; }
  83. };
  84. globalThis.window = globalThis;
  85. globalThis.location = { href: 'http://localhost/', protocol: 'http:', addEventListener: function () {} };
  86. globalThis.navigator = { userAgent: 'qjs' };
  87. globalThis.addEventListener = function (t, f) { (listeners[t] = listeners[t] || []).push(f); };
  88. globalThis.removeEventListener = function () {};
  89. globalThis.__STATUM_TEST__ = true;
  90. // Minimal fetch/Headers stub so scenarios can prove core's headless boot
  91. // (the full-featured DOM version lives in tests/dom/shim.js).
  92. var fetchHandler = null;
  93. function Headers() { this._h = {}; }
  94. Headers.prototype.set = function (n, v) { this._h[String(n).toLowerCase()] = String(v); };
  95. Headers.prototype.append = function (n, v) {
  96. var k = String(n).toLowerCase();
  97. this._h[k] = this._h[k] ? this._h[k] + ', ' + v : String(v);
  98. };
  99. Headers.prototype.get = function (n) {
  100. var k = Object.keys(this._h).filter(function (x) { return x === String(n).toLowerCase(); })[0];
  101. return k !== undefined ? this._h[k] : null;
  102. };
  103. globalThis.Headers = Headers;
  104. function okResponse(directives) {
  105. return {
  106. ok: true,
  107. status: 200,
  108. headers: { get: function (n) { return String(n).toLowerCase() === 'content-type' ? 'application/vnd.statum+json' : null; } },
  109. json: function () { return Promise.resolve(directives || []); }
  110. };
  111. }
  112. globalThis.fetch = function (url, init) {
  113. var res = fetchHandler ? fetchHandler(String(url), init || {}) : okResponse([]);
  114. return Promise.resolve(res);
  115. };
  116. globalThis.__shim.setFetch = function (fn) { fetchHandler = fn; };
  117. globalThis.__shim.okResponse = okResponse;
  118. }