160 lines
4.7 KiB
JavaScript
160 lines
4.7 KiB
JavaScript
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);
|
|
});
|