import { io } from 'socket.io-client' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { WebSocketClient } from './client' import type { Socket } from 'socket.io-client' vi.mock('socket.io-client') interface MockSocket extends Partial { connected: boolean on: ReturnType off: ReturnType emit: ReturnType disconnect: ReturnType removeAllListeners: ReturnType connect: ReturnType } describe('WebSocketClient', () => { let client: WebSocketClient let mockSocket: MockSocket beforeEach(() => { mockSocket = { connected: false, on: vi.fn(), off: vi.fn(), emit: vi.fn(), disconnect: vi.fn(), removeAllListeners: vi.fn(), connect: vi.fn(), } vi.mocked(io).mockReturnValue(mockSocket as unknown as Socket) }) afterEach(() => { vi.clearAllMocks() }) describe('constructor', () => { it('should create client with default config', () => { client = new WebSocketClient({ url: 'ws://localhost:4001' }) expect(client).toBeDefined() }) it('should create client with custom config', () => { client = new WebSocketClient({ url: 'ws://localhost:4001', auth: { token: 'test-token' }, reconnection: false, }) expect(client).toBeDefined() }) }) describe('connect', () => { it('should create socket connection', () => { client = new WebSocketClient({ url: 'ws://localhost:4001', autoConnect: false }) client.connect() expect(io).toHaveBeenCalledWith('ws://localhost:4001', expect.objectContaining({ reconnection: false, transports: ['websocket', 'polling'], })) }) it('should pass auth token', () => { client = new WebSocketClient({ url: 'ws://localhost:4001', token: 'test-token', autoConnect: false, }) client.connect() expect(io).toHaveBeenCalledWith('ws://localhost:4001', expect.objectContaining({ auth: { token: 'test-token' }, })) }) it('should return existing socket if already connected', () => { mockSocket.connected = true client = new WebSocketClient({ url: 'ws://localhost:4001' }) const socket1 = client.connect() const socket2 = client.connect() expect(socket1).toBe(socket2) expect(io).toHaveBeenCalledTimes(1) }) }) describe('disconnect', () => { it('should disconnect socket', () => { client = new WebSocketClient({ url: 'ws://localhost:4001' }) client.connect() client.disconnect() expect(mockSocket.disconnect).toHaveBeenCalled() expect(client.getSocket()).toBeNull() }) }) describe('emit', () => { it('should emit event through socket', () => { mockSocket.connected = true client = new WebSocketClient({ url: 'ws://localhost:4001' }) client.connect() client.emit('test.event', { data: 'test' }) expect(mockSocket.emit).toHaveBeenCalledWith('test.event', { data: 'test' }) }) it('should warn if not connected', () => { const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) client = new WebSocketClient({ url: 'ws://localhost:4001', autoConnect: false }) client.emit('test.event', { data: 'test' }) expect(consoleSpy).toHaveBeenCalledWith( expect.stringContaining('Cannot emit') ) consoleSpy.mockRestore() }) }) describe('on', () => { it('should register event listener', () => { client = new WebSocketClient({ url: 'ws://localhost:4001' }) client.connect() const callback = vi.fn() client.on('test.event', callback) expect(mockSocket.on).toHaveBeenCalledWith('test.event', callback) }) it('should return unsubscribe function', () => { client = new WebSocketClient({ url: 'ws://localhost:4001' }) client.connect() const callback = vi.fn() const unsubscribe = client.on('test.event', callback) unsubscribe() expect(mockSocket.off).toHaveBeenCalledWith('test.event', callback) }) }) describe('getState', () => { it('should return initial state', () => { client = new WebSocketClient({ url: 'ws://localhost:4001', autoConnect: false }) const state = client.getState() expect(state).toEqual({ connected: false, connecting: false, error: null, }) }) }) })