This commit is contained in:
2026-04-08 21:26:18 +08:00
commit 8fdc7ac0c3
401 changed files with 53093 additions and 0 deletions

View File

@@ -0,0 +1,304 @@
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)
})
})
})