diff --git a/ui/mobile/src/__tests__/services/ws.service.test.ts b/ui/mobile/src/__tests__/services/ws.service.test.ts index 5342b940..7f2dade7 100644 --- a/ui/mobile/src/__tests__/services/ws.service.test.ts +++ b/ui/mobile/src/__tests__/services/ws.service.test.ts @@ -109,6 +109,62 @@ describe('WsService', () => { expect(ws.getStatus()).toBe('simulated'); ws.disconnect(); }); + + it('cleans up active sockets and pending reconnects when switching to simulation mode', () => { + const sockets: MockWebSocketInstance[] = []; + const OrigWebSocket = globalThis.WebSocket; + + class MockWebSocket { + static OPEN = 1; + static CONNECTING = 0; + static CLOSED = 3; + readyState = MockWebSocket.OPEN; + onopen: (() => void) | null = null; + onclose: ((event: { code: number }) => void) | null = null; + onerror: (() => void) | null = null; + onmessage: (() => void) | null = null; + close = jest.fn((code?: number) => { + this.readyState = MockWebSocket.CLOSED; + this.onclose?.({ code: code ?? 1000 }); + }); + + constructor(_url: string) { + sockets.push(this as unknown as MockWebSocketInstance); + } + } + + type MockWebSocketInstance = { + onclose: ((event: { code: number }) => void) | null; + close: jest.Mock; + }; + + globalThis.WebSocket = MockWebSocket as any; + + try { + const ws = createWsService(); + ws.connect('http://localhost:3000'); + expect(sockets).toHaveLength(1); + + ws.connect(''); + expect(sockets[0].close).toHaveBeenCalledWith(1000, 'switch to simulation'); + expect(ws.getStatus()).toBe('simulated'); + ws.disconnect(); + + const wsWithReconnect = createWsService(); + wsWithReconnect.connect('http://localhost:3000'); + expect(sockets).toHaveLength(2); + + sockets[1].onclose?.({ code: 1006 }); + wsWithReconnect.connect(''); + + jest.advanceTimersByTime(60_000); + expect(sockets).toHaveLength(2); + expect(wsWithReconnect.getStatus()).toBe('simulated'); + wsWithReconnect.disconnect(); + } finally { + globalThis.WebSocket = OrigWebSocket; + } + }); }); describe('subscribe and unsubscribe', () => { diff --git a/ui/mobile/src/services/ws.service.ts b/ui/mobile/src/services/ws.service.ts index 8cd398ba..9eb76f9f 100644 --- a/ui/mobile/src/services/ws.service.ts +++ b/ui/mobile/src/services/ws.service.ts @@ -22,6 +22,13 @@ class WsService { this.reconnectAttempt = 0; if (!url) { + this.clearReconnectTimer(); + if (this.ws) { + const socket = this.ws; + this.ws = null; + socket.onclose = null; + socket.close(1000, 'switch to simulation'); + } this.handleStatusChange('simulated'); this.startSimulation(); return;