初始化
This commit is contained in:
80
.cursor/skills/vue-testing/SKILL.md
Normal file
80
.cursor/skills/vue-testing/SKILL.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: vue-testing
|
||||
version: 1.0.0
|
||||
description: "Vue 3 + Vitest 前端测试策略与工作流。当需要编写单元测试、组件测试或 E2E 测试时使用。涵盖测试决策树和覆盖率标准。"
|
||||
---
|
||||
|
||||
> ⚠️ 核心执行流程已在 `.cursor/rules/skill-vue-testing.mdc` 中由 Cursor 自动注入。
|
||||
> 本文件提供完整模板、代码示例和边缘场景处理,供 Agent 按需深入 Read。
|
||||
|
||||
# Vue 3 + Vitest 前端测试
|
||||
|
||||
## 触发条件
|
||||
|
||||
用户要求编写测试、改善测试覆盖率、或询问测试策略。
|
||||
|
||||
## 测试决策树(⚠️ 每次写测试前必须先走)
|
||||
|
||||
```
|
||||
纯转换逻辑(parse/format/validate/compute)?→ 提取到 .utils.ts,写 Vitest 单元测试
|
||||
涉及复杂 UI 交互?→ Vue Test Utils 组件测试
|
||||
能提取为纯函数或 composable?→ 提取后写单元测试
|
||||
否则 → 组件测试
|
||||
跨页面流程、关键业务路径?→ Playwright E2E
|
||||
```
|
||||
|
||||
## 测试类型与工具
|
||||
|
||||
| 类型 | 工具 | 文件命名 |
|
||||
|---|---|---|
|
||||
| 单元测试 | Vitest | *.test.ts |
|
||||
| 组件测试 | Vitest + Vue Test Utils | *.test.ts |
|
||||
| E2E | Playwright | *.spec.ts |
|
||||
|
||||
## 核心原则
|
||||
|
||||
1. **逻辑提取** — 业务逻辑提取到 .utils.ts 纯函数,组件薄壳
|
||||
2. **穷举排列** — 有效/无效/空值/边界/安全输入
|
||||
3. **AAA 模式** — Arrange-Act-Assert
|
||||
4. **单一行为** — 每个 it() 只验证一个行为
|
||||
5. **命名** — `should <行为> when <条件>`
|
||||
|
||||
## 组件测试结构
|
||||
|
||||
Rendering(必须)、Props(必须)、User Interactions、Edge Cases(必须)。完整模板见 **Tier 3**。
|
||||
|
||||
## 增量式工作流(必须)
|
||||
|
||||
工具函数 → Composables → 简单组件 → 中等 → 复杂 → 集成。逐个文件:写测试→vitest run→PASS 才下一个。
|
||||
|
||||
## 复杂度阈值
|
||||
|
||||
< 30 标准结构。30–50 按 describe 分组。> 50 先重构再测试。500+ 行强制考虑拆分。
|
||||
|
||||
## Mock 策略
|
||||
|
||||
API/Router Mock。Pinia 用真实 Store。管理端 Element Plus 不 Mock;用户端不涉及 Element Plus(使用 Headless UI)。子组件不 Mock。完整策略见 **Tier 3**。
|
||||
|
||||
## 覆盖率目标
|
||||
|
||||
Function 100%、Statement 100%、Branch > 95%、Line > 95%。
|
||||
|
||||
## 验证
|
||||
|
||||
1. [ ] 走过决策树
|
||||
2. [ ] 逻辑已提取到 .utils.ts
|
||||
3. [ ] 穷举排列覆盖
|
||||
4. [ ] 每测试单行为
|
||||
5. [ ] 命名 should...when...
|
||||
6. [ ] Mock 正确
|
||||
7. [ ] 增量式工作流
|
||||
8. [ ] 覆盖率达标
|
||||
9. [ ] vitest run 全部通过
|
||||
|
||||
## Tier 3 深度参考
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| `references/testing-templates.md` | 组件/Composable 测试模板、Mock 策略、增量工作流 |
|
||||
| `references/vitest-config.md` | Vitest + Vue 3 配置 |
|
||||
| `references/mock-patterns.md` | Mock 模式速查 |
|
||||
31
.cursor/skills/vue-testing/references/mock-patterns.md
Normal file
31
.cursor/skills/vue-testing/references/mock-patterns.md
Normal 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 过多导致测试与真实行为偏离
|
||||
79
.cursor/skills/vue-testing/references/testing-templates.md
Normal file
79
.cursor/skills/vue-testing/references/testing-templates.md
Normal 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。
|
||||
39
.cursor/skills/vue-testing/references/vitest-config.md
Normal file
39
.cursor/skills/vue-testing/references/vitest-config.md
Normal 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%
|
||||
Reference in New Issue
Block a user