import { describe, expect, it, vi } from 'vitest'; import { DEV_CLIENT_LOAD_ATTEMPTS, DevClientLoadDeps, loadDevelopmentClientWithRetry } from './dev-client-load.rules'; function createDeps(overrides: Partial = {}): DevClientLoadDeps { return { isAborted: () => false, load: vi.fn().mockResolvedValue(undefined), onGiveUp: vi.fn(), onRetry: vi.fn(), wait: vi.fn().mockResolvedValue(undefined), ...overrides }; } describe('loadDevelopmentClientWithRetry', () => { it('loads once when the dev server answers', async () => { const deps = createDeps(); await expect(loadDevelopmentClientWithRetry(deps)).resolves.toBe('loaded'); expect(deps.load).toHaveBeenCalledTimes(1); expect(deps.onRetry).not.toHaveBeenCalled(); }); it('retries a rebuild gap and reports the recovered load', async () => { const load = vi.fn() .mockRejectedValueOnce(new Error('ERR_CONNECTION_REFUSED (-102)')) .mockResolvedValue(undefined); const deps = createDeps({ load }); await expect(loadDevelopmentClientWithRetry(deps)).resolves.toBe('loaded'); expect(load).toHaveBeenCalledTimes(2); expect(deps.onRetry).toHaveBeenCalledTimes(1); expect(deps.wait).toHaveBeenCalledTimes(1); }); it('reports failure instead of throwing so the caller still wires the window', async () => { const error = new Error('ERR_FAILED (-2)'); const deps = createDeps({ load: vi.fn().mockRejectedValue(error) }); await expect(loadDevelopmentClientWithRetry(deps)).resolves.toBe('failed'); expect(deps.load).toHaveBeenCalledTimes(DEV_CLIENT_LOAD_ATTEMPTS); expect(deps.onGiveUp).toHaveBeenCalledWith(DEV_CLIENT_LOAD_ATTEMPTS, error); }); it('stops retrying once the window is gone', async () => { let windowAlive = true; const load = vi.fn().mockImplementation(() => { windowAlive = false; return Promise.reject(new Error('ERR_FAILED (-2)')); }); const deps = createDeps({ isAborted: () => !windowAlive, load }); await expect(loadDevelopmentClientWithRetry(deps)).resolves.toBe('aborted'); expect(load).toHaveBeenCalledTimes(1); expect(deps.onGiveUp).not.toHaveBeenCalled(); }); });