allinssl/frontend/packages/vue/hooks/test/socket.spec.js

306 lines
9.1 KiB
JavaScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { nextTick, ref } from 'vue'
import useSocket from '../src/socket'
// 模拟WebSocket
class MockWebSocket {
url
onopen = null
onclose = null
onmessage = null
onerror = null
readyState = 0 // CONNECTING
constructor(url) {
this.url = url
// 模拟异步连接
setTimeout(() => {
this.readyState = 1 // OPEN
this.onopen?.()
}, 50)
}
send = vi.fn((data) => {
// 模拟数据发送成功
return true
})
close = vi.fn(() => {
this.readyState = 3 // CLOSED
this.onclose?.({ code: 1000 })
})
// 模拟触发错误
triggerError(error) {
this.onerror?.(error)
}
// 模拟接收消息
simulateMessage(data) {
this.onmessage?.({ data: typeof data === 'object' ? JSON.stringify(data) : data })
}
// 模拟连接关闭
simulateClose(code = 1000, reason = '') {
this.readyState = 3 // CLOSED
this.onclose?.({ code, reason })
}
}
describe('useSocket', () => {
const originalWebSocket = global.WebSocket
let mockSocket
beforeEach(() => {
vi.useFakeTimers()
// 安装模拟的WebSocket
global.WebSocket = vi.fn((url) => {
mockSocket = new MockWebSocket(url)
return mockSocket
})
// 监视console.error和warn
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'warn').mockImplementation(() => {})
})
afterEach(() => {
global.WebSocket = originalWebSocket
vi.restoreAllMocks()
vi.useRealTimers()
})
it('应该连接到WebSocket', async () => {
const url = 'ws://example.com'
const { socket, connected } = useSocket(url)
expect(global.WebSocket).toHaveBeenCalledWith(url)
expect(socket.value).toBeTruthy()
expect(connected.value).toBe(false)
// 触发连接
vi.advanceTimersByTime(50)
await nextTick()
expect(connected.value).toBe(true)
})
it('应该发送消息', async () => {
const { socket, send, connected } = useSocket('ws://example.com')
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
expect(connected.value).toBe(true)
// 发送消息
send({ type: 'test', data: '测试数据' })
expect(mockSocket.send).toHaveBeenCalledWith(JSON.stringify({ type: 'test', data: '测试数据' }))
// 测试发送字符串
send('纯文本消息')
expect(mockSocket.send).toHaveBeenCalledWith('纯文本消息')
})
it('应该接收消息', async () => {
const { message, connected } = useSocket('ws://example.com')
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
expect(connected.value).toBe(true)
// 模拟接收JSON消息
const testMessage = { type: 'update', data: '新数据' }
mockSocket.onmessage?.({ data: JSON.stringify(testMessage) })
await nextTick()
expect(message.value).toEqual(testMessage)
// 模拟接收非JSON消息
mockSocket.onmessage?.({ data: 'plain text message' })
await nextTick()
expect(message.value).toEqual('plain text message')
})
it('应该支持数据中间件', async () => {
// 创建中间件函数
const middleware = (data) => {
if (typeof data === 'string') {
try {
const parsed = JSON.parse(data)
return { ...parsed, processed: true }
} catch (e) {
return `处理后: ${data}`
}
}
return data
}
const { message } = useSocket('ws://example.com', { middleware })
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 模拟接收JSON消息
const testMessage = { type: 'update', data: '新数据' }
mockSocket.simulateMessage(testMessage)
await nextTick()
// 验证中间件处理了数据
expect(message.value).toEqual({ ...testMessage, processed: true })
// 测试非JSON消息
mockSocket.simulateMessage('plain text')
await nextTick()
expect(message.value).toBe('处理后: plain text')
})
it('应该支持多个中间件函数', async () => {
// 创建多个中间件函数
const middleware1 = (data) => {
if (typeof data === 'string') {
try {
return JSON.parse(data)
} catch (e) {
return data
}
}
return data
}
const middleware2 = (data) => {
if (typeof data === 'object') {
return { ...data, step2: true }
}
return `Step2: ${data}`
}
const { message } = useSocket('ws://example.com', {
middleware: [middleware1, middleware2],
})
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 模拟接收JSON消息
mockSocket.simulateMessage({ type: 'test' })
await nextTick()
// 验证多个中间件都处理了数据
expect(message.value).toEqual({ type: 'test', step2: true })
// 测试字符串消息
mockSocket.simulateMessage('text')
await nextTick()
expect(message.value).toBe('Step2: text')
})
it('应该断开连接', async () => {
const { disconnect, connected } = useSocket('ws://example.com')
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
expect(connected.value).toBe(true)
// 断开连接
disconnect()
await nextTick()
expect(mockSocket.close).toHaveBeenCalled()
expect(connected.value).toBe(false)
})
it('应该发送心跳包', async () => {
// 使用较短的心跳间隔
useSocket('ws://example.com', { heartbeatInterval: 100, heartbeatMessage: 'PING' })
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 清除之前的send调用
mockSocket.send.mockClear()
// 等待心跳间隔
vi.advanceTimersByTime(100)
// 验证发送了心跳包
expect(mockSocket.send).toHaveBeenCalledWith('PING')
// 再等待一个间隔,应该再次发送
mockSocket.send.mockClear()
vi.advanceTimersByTime(100)
expect(mockSocket.send).toHaveBeenCalledWith('PING')
})
it('应该在连接关闭后自动重连', async () => {
useSocket('ws://example.com', { autoReconnect: true, reconnectDelay: 200 })
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 模拟连接关闭
mockSocket.onclose?.({ code: 1006 }) // 非正常关闭
await nextTick()
// 清除之前的WebSocket调用
global.WebSocket.mockClear()
// 等待重连延迟
vi.advanceTimersByTime(200)
// 验证尝试重连
expect(global.WebSocket).toHaveBeenCalledWith('ws://example.com')
})
it('主动断开连接不应该触发自动重连', async () => {
const { disconnect } = useSocket('ws://example.com', { autoReconnect: true, reconnectDelay: 200 })
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 主动断开连接
disconnect()
await nextTick()
// 清除之前的WebSocket调用
global.WebSocket.mockClear()
// 等待重连延迟
vi.advanceTimersByTime(200)
// 验证没有尝试重连
expect(global.WebSocket).not.toHaveBeenCalled()
})
it('应该在环境不支持WebSocket时给出警告', async () => {
// 临时删除WebSocket
global.WebSocket = undefined
useSocket('ws://example.com')
// 验证警告信息
expect(console.error).toHaveBeenCalledWith('WebSocket is not supported in this environment.')
})
it('应该在到达最大重连次数后停止尝试', async () => {
useSocket('ws://example.com', {
autoReconnect: true,
reconnectDelay: 100,
maxReconnectAttempts: 2,
})
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 第一次断开连接
mockSocket.simulateClose(1006)
await nextTick()
// 第一次重连
vi.advanceTimersByTime(100)
expect(global.WebSocket).toHaveBeenCalledTimes(2)
// 再次断开
mockSocket.simulateClose(1006)
await nextTick()
// 第二次重连
vi.advanceTimersByTime(100)
expect(global.WebSocket).toHaveBeenCalledTimes(3)
// 再次断开
mockSocket.simulateClose(1006)
await nextTick()
// 清除WebSocket调用记录
global.WebSocket.mockClear()
// 等待可能的第三次重连
vi.advanceTimersByTime(100)
// 验证没有第三次重连尝试
expect(global.WebSocket).not.toHaveBeenCalled()
})
it('应该在连接错误时记录日志', async () => {
useSocket('ws://example.com')
// 连接WebSocket
vi.advanceTimersByTime(50)
await nextTick()
// 模拟连接错误
const errorEvent = new Event('error')
mockSocket.triggerError(errorEvent)
// 验证错误被记录
expect(console.error).toHaveBeenCalledWith('WebSocket error:', errorEvent)
})
it('应该支持初始化后手动连接', async () => {
const { socket, connect, connected } = useSocket('ws://example.com', {
autoConnect: false, // 不自动连接
})
// 验证初始化时没有连接
expect(global.WebSocket).not.toHaveBeenCalled()
expect(socket.value).toBe(null)
// 手动连接
connect()
// 验证连接建立
expect(global.WebSocket).toHaveBeenCalledWith('ws://example.com')
// 触发连接完成
vi.advanceTimersByTime(50)
await nextTick()
expect(connected.value).toBe(true)
})
it('应该支持动态修改URL', async () => {
const dynamicUrl = ref('ws://example.com')
const { connected } = useSocket(dynamicUrl)
// 初始连接
vi.advanceTimersByTime(50)
await nextTick()
expect(connected.value).toBe(true)
expect(global.WebSocket).toHaveBeenCalledWith('ws://example.com')
// 断开现有连接
mockSocket.simulateClose()
await nextTick()
// 修改URL
dynamicUrl.value = 'ws://new-server.com'
await nextTick()
// 验证使用新URL重连
expect(global.WebSocket).toHaveBeenCalledWith('ws://new-server.com')
})
})
//# sourceMappingURL=socket.spec.js.map