91 lines
4.2 KiB
JavaScript
91 lines
4.2 KiB
JavaScript
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, []);
|
|
});
|
|
}
|