Files
novalon-website/docs/plans/2026-03-10-full-module-test-coverage-plan.md
T
张翔 b207bfa7af feat: 增加测试覆盖率并优化代码质量
test: 添加单元测试和端到端测试
refactor: 重构登录页面和上传模块
ci: 更新测试覆盖率阈值至42%
build: 添加测试相关依赖
docs: 更新测试文档
style: 修复代码格式问题
2026-03-11 11:14:37 +08:00

948 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 全模块测试覆盖实施计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 补充所有未覆盖模块的测试,将测试覆盖率从42.57%提升到70%
**Architecture:** 采用TDD方法,优先补充核心业务模块测试,确保所有页面和API功能正常
**Tech Stack:** Jest + React Testing Library + TypeScript
---
## 📊 当前状态
**当前覆盖率:** 42.57%
**未覆盖模块(0%):**
- 页面详情页:cases/[id], news/[slug], products/[id], services/[id], solutions
- 管理后台:admin主页, content/[id], logs
- 管理后台APIconfig, content/[id], logs, upload, users/[id]
- 其他页面:privacy, terms, preview/effects
- 组件:admin, analytics, effects, examples, seo
---
## 🎯 目标
**覆盖率目标:** 70%
**任务总数:** 25个
**预计时间:** 2-3周
---
## Phase 1: 页面详情页测试(提升到55%)
### Task 1: 案例详情页测试
**Files:**
- Create: `src/app/(marketing)/cases/[id]/page.test.tsx`
- Test: `src/app/(marketing)/cases/[id]/page.tsx`
**Step 1: 查看案例详情页源码**
Run:
```bash
cat src/app/\(marketing\)/cases/\[id\]/page.tsx | head -100
```
Expected: 显示案例详情页结构和功能
**Step 2: 编写案例详情页测试**
Create: `src/app/(marketing)/cases/[id]/page.test.tsx`
```typescript
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import CaseDetailPage from './page';
describe('CaseDetailPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render case detail page', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render case title', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
});
it('should render case content', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const content = screen.getByText(/案例详情/i);
expect(content).toBeInTheDocument();
});
it('should render case images', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const images = screen.getAllByRole('img');
expect(images.length).toBeGreaterThan(0);
});
it('should render related cases', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const relatedCases = screen.getByText(/相关案例/i);
expect(relatedCases).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have back to cases link', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const backLink = screen.getByRole('link', { name: /返回/i });
expect(backLink).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<CaseDetailPage params={{ id: '1' }} />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/app/\(marketing\)/cases/\[id\]/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/app/\(marketing\)/cases/\[id\]/page.test.tsx
git commit -m "test: add case detail page tests"
```
---
### Task 2: 新闻详情页测试
**Files:**
- Create: `src/app/(marketing)/news/[slug]/page.test.tsx`
- Test: `src/app/(marketing)/news/[slug]/page.tsx`
**Step 1: 查看新闻详情页源码**
Run:
```bash
cat src/app/\(marketing\)/news/\[slug\]/page.tsx | head -100
```
Expected: 显示新闻详情页结构和功能
**Step 2: 编写新闻详情页测试**
Create: `src/app/(marketing)/news/[slug]/page.test.tsx`
```typescript
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import NewsDetailPage from './page';
describe('NewsDetailPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render news detail page', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render news title', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
});
it('should render news content', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const content = screen.getByText(/新闻详情/i);
expect(content).toBeInTheDocument();
});
it('should render news date', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const date = screen.getByText(/\d{4}-\d{2}-\d{2}/);
expect(date).toBeInTheDocument();
});
it('should render news category', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const category = screen.getByText(/分类/i);
expect(category).toBeInTheDocument();
});
it('should render related news', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const relatedNews = screen.getByText(/相关新闻/i);
expect(relatedNews).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have back to news link', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const backLink = screen.getByRole('link', { name: /返回/i });
expect(backLink).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<NewsDetailPage params={{ slug: 'test-news' }} />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/app/\(marketing\)/news/\[slug\]/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/app/\(marketing\)/news/\[slug\]/page.test.tsx
git commit -m "test: add news detail page tests"
```
---
### Task 3-5: 产品详情页、服务详情页、解决方案页测试
(类似Task 1-2,为产品详情页、服务详情页、解决方案页添加测试)
---
## Phase 2: 管理后台页面测试(提升到60%)
### Task 6: 管理后台主页测试
**Files:**
- Create: `src/app/admin/page.test.tsx`
- Test: `src/app/admin/page.tsx`
**Step 1: 查看管理后台主页源码**
Run:
```bash
cat src/app/admin/page.tsx | head -100
```
Expected: 显示管理后台主页结构和功能
**Step 2: 编写管理后台主页测试**
Create: `src/app/admin/page.test.tsx`
```typescript
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import AdminPage from './page';
describe('AdminPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render admin page', () => {
render(<AdminPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render dashboard', () => {
render(<AdminPage />);
const dashboard = screen.getByText(/仪表盘/i);
expect(dashboard).toBeInTheDocument();
});
it('should render statistics', () => {
render(<AdminPage />);
const stats = screen.getByText(/统计/i);
expect(stats).toBeInTheDocument();
});
it('should render quick actions', () => {
render(<AdminPage />);
const quickActions = screen.getByText(/快捷操作/i);
expect(quickActions).toBeInTheDocument();
});
});
describe('Navigation', () => {
it('should have navigation menu', () => {
render(<AdminPage />);
const nav = screen.getByRole('navigation');
expect(nav).toBeInTheDocument();
});
it('should have content management link', () => {
render(<AdminPage />);
const contentLink = screen.getByRole('link', { name: /内容管理/i });
expect(contentLink).toBeInTheDocument();
});
it('should have user management link', () => {
render(<AdminPage />);
const userLink = screen.getByRole('link', { name: /用户管理/i });
expect(userLink).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<AdminPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<AdminPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/app/admin/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/app/admin/page.test.tsx
git commit -m "test: add admin main page tests"
```
---
### Task 7-8: 内容编辑页、审计日志页测试
(类似Task 6,为内容编辑页、审计日志页添加测试)
---
## Phase 3: 管理后台API测试(提升到65%)
### Task 9: 配置管理API测试
**Files:**
- Create: `src/app/api/admin/config/route.test.ts`
- Test: `src/app/api/admin/config/route.ts`
**Step 1: 查看配置管理API源码**
Run:
```bash
cat src/app/api/admin/config/route.ts | head -100
```
Expected: 显示配置管理API结构和功能
**Step 2: 编写配置管理API测试**
Create: `src/app/api/admin/config/route.test.ts`
```typescript
import { GET, POST, PUT, DELETE } from './route';
import { NextRequest } from 'next/server';
describe('/api/admin/config', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('GET', () => {
it('should return config list', async () => {
const request = new NextRequest('http://localhost/api/admin/config');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(Array.isArray(data.data)).toBe(true);
});
it('should return config by key', async () => {
const request = new NextRequest('http://localhost/api/admin/config?key=site_name');
const response = await GET(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.data).toBeDefined();
});
});
describe('POST', () => {
it('should create new config', async () => {
const request = new NextRequest('http://localhost/api/admin/config', {
method: 'POST',
body: JSON.stringify({
key: 'test_key',
value: 'test_value',
category: 'test',
}),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(201);
expect(data.success).toBe(true);
expect(data.data.key).toBe('test_key');
});
it('should reject invalid config', async () => {
const request = new NextRequest('http://localhost/api/admin/config', {
method: 'POST',
body: JSON.stringify({}),
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
});
});
describe('PUT', () => {
it('should update config', async () => {
const request = new NextRequest('http://localhost/api/admin/config', {
method: 'PUT',
body: JSON.stringify({
key: 'test_key',
value: 'updated_value',
}),
});
const response = await PUT(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
});
});
describe('DELETE', () => {
it('should delete config', async () => {
const request = new NextRequest('http://localhost/api/admin/config?key=test_key', {
method: 'DELETE',
});
const response = await DELETE(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/app/api/admin/config/route.test.ts
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/app/api/admin/config/route.test.ts
git commit -m "test: add config management API tests"
```
---
### Task 10-13: 内容API、日志API、上传API、用户API测试
(类似Task 9,为其他管理后台API添加测试)
---
## Phase 4: 其他页面测试(提升到68%)
### Task 14: 隐私政策页测试
**Files:**
- Create: `src/app/privacy/page.test.tsx`
- Test: `src/app/privacy/page.tsx`
**Step 1: 查看隐私政策页源码**
Run:
```bash
cat src/app/privacy/page.tsx | head -50
```
Expected: 显示隐私政策页结构和功能
**Step 2: 编写隐私政策页测试**
Create: `src/app/privacy/page.test.tsx`
```typescript
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import PrivacyPage from './page';
describe('PrivacyPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render privacy page', () => {
render(<PrivacyPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render privacy title', () => {
render(<PrivacyPage />);
const title = screen.getByRole('heading', { level: 1 });
expect(title).toBeInTheDocument();
expect(title).toHaveTextContent(/隐私政策/i);
});
it('should render privacy content', () => {
render(<PrivacyPage />);
const content = screen.getByText(/隐私/i);
expect(content).toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<PrivacyPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should have proper heading hierarchy', () => {
render(<PrivacyPage />);
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/app/privacy/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/app/privacy/page.test.tsx
git commit -m "test: add privacy page tests"
```
---
### Task 15: 服务条款页测试
(类似Task 14,为服务条款页添加测试)
---
### Task 16: 特效预览页测试
**Files:**
- Create: `src/app/preview/effects/page.test.tsx`
- Test: `src/app/preview/effects/page.tsx`
**Step 1: 查看特效预览页源码**
Run:
```bash
cat src/app/preview/effects/page.tsx | head -100
```
Expected: 显示特效预览页结构和功能
**Step 2: 编写特效预览页测试**
Create: `src/app/preview/effects/page.test.tsx`
```typescript
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import EffectsPreviewPage from './page';
describe('EffectsPreviewPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render effects preview page', () => {
render(<EffectsPreviewPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
it('should render effects list', () => {
render(<EffectsPreviewPage />);
const effectsList = screen.getByRole('list');
expect(effectsList).toBeInTheDocument();
});
it('should render effect cards', () => {
render(<EffectsPreviewPage />);
const effectCards = screen.getAllByRole('listitem');
expect(effectCards.length).toBeGreaterThan(0);
});
});
describe('Accessibility', () => {
it('should have main landmark', () => {
render(<EffectsPreviewPage />);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/app/preview/effects/page.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/app/preview/effects/page.test.tsx
git commit -m "test: add effects preview page tests"
```
---
## Phase 5: 组件测试(提升到70%
### Task 17: 管理后台组件测试
**Files:**
- Create: `src/components/admin/RichTextEditor.test.tsx`
- Test: `src/components/admin/RichTextEditor.tsx`
**Step 1: 查看富文本编辑器组件源码**
Run:
```bash
cat src/components/admin/RichTextEditor.tsx | head -100
```
Expected: 显示富文本编辑器组件结构和功能
**Step 2: 编写富文本编辑器组件测试**
Create: `src/components/admin/RichTextEditor.test.tsx`
```typescript
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import RichTextEditor from './RichTextEditor';
describe('RichTextEditor', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render editor', () => {
render(<RichTextEditor value="" onChange={() => {}} />);
const editor = screen.getByRole('textbox');
expect(editor).toBeInTheDocument();
});
it('should render toolbar', () => {
render(<RichTextEditor value="" onChange={() => {}} />);
const toolbar = screen.getByRole('toolbar');
expect(toolbar).toBeInTheDocument();
});
it('should render formatting buttons', () => {
render(<RichTextEditor value="" onChange={() => {}} />);
const boldButton = screen.getByRole('button', { name: /粗体/i });
const italicButton = screen.getByRole('button', { name: /斜体/i });
expect(boldButton).toBeInTheDocument();
expect(italicButton).toBeInTheDocument();
});
});
describe('Functionality', () => {
it('should call onChange when content changes', () => {
const handleChange = jest.fn();
render(<RichTextEditor value="" onChange={handleChange} />);
const editor = screen.getByRole('textbox');
fireEvent.change(editor, { target: { value: 'test content' } });
expect(handleChange).toHaveBeenCalledWith('test content');
});
it('should display initial value', () => {
render(<RichTextEditor value="initial content" onChange={() => {}} />);
const editor = screen.getByRole('textbox');
expect(editor).toHaveValue('initial content');
});
});
describe('Accessibility', () => {
it('should have accessible label', () => {
render(<RichTextEditor value="" onChange={() => {}} aria-label="Content editor" />);
const editor = screen.getByLabelText('Content editor');
expect(editor).toBeInTheDocument();
});
});
});
```
**Step 3: 运行测试验证通过**
Run:
```bash
npm run test:unit -- src/components/admin/RichTextEditor.test.tsx
```
Expected: 所有测试通过
**Step 4: 提交代码**
```bash
git add src/components/admin/RichTextEditor.test.tsx
git commit -m "test: add rich text editor component tests"
```
---
### Task 18-21: 其他组件测试
(类似Task 17,为analytics、effects、examples、seo组件添加测试)
---
## Phase 6: 最终验证
### Task 22: 验证覆盖率达到70%
**Step 1: 运行测试覆盖率检查**
Run:
```bash
npm run test:unit -- --coverage --coverageReporters=text-summary
```
Expected: 覆盖率达到70%以上
**Step 2: 如果未达标,补充缺失测试**
根据覆盖率报告,补充缺失的测试用例。
---
### Task 23: 更新覆盖率门禁
**Files:**
- Modify: `jest.config.js`
- Modify: `.woodpecker/quality-gate.yml`
**Step 1: 更新Jest覆盖率门禁**
Modify: `jest.config.js`
```javascript
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
}
```
**Step 2: 更新CI/CD质量门禁**
Modify: `.woodpecker/quality-gate.yml`
```yaml
unit-tests:
image: node:18-alpine
commands:
- echo "=== Running unit tests with coverage ==="
- npm run test:unit -- --coverage --coverageReporters=json
- |
COVERAGE=$(cat coverage/coverage-summary.json | grep -o '"lines":{"pct":[0-9.]*' | grep -o '[0-9.]*$')
echo "Current coverage: $COVERAGE%"
if [ $(echo "$COVERAGE < 70" | bc -l) -eq 1 ]; then
echo "❌ Coverage $COVERAGE% is below threshold 70%"
exit 1
fi
echo "✅ Coverage $COVERAGE% meets threshold 70%"
```
**Step 3: 提交配置**
```bash
git add jest.config.js .woodpecker/quality-gate.yml
git commit -m "feat: update coverage threshold gate to 70%"
```
---
### Task 24: 创建测试规范文档
**Files:**
- Create: `docs/TESTING_GUIDELINES.md`
**Step 1: 创建测试规范文档**
(参考之前的测试规范文档内容)
**Step 2: 提交文档**
```bash
git add docs/TESTING_GUIDELINES.md
git commit -m "docs: add testing guidelines"
```
---
### Task 25: 最终验收
**Step 1: 运行完整测试套件**
Run:
```bash
npm run test:unit -- --coverage
```
Expected: 覆盖率达到70%以上,所有测试通过
**Step 2: 运行CI/CD流水线**
Run:
```bash
npm run lint && npm run typecheck && npm run test:unit
```
Expected: 所有检查通过
**Step 3: 生成最终报告**
Run:
```bash
npm run test:unit -- --coverage --coverageReporters=html
```
Expected: 生成HTML覆盖率报告
**Step 4: 提交最终完成标记**
```bash
git add .
git commit -m "feat: complete full module test coverage to 70%"
git tag v1.0.0-test-coverage-70-full
```
---
## 📊 预期成果
**Phase 1完成后:**
- 测试覆盖率:≥55%
- 页面详情页测试:100%覆盖
**Phase 2完成后:**
- 测试覆盖率:≥60%
- 管理后台页面测试:100%覆盖
**Phase 3完成后:**
- 测试覆盖率:≥65%
- 管理后台API测试:100%覆盖
**Phase 4完成后:**
- 测试覆盖率:≥68%
- 其他页面测试:100%覆盖
**Phase 5完成后:**
- 测试覆盖率:≥70%
- 组件测试:100%覆盖
---
## 🎯 成功标准
1. ✅ 测试覆盖率达到70%以上
2. ✅ 所有测试通过率100%
3. ✅ 所有未覆盖模块测试完成
4. ✅ CI/CD质量门禁更新到70%
5. ✅ 测试规范文档完整
---
**计划创建完成!**