Files
HealthLog/src/lib/__tests__/retry-import.test.ts
T
Marc-André Bombeck b5e799c2d6 fix(charts): degrade chart failures to one card and heal stale shells per version
The update poller and the chunk-error reload guard key on the target
version instead of disarming for the whole session, so a second deploy
re-arms the self-heal and a stale shell can no longer strand with
404ing chunks. Lazy chart imports retry once and mount inside a
per-card error boundary with a reload affordance — a failed chunk
degrades to one card instead of replacing the page. The JSON API
client attaches a fifteen-second timeout unless the caller brings its
own signal (the raw streaming path is exempt), chart queries surface
errors with a retry instead of rendering as an empty period, per-type
fetch failures reject instead of caching an empty series as fresh, and
the session and snapshot queries retry once on transient network or
server errors.
2026-06-11 21:17:27 +02:00

58 lines
1.9 KiB
TypeScript

/**
* Unit tests for the lazy-chunk import retry wrapper.
*
* A rejected `import()` inside `React.lazy` / `next/dynamic` caches its
* rejection permanently, so the wrapper must absorb ONE transient
* failure (stale shell right after a deploy, flaky network) before the
* rejection reaches the boundary.
*/
import { afterEach, describe, expect, it, vi } from "vitest";
import { IMPORT_RETRY_DELAY_MS, importWithRetry } from "../retry-import";
afterEach(() => {
vi.useRealTimers();
});
describe("importWithRetry", () => {
it("resolves immediately when the first attempt succeeds", async () => {
const importer = vi.fn().mockResolvedValue({ default: "ok" });
await expect(importWithRetry(importer)).resolves.toEqual({
default: "ok",
});
expect(importer).toHaveBeenCalledTimes(1);
});
it("retries once after the delay when the first attempt rejects", async () => {
vi.useFakeTimers();
const importer = vi
.fn()
.mockRejectedValueOnce(new Error("Loading chunk 42 failed"))
.mockResolvedValueOnce({ default: "ok" });
const promise = importWithRetry(importer);
// The retry must not fire before the delay elapses.
await vi.advanceTimersByTimeAsync(IMPORT_RETRY_DELAY_MS - 1);
expect(importer).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(1);
await expect(promise).resolves.toEqual({ default: "ok" });
expect(importer).toHaveBeenCalledTimes(2);
});
it("rejects with the original error once the retry budget is spent", async () => {
vi.useFakeTimers();
const chunkError = new Error("Loading chunk 42 failed");
const importer = vi.fn().mockRejectedValue(chunkError);
const promise = importWithRetry(importer);
promise.catch(() => undefined); // pre-attach so the rejection is handled
await vi.advanceTimersByTimeAsync(IMPORT_RETRY_DELAY_MS);
await expect(promise).rejects.toBe(chunkError);
expect(importer).toHaveBeenCalledTimes(2);
});
});