spec-test.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * CommonMark 0.31.2 spec conformance runner.
  3. *
  4. * Loads js/statum-core.js + js/statum-markdown.js through the DOM shim,
  5. * renders every example in
  6. * spec-tests.json and compares (whitespace-normalised) against the expected
  7. * HTML. Sections listed in OUT_OF_SCOPE are counted separately: they cover
  8. * raw HTML pass-through and schemes we deliberately neutralise, so they are
  9. * excluded from the pass-rate denominator.
  10. *
  11. * Usage: qjs tests/markdown/spec-test.js [section-filter]
  12. */
  13. import * as std from 'std';
  14. import { installShim } from './shim.js';
  15. var ROOT = import.meta.url ? '.' : '.';
  16. function fail(msg) {
  17. std.err.puts(msg + '\n');
  18. std.exit(1);
  19. }
  20. installShim();
  21. // Load order matters: statum-core.js creates `window.statum` (and the module
  22. // seam), statum-markdown.js registers the engine + its bindings through it.
  23. // Loading exactly these two proves the core+markdown cherry-pick.
  24. (0, eval)(std.loadFile('js/statum-core.js'));
  25. (0, eval)(std.loadFile('js/statum-markdown.js'));
  26. var render = globalThis.statum._testMarkdown;
  27. if (typeof render !== 'function') {
  28. fail('statum._testMarkdown is not exposed — was __STATUM_TEST__ set before loading?');
  29. }
  30. /** cmark-style normalisation: collapse whitespace runs, trim the ends. */
  31. function normalize(html) {
  32. return String(html).replace(/[ \t\r\n]+/g, ' ').trim();
  33. }
  34. var OUT_OF_SCOPE = {
  35. 'HTML blocks': 'raw HTML blocks are escaped, not passed through',
  36. 'Raw HTML': 'inline raw HTML is escaped, not passed through'
  37. };
  38. var data;
  39. try {
  40. data = JSON.parse(std.loadFile('tests/markdown/spec-tests.json'));
  41. } catch (e) {
  42. fail('cannot read tests/markdown/spec-tests.json: ' + e);
  43. }
  44. var filter = scriptArgs.length > 1 ? scriptArgs[1] : null;
  45. var sections = {};
  46. var order = [];
  47. var pass = 0, failCount = 0, skipped = 0;
  48. var failures = [];
  49. for (var i = 0; i < data.length; i++) {
  50. var t = data[i];
  51. if (filter && t.section.indexOf(filter) < 0) continue;
  52. if (Object.prototype.hasOwnProperty.call(OUT_OF_SCOPE, t.section)) { skipped++; continue; }
  53. var got;
  54. try {
  55. got = normalize(render(t.markdown));
  56. } catch (e) {
  57. got = '<<threw ' + e + '>>';
  58. }
  59. var expected = normalize(t.html);
  60. var sec = sections[t.section];
  61. if (!sec) {
  62. sec = sections[t.section] = { pass: 0, fail: 0 };
  63. order.push(t.section);
  64. }
  65. if (got === expected) {
  66. pass++; sec.pass++;
  67. } else {
  68. failCount++; sec.fail++;
  69. failures.push({ example: t.example, section: t.section, markdown: t.markdown, html: t.html, got: got });
  70. }
  71. }
  72. print('CommonMark 0.31.2 spec suite (in scope: ' + (pass + failCount) + ', out of scope: ' + skipped + ')');
  73. print('');
  74. for (var s = 0; s < order.length; s++) {
  75. var st = sections[order[s]];
  76. var total = st.pass + st.fail;
  77. print(' ' + pad(order[s], 38) + ' ' + pad(String(st.pass) + '/' + total, 8) +
  78. bar(st.pass, total));
  79. }
  80. print('');
  81. var rate = (pass + failCount) ? (100 * pass / (pass + failCount)) : 0;
  82. print(' ' + pad('TOTAL', 38) + ' ' + pad(pass + '/' + (pass + failCount), 8) + rate.toFixed(1) + '%');
  83. print('');
  84. if (failures.length && (!filter || scriptArgs.indexOf('-v') >= 0)) {
  85. print('failures:');
  86. for (var f = 0; f < failures.length; f++) {
  87. var x = failures[f];
  88. print(' #' + x.example + ' [' + x.section + '] ' + JSON.stringify(x.markdown));
  89. print(' got: ' + x.got);
  90. print(' exp: ' + normalize(x.html));
  91. }
  92. print('');
  93. }
  94. function pad(s, n) {
  95. while (s.length < n) s += ' ';
  96. return s;
  97. }
  98. function bar(p, total) {
  99. if (!total) return '';
  100. var filled = Math.round(20 * p / total);
  101. var s = '';
  102. for (var i = 0; i < 20; i++) s += i < filled ? '#' : '.';
  103. return ' |' + s + '|';
  104. }
  105. // Exit status is decided by the caller (run.sh) based on the target rate; we
  106. // print the numbers and fail only on hard errors above.
  107. std.exit(0);