Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
160 lines
4.4 KiB
TypeScript
160 lines
4.4 KiB
TypeScript
import { io } from 'socket.io-client'
|
|
import type { Socket } from 'socket.io-client'
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
|
|
import { WebSocketClient } from './client'
|
|
|
|
vi.mock('socket.io-client')
|
|
|
|
interface MockSocket extends Partial<Socket> {
|
|
connected: boolean
|
|
on: ReturnType<typeof vi.fn>
|
|
off: ReturnType<typeof vi.fn>
|
|
emit: ReturnType<typeof vi.fn>
|
|
disconnect: ReturnType<typeof vi.fn>
|
|
removeAllListeners: ReturnType<typeof vi.fn>
|
|
connect: ReturnType<typeof vi.fn>
|
|
}
|
|
|
|
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,
|
|
})
|
|
})
|
|
})
|
|
})
|