Move the home startup sequence and consultation recovery poll into plain functions, and the error and onboarding screens into components. Landing rules, the 20s fatal screen, retry, and the chart settings deep link stay the same.
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
export function installWindow() {
|
|
const host = globalThis as { window?: unknown };
|
|
if (host.window === undefined) host.window = globalThis;
|
|
}
|
|
|
|
export async function withFakeTimers(run: (tick: (ms: number) => void) => Promise<void>): Promise<void> {
|
|
const originalSet = globalThis.setTimeout;
|
|
const originalClear = globalThis.clearTimeout;
|
|
const host = globalThis as { window?: unknown };
|
|
const originalWindow = host.window;
|
|
host.window = globalThis;
|
|
let now = 0;
|
|
let sequence = 1;
|
|
const queue = new Map<number, { at: number; fn: () => void }>();
|
|
globalThis.setTimeout = ((fn: TimerHandler, ms?: number) => {
|
|
const id = sequence;
|
|
sequence += 1;
|
|
queue.set(id, {
|
|
at: now + (typeof ms === "number" ? ms : 0),
|
|
fn: () => { if (typeof fn === "function") fn(); },
|
|
});
|
|
return id as unknown as ReturnType<typeof setTimeout>;
|
|
}) as unknown as typeof setTimeout;
|
|
globalThis.clearTimeout = ((id?: ReturnType<typeof setTimeout>) => {
|
|
if (typeof id === "number") queue.delete(id);
|
|
}) as unknown as typeof clearTimeout;
|
|
const tick = (ms: number) => {
|
|
now += ms;
|
|
const due = [...queue.entries()]
|
|
.filter((entry) => entry[1].at <= now)
|
|
.sort((left, right) => left[1].at - right[1].at);
|
|
for (const [id, item] of due) {
|
|
if (!queue.has(id)) continue;
|
|
queue.delete(id);
|
|
item.fn();
|
|
}
|
|
};
|
|
try {
|
|
await run(tick);
|
|
} finally {
|
|
globalThis.setTimeout = originalSet;
|
|
globalThis.clearTimeout = originalClear;
|
|
if (originalWindow === undefined) delete host.window;
|
|
else host.window = originalWindow;
|
|
}
|
|
}
|