/** * CommonMark 0.31.2 spec conformance runner. * * Loads js/statum.js through the DOM shim, renders every example in * spec-tests.json and compares (whitespace-normalised) against the expected * HTML. Sections listed in OUT_OF_SCOPE are counted separately: they cover * raw HTML pass-through and schemes we deliberately neutralise, so they are * excluded from the pass-rate denominator. * * Usage: qjs tests/markdown/spec-test.js [section-filter] */ import * as std from 'std'; import { installShim } from './shim.js'; var ROOT = import.meta.url ? '.' : '.'; function fail(msg) { std.err.puts(msg + '\n'); std.exit(1); } installShim(); (0, eval)(std.loadFile('js/statum.js')); var render = globalThis.statum._testMarkdown; if (typeof render !== 'function') { fail('statum._testMarkdown is not exposed — was __STATUM_TEST__ set before loading?'); } /** cmark-style normalisation: collapse whitespace runs, trim the ends. */ function normalize(html) { return String(html).replace(/[ \t\r\n]+/g, ' ').trim(); } var OUT_OF_SCOPE = { 'HTML blocks': 'raw HTML blocks are escaped, not passed through', 'Raw HTML': 'inline raw HTML is escaped, not passed through' }; var data; try { data = JSON.parse(std.loadFile('tests/markdown/spec-tests.json')); } catch (e) { fail('cannot read tests/markdown/spec-tests.json: ' + e); } var filter = scriptArgs.length > 1 ? scriptArgs[1] : null; var sections = {}; var order = []; var pass = 0, failCount = 0, skipped = 0; var failures = []; for (var i = 0; i < data.length; i++) { var t = data[i]; if (filter && t.section.indexOf(filter) < 0) continue; if (Object.prototype.hasOwnProperty.call(OUT_OF_SCOPE, t.section)) { skipped++; continue; } var got; try { got = normalize(render(t.markdown)); } catch (e) { got = '<>'; } var expected = normalize(t.html); var sec = sections[t.section]; if (!sec) { sec = sections[t.section] = { pass: 0, fail: 0 }; order.push(t.section); } if (got === expected) { pass++; sec.pass++; } else { failCount++; sec.fail++; failures.push({ example: t.example, section: t.section, markdown: t.markdown, html: t.html, got: got }); } } print('CommonMark 0.31.2 spec suite (in scope: ' + (pass + failCount) + ', out of scope: ' + skipped + ')'); print(''); for (var s = 0; s < order.length; s++) { var st = sections[order[s]]; var total = st.pass + st.fail; print(' ' + pad(order[s], 38) + ' ' + pad(String(st.pass) + '/' + total, 8) + bar(st.pass, total)); } print(''); var rate = (pass + failCount) ? (100 * pass / (pass + failCount)) : 0; print(' ' + pad('TOTAL', 38) + ' ' + pad(pass + '/' + (pass + failCount), 8) + rate.toFixed(1) + '%'); print(''); if (failures.length && (!filter || scriptArgs.indexOf('-v') >= 0)) { print('failures:'); for (var f = 0; f < failures.length; f++) { var x = failures[f]; print(' #' + x.example + ' [' + x.section + '] ' + JSON.stringify(x.markdown)); print(' got: ' + x.got); print(' exp: ' + normalize(x.html)); } print(''); } function pad(s, n) { while (s.length < n) s += ' '; return s; } function bar(p, total) { if (!total) return ''; var filled = Math.round(20 * p / total); var s = ''; for (var i = 0; i < 20; i++) s += i < filled ? '#' : '.'; return ' |' + s + '|'; } // Exit status is decided by the caller (run.sh) based on the target rate; we // print the numbers and fail only on hard errors above. std.exit(0);