Files
govoplan-core/webui/tests/module-loading.test.ts
T
zemion 6591aaa3fd
Module Package Release / publish-packages (push) Successful in 13s
fix(core): preserve data integrity and bound shared UI and response work
Release v0.1.46. Coordinated integrity review: GovOPlaN/govoplan-core#298.
2026-09-08 12:30:38 +02:00

83 lines
3.7 KiB
TypeScript

import { importModuleWithRetry } from "../src/platform/moduleLoading";
import { createModuleRefresh } from "../src/platform/moduleRefresh";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
async function verifyModuleImportRetry() {
let attempts = 0;
let pauses = 0;
const module = { id: "files", uiCapabilities: { "files.fileExplorer": {} } };
const imported = await importModuleWithRetry(async () => {
attempts += 1;
if (attempts === 1) throw new TypeError("Synthetic transient import failure");
return module;
}, async () => { pauses += 1; });
assert(imported === module, "a second successful import preserves the real module descriptor");
assert(attempts === 2 && pauses === 1, "a transient failure gets exactly one bounded retry");
attempts = 0;
pauses = 0;
const terminal = new TypeError("Synthetic final import failure");
let observed: unknown;
try {
await importModuleWithRetry(async () => { attempts += 1; throw terminal; }, async () => { pauses += 1; });
} catch (error) { observed = error; }
assert(observed === terminal, "a final failure must be reported, never replaced by an empty or fake module");
assert(attempts === 2 && pauses === 1, "permanent failure cannot start an unbounded retry loop");
attempts = 0;
pauses = 0;
await importModuleWithRetry(async () => { attempts += 1; return module; }, async () => { pauses += 1; });
assert(attempts === 1 && pauses === 0, "healthy imports do not pause or repeat");
console.log("Module import retry tests passed.");
}
void verifyModuleImportRetry().catch((error) => { throw error; });
async function verifyModuleRefresh() {
const requests: Array<{ resolve: (value: number) => void; reject: (error: unknown) => void }> = [];
const accepted: number[] = [];
const errors: unknown[] = [];
let time = 0;
const refresh = createModuleRefresh({
load: () => new Promise<number>((resolve, reject) => requests.push({ resolve, reject })),
accept: (value) => accepted.push(value), reject: (error) => errors.push(error), now: () => time
});
const settle = async () => { await Promise.resolve(); await Promise.resolve(); };
refresh.invalidate();
refresh.invalidate();
refresh.invalidate();
assert(requests.length === 1, "mutation bursts cannot start overlapping requests");
requests[0].resolve(0);
await settle();
assert(accepted.length === 0 && Number(requests.length) === 2, "obsolete success is ignored and invalidation gets one trailing read");
refresh.invalidate();
requests[1].reject(new Error("obsolete"));
await settle();
assert(errors.length === 0 && Number(requests.length) === 3, "obsolete error cannot clear the current catalogue or drop invalidation");
requests[2].resolve(2);
await settle();
assert(accepted.join() === "2", "only the current generation publishes");
refresh.refreshVisible(true);
time = 6_000;
refresh.refreshVisible(false);
assert(Number(requests.length) === 3, "focus throttle and hidden-document checks remain");
refresh.refreshVisible(true);
assert(Number(requests.length) === 4, "visible focus refresh remains available after the throttle");
requests[3].reject("current failure");
await settle();
assert(errors.join() === "current failure", "current errors retain fail-closed handling without automatic retries");
refresh.invalidate();
refresh.invalidate();
refresh.dispose();
requests[4].resolve(4);
await settle();
refresh.invalidate();
assert(Number(requests.length) === 5 && accepted.join() === "2", "authority change/unmount discards both old response and queued work");
console.log("Module refresh coalescing and generation tests passed.");
}
void verifyModuleRefresh().catch((error) => { throw error; });