初始化

This commit is contained in:
2026-03-05 21:27:11 +08:00
commit 130de0fd5d
140 changed files with 21972 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
# Mock Patterns Quick Reference
## API Mock
```js
import { vi } from 'vitest'
import * as userApi from '@/api/user'
vi.spyOn(userApi, 'getUser').mockResolvedValue({ id: 1, name: 'demo' })
```
## Router Mock
```js
const push = vi.fn()
vi.mock('vue-router', () => ({
useRouter: () => ({ push }),
useRoute: () => ({ params: { id: '1' } })
}))
```
## Pinia
- 默认优先真实 store`setActivePinia(createPinia())`
- 仅在外部依赖复杂且非本次关注点时 mock action
## 禁止过度 Mock
- 不要 mock Vue 内置行为
- 不要 mock 被测模块本身
- 避免因为 mock 过多导致测试与真实行为偏离

View File

@@ -0,0 +1,79 @@
# Vue Testing — 测试模板与策略
> 主流程见 SKILL.md本文档为组件测试、Composable 测试、Mock 策略的完整代码。
>
> **⚠️ 双前端区分**:管理端测试需注册 Element Plus 插件,用户端测试**不注册 Element Plus**。
## 组件测试模板
```typescript
import { mount } from '@vue/test-utils'
import { createPinia, setActivePinia } from 'pinia'
// 管理端import ElementPlus from 'element-plus'
// 用户端:不引入 Element Plus
import ComponentName from './ComponentName.vue'
describe('ComponentName', () => {
beforeEach(() => setActivePinia(createPinia()))
describe('Rendering', () => {
it('should render without crashing', () => {
// 管理端global: { plugins: [ElementPlus] }
// 用户端global: {}(无 Element Plus
const wrapper = mount(ComponentName, { props: { title: 'Test' } })
expect(wrapper.find('[data-testid="component-name"]').exists()).toBe(true)
})
})
describe('Props', () => {
it('should display title from props', () => { ... })
})
describe('User Interactions', () => {
it('should emit refresh when button clicked', async () => { ... })
})
describe('Edge Cases', () => {
it('should handle empty props gracefully', () => { ... })
})
})
```
## Composable 测试模板
```typescript
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('should initialize with default value', () => { const { count } = useCounter(); expect(count.value).toBe(0) })
it('should increment count', () => { const { count, increment } = useCounter(); increment(); expect(count.value).toBe(1) })
it('should not go below zero', () => { ... })
})
```
## 逻辑提取示例
```typescript
// OrderStatus.utils.ts
export function getStatusText(status, shipped) {
if (status === 'paid' && shipped) return '已发货'
if (status === 'paid') return '待发货'
if (status === 'refunded') return '已退款'
return '待支付'
}
// OrderStatus.utils.test.ts — 穷举排列:有效/无效/空值/边界
```
## Mock 策略
| 依赖 | Mock? | 方式 |
|------|-------|------|
| @/api/* | ✅ | vi.mock('@/api/xxx') |
| vue-router | ✅ | vi.mock('vue-router') |
| Pinia | ⚠️ 真实 | setActivePinia(createPinia()) |
| Element Plus | ❌ | 全局 plugin |
| 子组件 | ❌ | 直接导入 |
| window/document | ✅ | vi.stubGlobal |
## 增量式工作流
1. 工具函数 → Composables → 简单组件 → 中等组件 → 复杂组件 → 集成。2. 每个文件:写测试→运行 vitest→PASS 才下一个。3. 最终 vitest run --coverage。

View File

@@ -0,0 +1,39 @@
# Vitest Configuration (Vue 3)
## 最小配置
```js
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./tests/setup.ts'],
coverage: {
reporter: ['text', 'html'],
include: ['src/**/*.{js,vue}']
}
}
})
```
## setup 示例
```js
// tests/setup.ts
import { config } from '@vue/test-utils'
config.global.mocks = {
$t: (k) => k
}
```
## 建议
- 单元测试优先针对 `.utils.ts` 与 composables
- 组件测试只覆盖关键交互与边界状态
- 覆盖率门槛与业务关键性一致,不盲目追求 100%