@@ -0,0 +1,66 @@
|
||||
// Preloaded by `npm test` (node -r) before any compiled code runs: gives the ClojureScript
|
||||
// tests a browser-like global scope (window, document, location, ...) backed by jsdom.
|
||||
//
|
||||
// It has to happen first: React DOM decides at load time whether the browser supports the
|
||||
// `input` event; without a `window` then, <input onChange> never fires under test.
|
||||
const { JSDOM } = require("jsdom");
|
||||
|
||||
const { window } = new JSDOM('<!doctype html><html><body><div id="app"></div></body></html>', {
|
||||
url: "http://localhost:3000/",
|
||||
});
|
||||
|
||||
// Copy the window's globals over, but leave Node's own (fetch, setTimeout, navigator, ...) alone.
|
||||
for (const key of Object.getOwnPropertyNames(window)) {
|
||||
if (!(key in globalThis)) {
|
||||
Object.defineProperty(globalThis, key, {
|
||||
configurable: true,
|
||||
get: () => window[key],
|
||||
set: (value) => Object.defineProperty(globalThis, key, { value, writable: true, configurable: true }),
|
||||
});
|
||||
}
|
||||
}
|
||||
globalThis.window = window;
|
||||
|
||||
// WebMCP, for `require("@jason.today/webmcp/src/webmcp.js")`. Its only export is
|
||||
// `module.exports = WebMCP`, guarded by `typeof module`, in a file its package.json declares as an
|
||||
// ES module. The browser build wraps it as CommonJS, so there it exports the class; Node's
|
||||
// require() loads it as ESM, where `module` doesn't exist, and gets nothing. So run the same
|
||||
// source with a `module` to export to, and hand that to require.
|
||||
const Module = require("module");
|
||||
const fs = require("fs");
|
||||
|
||||
const webmcp = require.resolve("@jason.today/webmcp/src/webmcp.js");
|
||||
const cjs = new Module(webmcp);
|
||||
cjs.filename = webmcp;
|
||||
new Function("module", "exports", fs.readFileSync(webmcp, "utf8"))(cjs, cjs.exports);
|
||||
cjs.loaded = true;
|
||||
require.cache[webmcp] = cjs;
|
||||
|
||||
// A run that doesn't finish must not look like a pass. An async test that throws before it calls
|
||||
// `done` (or whose promise chain rejects) never ends: the process then hangs, or runs out of things
|
||||
// to do and exits 0 without a word. Fail instead, and say which test it was.
|
||||
function fail(message) {
|
||||
console.error(`\nFAILED: ${message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.on("unhandledRejection", (reason) =>
|
||||
fail(`unhandled promise rejection (an async test without a catch?): ${(reason && reason.stack) || reason}`));
|
||||
|
||||
// tbd.ui.test-util keeps `uiTestsCurrent` up to date: the test that is running, and since when.
|
||||
const STUCK_MS = 30000;
|
||||
globalThis.uiTestsCurrent = { name: "loading the tests", since: Date.now() };
|
||||
setInterval(() => {
|
||||
const { name, since } = globalThis.uiTestsCurrent;
|
||||
if (Date.now() - since > STUCK_MS) {
|
||||
fail(`stuck for ${STUCK_MS / 1000}s in ${name} (an async test that never called done?)`);
|
||||
}
|
||||
}, 1000).unref();
|
||||
|
||||
// ... and sets `uiTestsFinished` once cljs.test has printed its summary.
|
||||
process.on("exit", (code) => {
|
||||
if (code === 0 && !globalThis.uiTestsFinished) {
|
||||
console.error("\nFAILED: the tests ended without reporting a result");
|
||||
process.exitCode = 1;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user