import { describe, expect, test } from "vitest"; import { attachDashboardRefreshLifecycle, createDashboardTileBackoff, createDashboardRequestAborter, runDashboardHydrationQueue, shouldPauseDashboardRefresh, } from "./dashboard-refresh"; describe("dashboard refresh lifecycle", () => { test("pauses refreshes when the document is hidden or the browser is offline", () => { expect( shouldPauseDashboardRefresh({ visibilityState: "visible", online: true }), ).toBe(false); expect( shouldPauseDashboardRefresh({ visibilityState: "hidden", online: true }), ).toBe(true); expect( shouldPauseDashboardRefresh({ visibilityState: "visible", online: false }), ).toBe(true); }); test("aborts shell and tile requests when a new shell run starts", () => { const aborter = createDashboardRequestAborter(); const shellSignal = aborter.beginShellRun(); const tileSignal = aborter.beginTileRun(); const nextShellSignal = aborter.beginShellRun(); expect(shellSignal.aborted).toBe(true); expect(tileSignal.aborted).toBe(true); expect(nextShellSignal.aborted).toBe(false); }); test("aborts active requests when refreshes are paused", () => { const aborter = createDashboardRequestAborter(); const shellSignal = aborter.beginShellRun(); const tileSignal = aborter.beginTileRun(); aborter.abortActiveRequests(); expect(shellSignal.aborted).toBe(true); expect(tileSignal.aborted).toBe(true); }); test("pauses on hidden, offline, and pagehide events then resumes immediately when visible", () => { const documentTarget = new EventTarget(); const windowTarget = new EventTarget(); const calls: string[] = []; let paused = true; const detach = attachDashboardRefreshLifecycle({ documentTarget, windowTarget, loadDashboard: () => { calls.push("load"); }, pauseRefreshes: () => { calls.push("pause"); }, refreshPaused: () => paused, }); documentTarget.dispatchEvent(new Event("visibilitychange")); windowTarget.dispatchEvent(new Event("offline")); windowTarget.dispatchEvent(new Event("pagehide")); paused = false; documentTarget.dispatchEvent(new Event("visibilitychange")); windowTarget.dispatchEvent(new Event("online")); detach(); documentTarget.dispatchEvent(new Event("visibilitychange")); windowTarget.dispatchEvent(new Event("online")); expect(calls).toEqual(["pause", "pause", "pause", "load", "load"]); }); test("limits tile hydration concurrency", async () => { let active = 0; let maxActive = 0; const started: number[] = []; const releases = new Map void>(); const queue = runDashboardHydrationQueue({ concurrency: 2, items: [1, 2, 3, 4], signal: new AbortController().signal, hydrate: async (item) => { active += 1; maxActive = Math.max(maxActive, active); started.push(item); await new Promise((resolve) => releases.set(item, resolve)); active -= 1; }, }); await waitFor(() => started.length === 2); expect(started).toEqual([1, 2]); expect(maxActive).toBe(2); releases.get(1)?.(); await waitFor(() => started.length === 3); expect(started).toEqual([1, 2, 3]); expect(maxActive).toBe(2); releases.get(2)?.(); releases.get(3)?.(); await waitFor(() => started.length === 4); releases.get(4)?.(); await queue; expect(maxActive).toBe(2); }); test("backs off failed tile keys and resets after success", () => { const backoff = createDashboardTileBackoff(); backoff.recordFailure("telemetry:infra-ram", 1_000); expect(backoff.canAttempt("telemetry:infra-ram", 15_999)).toBe(false); expect(backoff.canAttempt("telemetry:infra-ram", 16_000)).toBe(true); backoff.recordFailure("telemetry:infra-ram", 16_000); expect(backoff.canAttempt("telemetry:infra-ram", 45_999)).toBe(false); expect(backoff.canAttempt("telemetry:infra-ram", 46_000)).toBe(true); backoff.recordFailure("telemetry:infra-ram", 46_000); backoff.recordFailure("telemetry:infra-ram", 106_000); expect(backoff.canAttempt("telemetry:infra-ram", 225_999)).toBe(false); expect(backoff.canAttempt("telemetry:infra-ram", 226_000)).toBe(true); backoff.recordSuccess("telemetry:infra-ram"); expect(backoff.canAttempt("telemetry:infra-ram", 107_000)).toBe(true); }); }); async function waitFor(predicate: () => boolean) { for (let attempt = 0; attempt < 20; attempt += 1) { if (predicate()) return; await Promise.resolve(); } throw new Error("condition was not met"); }