test: cover frontend modules

browser-globals.test.js loads every page module through vm with a fake
window, the way the pages do, and asserts the global each one exposes
(HtmlEscape/escapeHtml, DataActions, ApiClient, ListState, SseStream,
PngMetadata, ViewRouter, PromptListSort, ComfyMetadata). sse-stream gains
cases for a POST body, missing handlers and close() before or during
delivery. Every pure module is above 85% line coverage under
node --test --experimental-test-coverage.
This commit is contained in:
Vito Sansevero
2026-10-08 18:01:34 -07:00
parent 247abc3475
commit 11aea013ca
2 changed files with 118 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
// Run with: node --test tests/js/
// Each page module is a UMD-style IIFE: CommonJS export under Node, a window
// global in the browser. This loads every module the way a browser would and
// checks the global name the pages rely on.
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const MODULES = {
"html-escape.js": ["HtmlEscape", "escapeHtml", "escapeAttr"],
"data-actions.js": ["DataActions"],
"api-client.js": ["ApiClient"],
"list-state.js": ["ListState"],
"sse-stream.js": ["SseStream"],
"png-metadata.js": ["PngMetadata"],
"view-router.js": ["ViewRouter"],
"prompt-list-sort.js": ["PromptListSort"],
"comfy-metadata.js": ["ComfyMetadata"],
};
function loadInFakeBrowser(file) {
const source = fs.readFileSync(path.join(__dirname, "../../web/js", file), "utf8");
const window = {};
window.window = window;
const context = vm.createContext({
window,
globalThis: window,
console,
TextDecoder,
TextEncoder,
AbortController,
URLSearchParams,
Symbol,
Object,
Array,
Number,
String,
Math,
Set,
Map,
JSON,
Error,
Promise,
DataView,
Uint8Array,
ArrayBuffer,
});
vm.runInContext(source, context, { filename: file });
return window;
}
for (const [file, globals] of Object.entries(MODULES)) {
test(`${file} exposes ${globals.join(", ")} on window when module is undefined`, () => {
const window = loadInFakeBrowser(file);
for (const name of globals) {
assert.ok(window[name], `${name} missing`);
assert.ok(["object", "function"].includes(typeof window[name]), `${name} is a ${typeof window[name]}`);
}
});
}
test("the browser escapeHtml global is the same function as HtmlEscape.escapeHtml", () => {
const window = loadInFakeBrowser("html-escape.js");
assert.equal(window.escapeHtml, window.HtmlEscape.escapeHtml);
assert.equal(window.escapeAttr, window.escapeHtml);
assert.equal(window.escapeHtml(`<a href="x">`), "&lt;a href=&quot;x&quot;&gt;");
});
test("the browser ViewRouter global resolves the tags deep link", () => {
const window = loadInFakeBrowser("view-router.js");
assert.equal(window.ViewRouter.resolveView("#/tags/a"), "tags");
});
+44
View File
@@ -104,6 +104,50 @@ test("close() aborts the request and suppresses later callbacks", async () => {
assert.equal(errors, 0);
});
test("connect forwards a request body and an Accept header", async () => {
let init = null;
const fetchImpl = async (_url, i) => { init = i; return new Response(streamOf([]), { status: 200 }); };
const source = connect("/x", { method: "POST", body: '{"a":1}', fetchImpl });
await until(() => source.readyState === source.CLOSED);
assert.equal(init.body, '{"a":1}');
assert.equal(init.headers.Accept, "text/event-stream");
});
test("events arriving with no onmessage handler are dropped without error", async () => {
const fetchImpl = async () => new Response(streamOf(["data: 1\n\n", "data: 2\n\n"]), { status: 200 });
const source = connect("/x", { fetchImpl });
let errors = 0;
source.onerror = () => errors++;
await until(() => source.readyState === source.CLOSED);
assert.equal(errors, 0);
});
test("close() during delivery stops further messages and skips onclose", async () => {
const seen = [];
let closed = false;
const fetchImpl = async () => new Response(streamOf(["data: 1\n\ndata: 2\n\ndata: 3\n\n"]), { status: 200 });
const source = connect("/x", { fetchImpl });
source.onmessage = (e) => { seen.push(e.data); if (e.data === "1") source.close(); };
source.onclose = () => { closed = true; };
await until(() => source.readyState === source.CLOSED);
await new Promise((r) => setTimeout(r, 10));
assert.deepEqual(seen, ["1"]);
assert.equal(closed, false);
});
test("close() before the response arrives discards it silently", async () => {
let resolveFetch;
let errors = 0;
const fetchImpl = () => new Promise((resolve) => { resolveFetch = resolve; });
const source = connect("/x", { fetchImpl });
source.onerror = () => errors++;
source.close();
resolveFetch(new Response(streamOf(["data: late\n\n"]), { status: 200 }));
await new Promise((r) => setTimeout(r, 10));
assert.equal(errors, 0);
assert.equal(source.readyState, source.CLOSED);
});
test("connect defaults to GET and the global fetch", async () => {
const original = globalThis.fetch;
let init = null;