When no realtime connection gets through (proxy buffering SSE, notes on long polling) but the server answers, the profile shows a yellow "eingeschränkt" instead of "offline". A full connection now shows a green "online" on the desktop too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
3.6 KiB
TypeScript
109 lines
3.6 KiB
TypeScript
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 { useConnectionState, SERVER_OFFLINE_GRACE_MS, SERVER_PROBE_INTERVAL_MS } from '../useConnectionState';
|
|
|
|
function setDeviceOnline(value: boolean) {
|
|
Object.defineProperty(navigator, 'onLine', { value, configurable: true });
|
|
}
|
|
|
|
let serverAnswers = false;
|
|
const fetchMock = vi.fn(async () => {
|
|
if (!serverAnswers) throw new TypeError('Failed to fetch');
|
|
return { ok: true } as Response;
|
|
});
|
|
|
|
// Runs the timers and lets the health request settle
|
|
async function wait(ms: number) {
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(ms);
|
|
});
|
|
}
|
|
|
|
describe('useConnectionState', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
fetchMock.mockClear();
|
|
currentConnected = false;
|
|
serverAnswers = false;
|
|
setDeviceOnline(true);
|
|
});
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('stays online during the grace period at app start', async () => {
|
|
const { result } = renderHook(() => useConnectionState());
|
|
expect(result.current).toBe('online');
|
|
act(() => statusListener?.(true));
|
|
await wait(SERVER_OFFLINE_GRACE_MS + 100);
|
|
expect(result.current).toBe('online');
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('turns offline once the server stays unreachable and back online on reconnect', async () => {
|
|
currentConnected = true;
|
|
const { result } = renderHook(() => useConnectionState());
|
|
act(() => statusListener?.(false));
|
|
expect(result.current).toBe('online');
|
|
await wait(SERVER_OFFLINE_GRACE_MS + 100);
|
|
expect(result.current).toBe('offline');
|
|
act(() => statusListener?.(true));
|
|
expect(result.current).toBe('online');
|
|
});
|
|
|
|
it('is limited when the server answers but no realtime connection gets through', async () => {
|
|
serverAnswers = true;
|
|
const { result } = renderHook(() => useConnectionState());
|
|
await wait(SERVER_OFFLINE_GRACE_MS + 100);
|
|
expect(result.current).toBe('limited');
|
|
expect(fetchMock).toHaveBeenCalledWith('/api/v1/health', expect.anything());
|
|
|
|
// Asked again while the connection is missing
|
|
serverAnswers = false;
|
|
await wait(SERVER_PROBE_INTERVAL_MS + 100);
|
|
expect(result.current).toBe('offline');
|
|
serverAnswers = true;
|
|
await wait(SERVER_PROBE_INTERVAL_MS + 100);
|
|
expect(result.current).toBe('limited');
|
|
|
|
act(() => statusListener?.(true));
|
|
expect(result.current).toBe('online');
|
|
const calls = fetchMock.mock.calls.length;
|
|
await wait(SERVER_PROBE_INTERVAL_MS * 2);
|
|
expect(fetchMock.mock.calls.length).toBe(calls);
|
|
});
|
|
|
|
it('repeated failed reconnects do not postpone the check', async () => {
|
|
currentConnected = true;
|
|
const { result } = renderHook(() => useConnectionState());
|
|
for (let i = 0; i < 4; i++) {
|
|
act(() => statusListener?.(false));
|
|
await wait(SERVER_OFFLINE_GRACE_MS / 2);
|
|
}
|
|
expect(result.current).toBe('offline');
|
|
});
|
|
|
|
it('is offline right away without device network', () => {
|
|
currentConnected = true;
|
|
const { result } = renderHook(() => useConnectionState());
|
|
act(() => {
|
|
setDeviceOnline(false);
|
|
statusListener?.(false);
|
|
});
|
|
expect(result.current).toBe('offline');
|
|
});
|
|
});
|