// sx-test-runner.js — Run test.sx in the browser using sx-browser.js. // Loaded on the /specs/testing page. Uses the Sx global. (function() { var NIL = Sx.NIL; function isNil(x) { return x === NIL || x === null || x === undefined; } function deepEqual(a, b) { if (a === b) return true; if (isNil(a) && isNil(b)) return true; if (typeof a !== typeof b) return false; if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (var i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false; return true; } if (a && typeof a === "object" && b && typeof b === "object") { var ka = Object.keys(a), kb = Object.keys(b); if (ka.length !== kb.length) return false; for (var j = 0; j < ka.length; j++) if (!deepEqual(a[ka[j]], b[ka[j]])) return false; return true; } return false; } window.sxRunTests = function(srcId, outId, btnId) { var src = document.getElementById(srcId).textContent; var out = document.getElementById(outId); var btn = document.getElementById(btnId); var stack = [], passed = 0, failed = 0, num = 0, lines = []; var env = { "try-call": function(thunk) { try { Sx.eval([thunk], env); return { ok: true }; } catch(e) { return { ok: false, error: e.message || String(e) }; } }, "report-pass": function(name) { num++; passed++; lines.push("ok " + num + " - " + stack.concat([name]).join(" > ")); }, "report-fail": function(name, error) { num++; failed++; lines.push("not ok " + num + " - " + stack.concat([name]).join(" > ")); lines.push(" # " + error); }, "push-suite": function(name) { stack.push(name); }, "pop-suite": function() { stack.pop(); }, "equal?": function(a, b) { return deepEqual(a, b); }, "eq?": function(a, b) { return a === b; }, "boolean?": function(x) { return typeof x === "boolean"; }, "string-length": function(s) { return String(s).length; }, "substring": function(s, start, end) { return String(s).slice(start, end); }, "string-contains?": function(s, n) { return String(s).indexOf(n) !== -1; }, "upcase": function(s) { return String(s).toUpperCase(); }, "downcase": function(s) { return String(s).toLowerCase(); }, "reverse": function(c) { return c ? c.slice().reverse() : []; }, "flatten": function(c) { var r = []; for (var i = 0; i < (c||[]).length; i++) { if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]); else r.push(c[i]); } return r; }, "has-key?": function(d, k) { return d && typeof d === "object" && k in d; }, "append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); }, }; try { var t0 = performance.now(); var exprs = Sx.parseAll(src); for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env); var elapsed = Math.round(performance.now() - t0); lines.push(""); lines.push("1.." + num); lines.push("# tests " + (passed + failed)); lines.push("# pass " + passed); if (failed > 0) lines.push("# fail " + failed); lines.push("# time " + elapsed + "ms"); } catch(e) { lines.push(""); lines.push("FATAL: " + (e.message || String(e))); } out.textContent = lines.join("\n"); out.style.display = "block"; btn.textContent = passed + "/" + (passed + failed) + " passed" + (failed === 0 ? "" : " (" + failed + " failed)"); btn.className = failed > 0 ? "px-4 py-2 rounded-md bg-red-600 text-white font-medium text-sm cursor-default" : "px-4 py-2 rounded-md bg-green-600 text-white font-medium text-sm cursor-default"; }; })();