test: 集成 Vitest 测试框架和开发规范

- 安装 vitest, @vue/test-utils, jsdom 等测试依赖
- 配置 vitest.config.js 测试环境
- 添加 schema.test.ts (7个数据结构验证测试)
- 添加 useStore.test.ts (7个状态管理测试)
- 创建测试指南文档 (docs/testing.md)
- 创建测试规范文档 (docs/testing-rules.md)
- 创建开发规范文档 (docs/development-rules.md)
- 创建开发工作流程文档 (docs/1management/workflow.md)
- 添加测试相关 npm scripts (test, test:watch, test:ui, test:coverage)
- 所有测试通过 (14/14)
This commit is contained in:
2026-02-12 23:25:13 +08:00
parent c4d701b443
commit 92aa4094f5
13 changed files with 4245 additions and 17 deletions

View File

@@ -0,0 +1,98 @@
import { describe, it, expect } from 'vitest'
import {
CURRENT_SCHEMA_VERSION,
DefaultNodeStyle,
type GraphNode,
type GraphEdge,
type NodeProperties,
type RootDocument
} from '../ts/schema'
describe('Schema 数据结构验证', () => {
it('当前 schema 版本应该是 1.0.0', () => {
expect(CURRENT_SCHEMA_VERSION).toBe('1.0.0')
})
it('DefaultNodeStyle 应该包含正确的默认值', () => {
expect(DefaultNodeStyle).toMatchObject({
width: 180,
height: 120,
rotate: 0,
fill: '#ffffff',
stroke: '#dcdfe6'
})
})
it('创建 GraphNode 应该符合类型定义', () => {
const node: GraphNode = {
id: 'node-1',
type: 'rect',
x: 100,
y: 200,
properties: {
style: {
width: 200,
height: 150,
fill: '#ff0000'
}
}
}
expect(node.id).toBe('node-1')
expect(node.type).toBe('rect')
expect(node.properties.style.width).toBe(200)
})
it('创建 GraphEdge 应该包含必需字段', () => {
const edge: GraphEdge = {
id: 'edge-1',
sourceNodeId: 'node-1',
targetNodeId: 'node-2'
}
expect(edge.id).toBe('edge-1')
expect(edge.sourceNodeId).toBe('node-1')
expect(edge.targetNodeId).toBe('node-2')
})
it('NodeProperties 应该支持式神数据', () => {
const properties: NodeProperties = {
style: DefaultNodeStyle,
shikigami: {
name: '茨木童子',
avatar: '/assets/Shikigami/ibaraki.png',
rarity: 'SSR'
}
}
expect(properties.shikigami?.name).toBe('茨木童子')
expect(properties.shikigami?.rarity).toBe('SSR')
})
it('NodeProperties 应该支持御魂数据', () => {
const properties: NodeProperties = {
style: DefaultNodeStyle,
yuhun: {
name: '破势',
type: '攻击',
avatar: '/assets/Yuhun/poshi.png'
}
}
expect(properties.yuhun?.name).toBe('破势')
expect(properties.yuhun?.type).toBe('攻击')
})
it('RootDocument 应该包含文件列表和活动文件', () => {
const doc: RootDocument = {
schemaVersion: '1.0.0',
fileList: [],
activeFile: 'File 1',
activeFileId: 'f_123'
}
expect(doc.schemaVersion).toBe('1.0.0')
expect(doc.activeFile).toBe('File 1')
expect(doc.activeFileId).toBe('f_123')
})
})

View File

@@ -0,0 +1,157 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useFilesStore } from '../ts/useStore'
// Mock localStorage
const localStorageMock = (() => {
let store: Record<string, string> = {}
return {
getItem: (key: string) => store[key] || null,
setItem: (key: string, value: string) => { store[key] = value },
removeItem: (key: string) => { delete store[key] },
clear: () => { store = {} }
}
})()
Object.defineProperty(global, 'localStorage', { value: localStorageMock })
// Mock ElMessageBox 和 useGlobalMessage
vi.mock('element-plus', () => ({
ElMessageBox: {
confirm: vi.fn()
}
}))
vi.mock('../ts/useGlobalMessage', () => ({
useGlobalMessage: () => ({
showMessage: vi.fn()
})
}))
vi.mock('../ts/useLogicFlow', () => ({
getLogicFlowInstance: vi.fn(() => ({
getGraphRawData: vi.fn(() => ({ nodes: [], edges: [] })),
getTransform: vi.fn(() => ({
SCALE_X: 1,
SCALE_Y: 1,
TRANSLATE_X: 0,
TRANSLATE_Y: 0
}))
}))
}))
describe('useFilesStore 数据操作测试', () => {
beforeEach(() => {
setActivePinia(createPinia())
localStorageMock.clear()
})
it('应该初始化默认文件列表', () => {
const store = useFilesStore()
store.initializeWithPrompt()
expect(store.fileList.length).toBeGreaterThan(0)
expect(store.fileList[0].name).toBe('File 1')
expect(store.fileList[0].type).toBe('FLOW')
})
it('添加新文件应该增加文件列表长度', async () => {
const store = useFilesStore()
store.initializeWithPrompt()
const initialLength = store.fileList.length
store.addTab()
// 等待 requestAnimationFrame 完成
await new Promise(resolve => setTimeout(resolve, 50))
expect(store.fileList.length).toBe(initialLength + 1)
expect(store.fileList[store.fileList.length - 1].name).toContain('File')
})
it('删除文件应该减少文件列表长度', async () => {
const store = useFilesStore()
store.initializeWithPrompt()
store.addTab()
// 等待添加完成
await new Promise(resolve => setTimeout(resolve, 50))
const initialLength = store.fileList.length
const fileToDelete = store.fileList[0]
store.removeTab(fileToDelete.id)
expect(store.fileList.length).toBe(initialLength - 1)
})
it('切换活动文件应该更新 activeFileId', async () => {
const store = useFilesStore()
store.initializeWithPrompt()
store.addTab()
// 等待添加完成
await new Promise(resolve => setTimeout(resolve, 50))
const secondFile = store.fileList[1]
store.activeFileId = secondFile.id
expect(store.activeFileId).toBe(secondFile.id)
})
it('visibleFiles 应该只返回可见文件', async () => {
const store = useFilesStore()
store.initializeWithPrompt()
store.addTab()
// 等待添加完成
await new Promise(resolve => setTimeout(resolve, 50))
// 隐藏第一个文件
store.fileList[0].visible = false
expect(store.visibleFiles.length).toBe(store.fileList.length - 1)
expect(store.visibleFiles.every(f => f.visible)).toBe(true)
})
it('导入数据应该正确恢复文件列表', () => {
const store = useFilesStore()
const mockData = {
schemaVersion: '1.0.0',
fileList: [
{
id: 'test-1',
name: 'Test File',
label: 'Test File',
visible: true,
type: 'FLOW',
graphRawData: { nodes: [], edges: [] }
}
],
activeFileId: 'test-1',
activeFile: 'Test File'
}
store.importData(mockData)
expect(store.fileList.length).toBe(1)
expect(store.fileList[0].name).toBe('Test File')
expect(store.activeFileId).toBe('test-1')
})
it('重置工作区应该恢复到默认状态', async () => {
const store = useFilesStore()
store.initializeWithPrompt()
store.addTab()
store.addTab()
// 等待添加完成
await new Promise(resolve => setTimeout(resolve, 100))
store.resetWorkspace()
expect(store.fileList.length).toBe(1)
expect(store.fileList[0].name).toBe('File 1')
})
})