const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const test = require("node:test"); const vm = require("node:vm"); const source = fs.readFileSync( path.join(__dirname, "../docs/javascripts/mathjax.js"), "utf8" ); const settle = () => new Promise(resolve => setImmediate(resolve)); function setup(base = "https://notes.example/javascripts/mathjax.js") { const scripts = []; const errors = []; const calls = []; let subscriber; let content; const window = {}; const renderer = { startup: { promise: Promise.resolve(), output: { clearCache: () => calls.push("cache") } }, typesetClear: () => calls.push("clear"), texReset: () => calls.push("reset"), typesetPromise: async elements => calls.push(elements[0]) }; vm.runInNewContext(source, { URL, window, console: { error: (...args) => errors.push(args) }, document: { currentScript: { src: base }, querySelector: () => content, createElement: () => ({ remove() { this.removed = true; } }), head: { append: script => scripts.push(script) } }, document$: { subscribe: callback => { subscriber = callback; } } }); return { scripts, errors, calls, renderer, window, navigate(math) { content = { querySelector: () => math ? {} : null }; subscriber(); return content; }, load() { window.MathJax = renderer; scripts.at(-1).onload(); } }; } test("non-maths pages do not load or initialize MathJax", async () => { const site = setup(); site.navigate(false); await settle(); assert.equal(site.scripts.length, 0); assert.equal(site.window.MathJax, undefined); }); test("maths pages wait for startup and use local assets under a subpath", async () => { const site = setup("https://notes.example/jupiter/javascripts/mathjax.js"); const content = site.navigate(true); await settle(); assert.equal(site.scripts.length, 1); assert.equal(site.scripts[0].src, "https://notes.example/jupiter/assets/vendor/mathjax/tex-mml-chtml.js"); const config = site.window.MathJax; assert.equal(config.startup.typeset, false); assert.equal(config.loader.paths.sre, "https://notes.example/jupiter/assets/vendor/mathjax/sre/mathmaps"); assert.equal(config.chtml.fontURL, "https://notes.example/jupiter/assets/vendor/mathjax/output/chtml/fonts/woff-v2"); let ready; site.renderer.startup.promise = new Promise(resolve => { ready = resolve; }); site.load(); await settle(); assert.equal(site.calls.length, 0); ready(); await settle(); assert.deepEqual(site.calls, ["cache", "clear", "reset", content]); }); test("navigation during loading renders only the latest page and reuses the renderer", async () => { const site = setup(); const first = site.navigate(true); await settle(); const second = site.navigate(true); site.load(); await settle(); assert.equal(site.scripts.length, 1); assert.ok(!site.calls.includes(first)); assert.equal(site.calls.at(-1), second); const third = site.navigate(true); await settle(); assert.equal(site.scripts.length, 1); assert.equal(site.calls.at(-1), third); }); test("leaving a maths page during loading does not typeset detached content", async () => { const site = setup(); site.navigate(true); await settle(); site.navigate(false); site.load(); await settle(); assert.deepEqual(site.calls, ["clear"]); }); test("returning to a non-maths page clears old maths without another download", async () => { const site = setup(); site.navigate(true); await settle(); site.load(); await settle(); site.navigate(false); await settle(); assert.equal(site.calls.at(-1), "clear"); assert.equal(site.scripts.length, 1); }); test("typesetting is serialized across navigation", async () => { const site = setup(); let finish; site.renderer.typesetPromise = elements => { site.calls.push(elements[0]); return new Promise(resolve => { finish = resolve; }); }; const first = site.navigate(true); await settle(); site.load(); await settle(); const second = site.navigate(true); await settle(); assert.equal(site.calls.at(-1), first); finish(); await settle(); assert.equal(site.calls.at(-1), second); finish(); await settle(); }); test("load errors are reported and a later maths page can retry", async () => { const site = setup(); site.navigate(true); await settle(); site.scripts[0].onerror(); await settle(); assert.equal(site.errors.length, 1); assert.match(site.errors[0][1].message, /Failed to load MathJax/); assert.equal(site.scripts[0].removed, true); const content = site.navigate(true); await settle(); assert.equal(site.scripts.length, 2); site.load(); await settle(); assert.equal(site.calls.at(-1), content); });