mirror of https://github.com/allinssl/allinssl
306 lines
9.1 KiB
JavaScript
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
|