move claude-marketplace to ai-proj-helper
This commit is contained in:
174
plugins/dev-test-plugin/skills/dev-test/frontend-testing.md
Normal file
174
plugins/dev-test-plugin/skills/dev-test/frontend-testing.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 前端测试 (Vue + React)
|
||||
|
||||
## Vue 前端测试
|
||||
|
||||
### 测试框架
|
||||
|
||||
- **Vitest**: 测试运行器
|
||||
- **Vue Test Utils**: 组件测试
|
||||
- **MSW**: API Mock
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
npm run test:watch
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
### 组件测试
|
||||
|
||||
```typescript
|
||||
// UserList.test.ts
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserList from './UserList.vue'
|
||||
|
||||
describe('UserList', () => {
|
||||
it('renders user list', () => {
|
||||
const wrapper = mount(UserList, {
|
||||
props: {
|
||||
users: [
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 2, name: 'Bob' }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.user-item')).toHaveLength(2)
|
||||
expect(wrapper.text()).toContain('Alice')
|
||||
})
|
||||
|
||||
it('emits select event', async () => {
|
||||
const wrapper = mount(UserList, {
|
||||
props: { users: [{ id: 1, name: 'Alice' }] }
|
||||
})
|
||||
|
||||
await wrapper.find('.user-item').trigger('click')
|
||||
|
||||
expect(wrapper.emitted('select')).toBeTruthy()
|
||||
expect(wrapper.emitted('select')[0]).toEqual([1])
|
||||
})
|
||||
|
||||
it('shows empty state', () => {
|
||||
const wrapper = mount(UserList, {
|
||||
props: { users: [] }
|
||||
})
|
||||
|
||||
expect(wrapper.find('.empty-state').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### API Mock (MSW)
|
||||
|
||||
```typescript
|
||||
// mocks/handlers.ts
|
||||
import { rest } from 'msw'
|
||||
|
||||
export const handlers = [
|
||||
rest.get('/api/v1/users', (req, res, ctx) => {
|
||||
return res(ctx.json({
|
||||
code: 0,
|
||||
data: {
|
||||
total: 2,
|
||||
list: [{ id: 1, name: 'Alice' }]
|
||||
}
|
||||
}))
|
||||
}),
|
||||
|
||||
rest.post('/api/v1/users', async (req, res, ctx) => {
|
||||
const body = await req.json()
|
||||
return res(ctx.json({
|
||||
code: 0,
|
||||
data: { id: 3, ...body }
|
||||
}))
|
||||
})
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## React 前端测试
|
||||
|
||||
### 测试框架
|
||||
|
||||
- **Jest**: 测试运行器
|
||||
- **React Testing Library**: 组件测试
|
||||
- **Playwright**: E2E 测试
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run test:e2e
|
||||
npm run test:e2e:headed
|
||||
```
|
||||
|
||||
### 组件测试
|
||||
|
||||
```typescript
|
||||
// TaskCard.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import TaskCard from './TaskCard'
|
||||
|
||||
describe('TaskCard', () => {
|
||||
const mockTask = {
|
||||
id: 1,
|
||||
title: 'Test Task',
|
||||
status: 'todo',
|
||||
priority: 'high'
|
||||
}
|
||||
|
||||
it('renders task title', () => {
|
||||
render(<TaskCard task={mockTask} />)
|
||||
expect(screen.getByText('Test Task')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays priority', () => {
|
||||
render(<TaskCard task={mockTask} />)
|
||||
expect(screen.getByText('high')).toHaveClass('priority-high')
|
||||
})
|
||||
|
||||
it('calls onClick', () => {
|
||||
const handleClick = jest.fn()
|
||||
render(<TaskCard task={mockTask} onClick={handleClick} />)
|
||||
|
||||
fireEvent.click(screen.getByRole('article'))
|
||||
|
||||
expect(handleClick).toHaveBeenCalledWith(mockTask)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Hook 测试
|
||||
|
||||
```typescript
|
||||
// useTimer.test.ts
|
||||
import { renderHook, act } from '@testing-library/react-hooks'
|
||||
import { useTimer } from './useTimer'
|
||||
|
||||
describe('useTimer', () => {
|
||||
beforeEach(() => jest.useFakeTimers())
|
||||
afterEach(() => jest.useRealTimers())
|
||||
|
||||
it('starts timer', () => {
|
||||
const { result } = renderHook(() => useTimer())
|
||||
|
||||
act(() => result.current.start())
|
||||
|
||||
expect(result.current.isRunning).toBe(true)
|
||||
})
|
||||
|
||||
it('increments time', () => {
|
||||
const { result } = renderHook(() => useTimer())
|
||||
|
||||
act(() => {
|
||||
result.current.start()
|
||||
jest.advanceTimersByTime(3000)
|
||||
})
|
||||
|
||||
expect(result.current.seconds).toBe(3)
|
||||
})
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user