import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { act, renderHook } from '@testing-library/react'; let statusListener: ((connected: boolean) => void) | null = null; let currentConnected = false; vi.mock('../api', () => ({ onRealtimeStatus: (cb: (connected: boolean) => void) => { statusListener = cb; cb(currentConnected); return () => (statusListener = null); }, })); import { useServerOnline, SERVER_OFFLINE_GRACE_MS } from '../useServerOnline'; function setDeviceOnline(value: boolean) { Object.defineProperty(navigator, 'onLine', { value, configurable: true }); } describe('useServerOnline', () => { beforeEach(() => { vi.useFakeTimers(); currentConnected = false; setDeviceOnline(true); }); afterEach(() => vi.useRealTimers()); it('stays online during the grace period at app start', () => { const { result } = renderHook(() => useServerOnline()); expect(result.current).toBe(true); act(() => statusListener?.(true)); act(() => vi.advanceTimersByTime(SERVER_OFFLINE_GRACE_MS + 100)); expect(result.current).toBe(true); }); it('turns offline once the server stays unreachable and back online on reconnect', () => { currentConnected = true; const { result } = renderHook(() => useServerOnline()); act(() => statusListener?.(false)); expect(result.current).toBe(true); act(() => vi.advanceTimersByTime(SERVER_OFFLINE_GRACE_MS + 100)); expect(result.current).toBe(false); act(() => statusListener?.(true)); expect(result.current).toBe(true); }); it('repeated failed reconnects do not postpone the offline state', () => { currentConnected = true; const { result } = renderHook(() => useServerOnline()); for (let i = 0; i < 4; i++) { act(() => statusListener?.(false)); act(() => vi.advanceTimersByTime(SERVER_OFFLINE_GRACE_MS / 2)); } expect(result.current).toBe(false); }); it('is offline right away without device network', () => { currentConnected = true; const { result } = renderHook(() => useServerOnline()); act(() => { setDeviceOnline(false); statusListener?.(false); }); expect(result.current).toBe(false); }); });