Files
test/tests/unit/composables/useRuntimeConsole.test.ts
2026-04-08 21:26:18 +08:00

305 lines
9.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useRuntimeConsole } from '@/composables/useRuntimeConsole'
describe('useRuntimeConsole', () => {
let console_: ReturnType<typeof useRuntimeConsole>
beforeEach(() => {
console_ = useRuntimeConsole()
console_.clear()
console_.levelFilter.value = ''
console_.categoryFilter.value = ''
console_.searchText.value = ''
})
// ──── log() ────
describe('log() 添加运行日志', () => {
it('应添加一条日志到列表头部', () => {
console_.log('INFO', '系统', '启动完成')
expect(console_.entries.value).toHaveLength(1)
expect(console_.entries.value[0]).toMatchObject({
level: 'INFO',
category: '系统',
message: '启动完成',
})
})
it('应支持 quality 和 source 可选参数', () => {
console_.log('WARN', '信号', '信号质量下降', 'BAD', 'PLC-01')
const entry = console_.entries.value[0]
expect(entry.quality).toBe('BAD')
expect(entry.source).toBe('PLC-01')
})
it('quality 和 source 未提供时应为 undefined', () => {
console_.log('INFO', '系统', '正常运行')
const entry = console_.entries.value[0]
expect(entry.quality).toBeUndefined()
expect(entry.source).toBeUndefined()
})
it('新日志应排在最前面', () => {
console_.log('INFO', '系统', '第一条')
console_.log('ERROR', '告警', '第二条')
expect(console_.entries.value[0].message).toBe('第二条')
expect(console_.entries.value[1].message).toBe('第一条')
})
it('超过 1000 条时应截断旧日志', () => {
for (let i = 0; i < 1010; i++) {
console_.log('INFO', '系统', `消息${i}`)
}
expect(console_.entries.value).toHaveLength(1000)
expect(console_.entries.value[0].message).toBe('消息1009')
})
it('每条日志应有唯一 id 和时间戳', () => {
console_.log('INFO', '系统', '消息A')
console_.log('INFO', '系统', '消息B')
const [a, b] = console_.entries.value
expect(a.id).not.toBe(b.id)
expect(a.timestamp).toBeTruthy()
expect(b.timestamp).toBeTruthy()
})
})
// ──── 便捷方法 info/warn/error ────
describe('info() 便捷方法', () => {
it('应以 INFO 级别记录', () => {
console_.info('系统', '信息消息')
expect(console_.entries.value[0].level).toBe('INFO')
expect(console_.entries.value[0].message).toBe('信息消息')
})
it('应支持 quality 参数', () => {
console_.info('信号', '信号正常', 'GOOD')
expect(console_.entries.value[0].quality).toBe('GOOD')
})
})
describe('warn() 便捷方法', () => {
it('应以 WARN 级别记录', () => {
console_.warn('系统', '警告消息')
expect(console_.entries.value[0].level).toBe('WARN')
})
it('应支持 quality 参数', () => {
console_.warn('信号', '信号不确定', 'UNCERTAIN')
expect(console_.entries.value[0].quality).toBe('UNCERTAIN')
})
})
describe('error() 便捷方法', () => {
it('应以 ERROR 级别记录', () => {
console_.error('系统', '错误消息')
expect(console_.entries.value[0].level).toBe('ERROR')
})
it('应支持 source 参数', () => {
console_.error('通信', '连接超时', 'PLC-02')
expect(console_.entries.value[0].source).toBe('PLC-02')
})
it('quality 应为 undefinederror 不传 quality', () => {
console_.error('系统', '错误消息', 'source')
expect(console_.entries.value[0].quality).toBeUndefined()
})
})
// ──── clear() ────
describe('clear() 清空日志', () => {
it('应清空所有条目', () => {
console_.info('系统', '消息1')
console_.warn('告警', '消息2')
console_.clear()
expect(console_.entries.value).toHaveLength(0)
})
})
// ──── filteredEntries ────
describe('filteredEntries 过滤逻辑', () => {
beforeEach(() => {
console_.info('系统', '系统启动')
console_.warn('信号', '信号波动')
console_.error('通信', '连接中断', 'PLC-01')
console_.info('信号', '信号恢复正常')
})
it('无过滤条件时应返回全部', () => {
expect(console_.filteredEntries.value).toHaveLength(4)
})
it('按 levelFilter 过滤级别', () => {
console_.levelFilter.value = 'INFO'
expect(console_.filteredEntries.value).toHaveLength(2)
expect(console_.filteredEntries.value.every(e => e.level === 'INFO')).toBe(true)
})
it('按 categoryFilter 过滤分类', () => {
console_.categoryFilter.value = '信号'
expect(console_.filteredEntries.value).toHaveLength(2)
expect(console_.filteredEntries.value.every(e => e.category === '信号')).toBe(true)
})
it('按 searchText 模糊搜索 message', () => {
console_.searchText.value = '连接'
expect(console_.filteredEntries.value).toHaveLength(1)
expect(console_.filteredEntries.value[0].message).toBe('连接中断')
})
it('按 searchText 模糊搜索 source', () => {
console_.searchText.value = 'PLC'
expect(console_.filteredEntries.value).toHaveLength(1)
expect(console_.filteredEntries.value[0].source).toBe('PLC-01')
})
it('按 searchText 模糊搜索 category', () => {
console_.searchText.value = '通信'
expect(console_.filteredEntries.value).toHaveLength(1)
expect(console_.filteredEntries.value[0].category).toBe('通信')
})
it('searchText 不区分大小写', () => {
console_.log('INFO', 'System', 'Connection OK', undefined, 'Device-A')
console_.searchText.value = 'connection'
expect(console_.filteredEntries.value).toHaveLength(1)
expect(console_.filteredEntries.value[0].message).toBe('Connection OK')
})
it('多个过滤条件组合使用', () => {
console_.levelFilter.value = 'INFO'
console_.categoryFilter.value = '信号'
expect(console_.filteredEntries.value).toHaveLength(1)
expect(console_.filteredEntries.value[0].message).toBe('信号恢复正常')
})
it('三个过滤条件同时使用', () => {
console_.levelFilter.value = 'INFO'
console_.categoryFilter.value = '信号'
console_.searchText.value = '恢复'
expect(console_.filteredEntries.value).toHaveLength(1)
expect(console_.filteredEntries.value[0].message).toBe('信号恢复正常')
})
it('过滤条件无匹配时返回空数组', () => {
console_.levelFilter.value = 'ERROR'
console_.categoryFilter.value = '系统'
expect(console_.filteredEntries.value).toHaveLength(0)
})
})
// ──── categories ────
describe('categories 计算属性', () => {
it('空日志时返回空数组', () => {
expect(console_.categories.value).toEqual([])
})
it('应返回去重排序后的分类列表', () => {
console_.info('系统', '消息1')
console_.info('信号', '消息2')
console_.warn('系统', '消息3')
console_.error('通信', '消息4')
expect(console_.categories.value).toEqual(['信号', '系统', '通信'])
})
})
// ──── levelCounts ────
describe('levelCounts 计算属性', () => {
it('空日志时所有计数为零', () => {
expect(console_.levelCounts.value).toEqual({ INFO: 0, WARN: 0, ERROR: 0 })
})
it('应正确统计各级别数量', () => {
console_.info('系统', '消息1')
console_.info('系统', '消息2')
console_.warn('告警', '消息3')
console_.error('通信', '消息4')
console_.error('通信', '消息5')
console_.error('通信', '消息6')
expect(console_.levelCounts.value).toEqual({
INFO: 2,
WARN: 1,
ERROR: 3,
})
})
})
// ──── exportJSON() ────
describe('exportJSON() 导出日志', () => {
it('应创建 Blob 下载并清理', () => {
console_.info('系统', '测试消息')
const mockClick = vi.fn()
const mockAppendChild = vi.spyOn(document.body, 'appendChild').mockImplementation(node => node)
const mockRemoveChild = vi.spyOn(document.body, 'removeChild').mockImplementation(node => node)
const mockCreateElement = vi.spyOn(document, 'createElement').mockReturnValue({
get href() { return '' },
set href(_: string) {},
get download() { return '' },
set download(_: string) {},
click: mockClick,
} as unknown as HTMLAnchorElement)
const mockRevokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {})
const mockCreateObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:test')
console_.exportJSON()
expect(mockCreateObjectURL).toHaveBeenCalled()
expect(mockClick).toHaveBeenCalled()
expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:test')
mockAppendChild.mockRestore()
mockRemoveChild.mockRestore()
mockCreateElement.mockRestore()
mockRevokeObjectURL.mockRestore()
mockCreateObjectURL.mockRestore()
})
})
// ──── 单例共享 ────
describe('单例模式', () => {
it('多次调用 useRuntimeConsole() 应共享同一份状态', () => {
const c1 = useRuntimeConsole()
const c2 = useRuntimeConsole()
c1.info('系统', '测试')
expect(c2.entries.value).toHaveLength(c1.entries.value.length)
expect(c2.entries.value[0].id).toBe(c1.entries.value[0].id)
})
})
})