import assert from "node:assert/strict"; import fs from "node:fs"; import vm from "node:vm"; import test from "node:test"; import ts from "typescript"; const implementations = { public: fs.readFileSync(new URL("../public/module-preload.js", import.meta.url), "utf8"), typescript: ts.transpileModule(fs.readFileSync(new URL("../src/module-preload.ts", import.meta.url), "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, }).outputText, }; async function preload(source, search, manifest, failedUrl) { const window = { location: { search, href: `http://localhost/${search}` } }; const requests = []; let active = 0; let peak = 0; const fetch = async (url, options) => { requests.push({ url, cache: options.cache }); if (url === "/manifest.json") return { ok: true, json: async () => manifest }; active += 1; peak = Math.max(peak, active); await new Promise((resolve) => setImmediate(resolve)); active -= 1; return { ok: url !== failedUrl, status: url === failedUrl ? 404 : 200, arrayBuffer: async () => new ArrayBuffer(8) }; }; vm.runInNewContext(source, { window, fetch, URL, URLSearchParams, exports: {} }); const deadline = Date.now() + 2000; while (window.__freecadModulePreloadState.phase === "loading" && Date.now() < deadline) { await new Promise((resolve) => setImmediate(resolve)); } const state = JSON.parse(JSON.stringify(window.__freecadModulePreloadState)); assert.notEqual(state.phase, "loading", "preload did not finish"); return { state, requests, peak }; } const manifest = { schema: "web-module-manifest/v1", modules: { Part: { revision: "part-one", workers: { "part.worker.ts": { wasmUrl: "/Part.wasm", dataUrl: "/Part.data", runtimeUrl: "/Part.js" }, "part-inspect.worker.ts": { wasmUrl: "/Part.wasm", dataUrl: "/Part.data" }, } }, TechDraw: { revision: "techdraw-one", wasmUrl: "/TechDraw.wasm", runtimeUrl: "/TechDraw.js" }, Fem: { revision: "fem-one", wasmUrl: "/Fem.wasm" }, } }; for (const [name, source] of Object.entries(implementations)) { test(`${name}: unrequested modules stay unloaded; shared artifact URLs are fetched once`, async () => { const { state, requests, peak } = await preload(source, "?moduleManifest=/manifest.json&preload=Part,Part", manifest); assert.equal(state.phase, "ready"); assert.equal(state.requestedAssets, 2); assert.equal(state.loadedAssets, 2); assert.equal(state.loadedBytes, 16); assert.deepEqual(requests, [ { url: "/manifest.json", cache: "no-cache" }, { url: "http://localhost/Part.wasm", cache: "force-cache" }, { url: "http://localhost/Part.data", cache: "force-cache" }, ]); assert.equal(peak, 2); }); test(`${name}: top-level artifact URLs and requested asset kinds are honored`, async () => { const { state, requests } = await preload(source, "?moduleManifest=/manifest.json&preloadModules=TechDraw&preloadKinds=wasm,runtime", manifest); assert.equal(state.phase, "ready"); assert.equal(state.loadedAssets, 2); assert.deepEqual(requests.slice(1).map((row) => row.url), ["http://localhost/TechDraw.wasm", "http://localhost/TechDraw.js"]); }); test(`${name}: concurrency is bounded and invalid values cannot silently skip downloads`, async () => { for (const [value, expectedPeak] of [["invalid", 2], ["99", 2], ["1", 1], ["-1", 1]]) { const { state, peak } = await preload(source, `?moduleManifest=/manifest.json&preload=Part&preloadConcurrency=${value}`, manifest); assert.equal(state.phase, "ready"); assert.equal(state.loadedAssets, 2); assert.equal(peak, expectedPeak); } }); test(`${name}: missing modules and download failures remain errors`, async () => { const { state } = await preload(source, "?moduleManifest=/manifest.json&preload=Part,Missing", manifest, "http://localhost/Part.wasm"); assert.equal(state.phase, "error"); assert.equal(state.loadedAssets, 1); assert.equal(state.failedAssets.length, 2); }); test(`${name}: no preload selection makes no requests`, async () => { const { state, requests } = await preload(source, "?moduleManifest=/manifest.json", manifest); assert.equal(state.phase, "disabled"); assert.deepEqual(requests, []); }); }