测试与部署
约 586 字大约 2 分钟
布欧-Lewyon
2026-05-15
单元测试
// store/__tests__/board-store.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { useBoardStore } from '../board-store'
describe('BoardStore', () => {
const mockBoard: Board = {
id: 'board-1',
title: 'Test Board',
columns: [
{
id: 'col-1',
title: 'Todo',
cards: [{ id: 'card-1', title: 'Task 1', description: '', priority: 'medium', createdAt: '2026-01-01' }],
},
{
id: 'col-2',
title: 'Done',
cards: [],
},
],
}
beforeEach(() => {
useBoardStore.setState({ boards: [mockBoard], currentBoardId: null })
})
it('should add a card to a column', () => {
const newCard: Card = { id: 'card-2', title: 'New Task', description: '', priority: 'low', createdAt: '2026-01-02' }
useBoardStore.getState().addCard('col-1', newCard)
const columns = useBoardStore.getState().boards[0].columns
expect(columns[0].cards).toHaveLength(2)
expect(columns[0].cards[1].title).toBe('New Task')
})
it('should move a card between columns', () => {
useBoardStore.getState().moveCard('card-1', 'col-2')
const columns = useBoardStore.getState().boards[0].columns
expect(columns[0].cards).toHaveLength(0) // 移出
expect(columns[1].cards).toHaveLength(1) // 移入
})
it('should delete a card', () => {
useBoardStore.getState().deleteCard('card-1')
const cards = useBoardStore.getState().boards[0].columns[0].cards
expect(cards).toHaveLength(0)
})
})组件测试
// components/__tests__/Card.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { Card } from '../Card'
describe('Card', () => {
const mockCard: Card = {
id: '1',
title: 'Test Card',
description: 'Description',
priority: 'high',
createdAt: '2026-01-01',
}
it('renders card content', () => {
render(<Card card={mockCard} onEdit={vi.fn()} />)
expect(screen.getByText('Test Card')).toBeInTheDocument()
expect(screen.getByText('Description')).toBeInTheDocument()
})
it('shows high priority indicator', () => {
render(<Card card={mockCard} onEdit={vi.fn()} />)
expect(screen.getByText(/high/i)).toBeInTheDocument()
})
it('calls onEdit when double-clicked', async () => {
const onEdit = vi.fn()
const user = userEvent.setup()
render(<Card card={mockCard} onEdit={onEdit} />)
await user.dblClick(screen.getByText('Test Card'))
expect(onEdit).toHaveBeenCalledWith(mockCard)
})
})E2E 测试
// cypress/e2e/board.cy.ts
describe('Kanban Board', () => {
beforeEach(() => {
cy.intercept('GET', '/api/boards', { fixture: 'boards.json' }).as('getBoards')
cy.visit('/')
cy.wait('@getBoards')
})
it('should display board list', () => {
cy.contains('My Board').should('be.visible')
cy.contains('Create Board').should('be.visible')
})
it('should create a new card', () => {
cy.intercept('POST', '/api/columns/**/cards', { statusCode: 201 }).as('createCard')
cy.contains('Add Card').first().click()
cy.get('[data-testid="card-title"]').type('New E2E Card')
cy.get('[data-testid="save-card"]').click()
cy.wait('@createCard')
cy.contains('New E2E Card').should('be.visible')
})
it('should drag and drop a card', () => {
cy.get('[data-testid="card"]').first().as('source')
cy.get('[data-testid="column"]').eq(1).as('target')
// 模拟拖拽
cy.get('@source').trigger('dragstart')
cy.get('@target').trigger('drop')
})
})Vercel 部署
npm install -g vercel
vercel
# 或者 GitHub 自动部署# vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "vite"
}部署 checklist
- [ ] Vite 构建通过
- [ ] 环境变量配置(API URL)
- [ ] 单元测试通过
- [ ] E2E 核心流程通过
- [ ] Bundle 大小分析
- [ ] 配置 CDN 缓存
- [ ] 配置错误监控(Sentry)小结
- Zustand store 纯函数测试(vitest)。
- React Testing Library 组件测试(用户行为驱动)。
- Cypress E2E 测试核心用户流程(CRUD + 拖拽)。
- Vercel 一键部署前端应用。
- 完整的测试体系保障上线质量,完成此项目 = 掌握 React 全栈能力。
上一节:状态管理与 API 集成
