import { afterEach, describe, expect, it, vi } from 'vitest'; import { createMetricsRefresh } from '../../src/lib/ui/metrics-refresh.js'; afterEach(() => vi.useRealTimers()); const report = (days: number) => ({ days, summary: {}, points: [], maps: [], servers: [] }); function setup(fetcher: typeof fetch) { let query = 'days=7'; const onData = vi.fn(), onError = vi.fn(), onLoading = vi.fn(); return { ...createMetricsRefresh({ getQuery: () => query, onData, onError, onLoading, fetcher, timeoutMs: 100 }), onData, onError, onLoading, setQuery: (value: string) => { query = value; } }; } describe('metrics background refresh', () => { it('keeps the previous report after failure and accepts a successful retry', async () => { const fetcher = vi.fn().mockResolvedValueOnce(new Response('', { status: 503 })) .mockResolvedValueOnce(Response.json(report(7))); const refresh = setup(fetcher); await refresh.refresh(); expect(refresh.onData).not.toHaveBeenCalled(); expect(refresh.onError).toHaveBeenLastCalledWith('Report refresh failed (503).'); await refresh.refresh(); expect(refresh.onData).toHaveBeenLastCalledWith(report(7)); expect(refresh.onError).toHaveBeenLastCalledWith(null); expect(refresh.onLoading).toHaveBeenLastCalledWith(false); }); it('coalesces duplicate requests and ignores old replies even when transport ignores abort', async () => { const resolve: ((r: Response) => void)[] = []; const fetcher = vi.fn(() => new Promise(done => resolve.push(done))); const refresh = setup(fetcher); const old = refresh.refresh(); expect(refresh.refresh()).toBe(old); expect(fetcher).toHaveBeenCalledTimes(1); refresh.setQuery('days=1'); const next = refresh.refresh(); resolve[1](Response.json(report(1))); await next; resolve[0](Response.json(report(7))); await old; expect(refresh.onData).toHaveBeenCalledTimes(1); expect(refresh.onData).toHaveBeenLastCalledWith(report(1)); }); it('suppresses errors from a cancelled request', async () => { let reject!: (error: Error) => void; const refresh = setup(vi.fn(() => new Promise((_, fail) => { reject = fail; }))); const pending = refresh.refresh(); refresh.cancel(); reject(new Error('Obsolete response')); await pending; expect(refresh.onData).not.toHaveBeenCalled(); expect(refresh.onError.mock.calls).toEqual([[null]]); expect(refresh.onLoading).toHaveBeenLastCalledWith(false); }); it('times out with retry feedback and clears loading', async () => { vi.useFakeTimers(); const fetcher = vi.fn((_input, init) => new Promise((_, reject) => { init?.signal?.addEventListener('abort', () => reject(new Error('aborted'))); })) as typeof fetch; const refresh = setup(fetcher); const pending = refresh.refresh(); await vi.advanceTimersByTimeAsync(100); await pending; expect(refresh.onError).toHaveBeenLastCalledWith('Report refresh timed out. Try again.'); expect(refresh.onLoading).toHaveBeenLastCalledWith(false); }); });