chore: 清理过时的计划文档,保留最新优化计划
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,947 +0,0 @@
|
|||||||
# 全模块测试覆盖实施计划
|
|
||||||
|
|
||||||
> **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
|
|
||||||
- 管理后台API:config, 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. ✅ 测试规范文档完整
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**计划创建完成!**
|
|
||||||
@@ -1,579 +0,0 @@
|
|||||||
# 渐进式测试覆盖率提升计划
|
|
||||||
|
|
||||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** 建立可持续的测试覆盖率提升体系,从当前29.5%逐步提升到更高水平,同时保证开发效率和代码质量。
|
|
||||||
|
|
||||||
**Architecture:** 采用分层覆盖率策略,核心业务逻辑层高覆盖(70-80%),UI组件层中覆盖(60-70%),页面展示层适度覆盖(40-50%)。
|
|
||||||
|
|
||||||
**Tech Stack:** Jest, React Testing Library, Next.js, TypeScript, GitHub Actions
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 已完成任务
|
|
||||||
|
|
||||||
### Task 1: 调整Jest配置覆盖率阈值
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `jest.config.js:12-18`
|
|
||||||
|
|
||||||
**Step 1: 修改覆盖率阈值为合理水平**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
coverageThreshold: {
|
|
||||||
global: {
|
|
||||||
branches: 20,
|
|
||||||
functions: 25,
|
|
||||||
lines: 25,
|
|
||||||
statements: 25,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证配置**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --coverage --coverageReporters=text-summary`
|
|
||||||
|
|
||||||
Expected: PASS with coverage above 25%
|
|
||||||
|
|
||||||
**Step 3: 提交配置修改**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add jest.config.js
|
|
||||||
git commit -m "test: adjust coverage threshold to reasonable level (25%)"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第一阶段:稳定当前覆盖率(1-2周)
|
|
||||||
|
|
||||||
### Task 2: 补充核心业务逻辑测试
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/lib/auth/session.test.ts`
|
|
||||||
- Create: `src/lib/auth/token.test.ts`
|
|
||||||
- Create: `src/lib/validation.test.ts`
|
|
||||||
|
|
||||||
**Step 1: 为session管理编写测试**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
describe('session management', () => {
|
|
||||||
it('should create session with user data', () => {
|
|
||||||
const session = createSession({ userId: '123', role: 'admin' });
|
|
||||||
expect(session).toBeDefined();
|
|
||||||
expect(session.userId).toBe('123');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate session expiration', () => {
|
|
||||||
const expiredSession = createSession({ userId: '123', expiresIn: -1 });
|
|
||||||
expect(isSessionValid(expiredSession)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证失败**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="session.test.ts"`
|
|
||||||
|
|
||||||
Expected: FAIL with "createSession not defined"
|
|
||||||
|
|
||||||
**Step 3: 实现session管理功能**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export function createSession(userData: SessionData): Session {
|
|
||||||
return {
|
|
||||||
...userData,
|
|
||||||
createdAt: Date.now(),
|
|
||||||
expiresAt: Date.now() + (24 * 60 * 60 * 1000), // 24小时
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSessionValid(session: Session): boolean {
|
|
||||||
return Date.now() < session.expiresAt;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 运行测试验证通过**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="session.test.ts"`
|
|
||||||
|
|
||||||
Expected: PASS
|
|
||||||
|
|
||||||
**Step 5: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/lib/auth/session.test.ts src/lib/auth/session.ts
|
|
||||||
git commit -m "test: add session management tests"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: 补充数据验证测试
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/lib/validation.test.ts`
|
|
||||||
- Modify: `src/lib/validation.ts`
|
|
||||||
|
|
||||||
**Step 1: 编写数据验证测试**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
describe('validation', () => {
|
|
||||||
describe('email validation', () => {
|
|
||||||
it('should accept valid email', () => {
|
|
||||||
expect(validateEmail('test@example.com')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject invalid email', () => {
|
|
||||||
expect(validateEmail('invalid')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('phone validation', () => {
|
|
||||||
it('should accept valid phone', () => {
|
|
||||||
expect(validatePhone('13800138000')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reject invalid phone', () => {
|
|
||||||
expect(validatePhone('123')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证失败**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="validation.test.ts"`
|
|
||||||
|
|
||||||
Expected: FAIL with validation functions not defined
|
|
||||||
|
|
||||||
**Step 3: 实现验证函数**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export function validateEmail(email: string): boolean {
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return emailRegex.test(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validatePhone(phone: string): boolean {
|
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
|
||||||
return phoneRegex.test(phone);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 运行测试验证通过**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="validation.test.ts"`
|
|
||||||
|
|
||||||
Expected: PASS
|
|
||||||
|
|
||||||
**Step 5: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/lib/validation.test.ts src/lib/validation.ts
|
|
||||||
git commit -m "test: add data validation tests"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: 优化测试执行性能
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `jest.config.js`
|
|
||||||
- Create: `.github/workflows/test-optimized.yml`
|
|
||||||
|
|
||||||
**Step 1: 配置并行测试执行**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// jest.config.js
|
|
||||||
module.exports = {
|
|
||||||
// ... existing config
|
|
||||||
maxWorkers: '50%',
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 创建优化后的CI工作流**
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# .github/workflows/test-optimized.yml
|
|
||||||
name: Optimized Test
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
shard: [1, 2, 3, 4]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '20'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Run tests (shard ${{ matrix.shard }})
|
|
||||||
run: npm run test:unit -- --shard=${{ matrix.shard }}/4
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
if: matrix.shard == 4
|
|
||||||
uses: codecov/codecov-action@v4
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 提交配置**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add jest.config.js .github/workflows/test-optimized.yml
|
|
||||||
git commit -m "ci: optimize test execution with parallelization"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第二阶段:提升覆盖率到35%(1-2个月)
|
|
||||||
|
|
||||||
### Task 5: 补充API路由测试
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/app/api/contact/route.test.ts`
|
|
||||||
- Create: `src/app/api/auth/route.test.ts`
|
|
||||||
|
|
||||||
**Step 1: 编写API路由测试**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
describe('/api/contact', () => {
|
|
||||||
it('should handle POST request', async () => {
|
|
||||||
const response = await POST(new Request('http://localhost/api/contact', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ name: 'Test', email: 'test@example.com' }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate required fields', async () => {
|
|
||||||
const response = await POST(new Request('http://localhost/api/contact', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证失败**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="api/contact/route.test.ts"`
|
|
||||||
|
|
||||||
Expected: FAIL with API route not tested
|
|
||||||
|
|
||||||
**Step 3: 实现API路由验证逻辑**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 确保API路由有适当的验证和错误处理
|
|
||||||
export async function POST(request: Request) {
|
|
||||||
try {
|
|
||||||
const body = await request.json();
|
|
||||||
|
|
||||||
if (!body.name || !body.email) {
|
|
||||||
return Response.json({ error: 'Missing required fields' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理逻辑...
|
|
||||||
return Response.json({ success: true }, { status: 200 });
|
|
||||||
} catch (error) {
|
|
||||||
return Response.json({ error: 'Internal server error' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 运行测试验证通过**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="api/contact/route.test.ts"`
|
|
||||||
|
|
||||||
Expected: PASS
|
|
||||||
|
|
||||||
**Step 5: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/app/api/contact/route.test.ts src/app/api/contact/route.ts
|
|
||||||
git commit -m "test: add API route tests"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 6: 补充数据库操作测试
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/db/queries.test.ts`
|
|
||||||
- Create: `src/db/mutations.test.ts`
|
|
||||||
|
|
||||||
**Step 1: 编写数据库查询测试**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
describe('database queries', () => {
|
|
||||||
it('should query user by id', async () => {
|
|
||||||
const user = await getUserById('123');
|
|
||||||
expect(user).toBeDefined();
|
|
||||||
expect(user.id).toBe('123');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null for non-existent user', async () => {
|
|
||||||
const user = await getUserById('non-existent');
|
|
||||||
expect(user).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证失败**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="db/queries.test.ts"`
|
|
||||||
|
|
||||||
Expected: FAIL with database functions not tested
|
|
||||||
|
|
||||||
**Step 3: 实现数据库查询函数**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export async function getUserById(id: string): Promise<User | null> {
|
|
||||||
const result = await db.select().from(users).where(eq(users.id, id)).limit(1);
|
|
||||||
return result[0] || null;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 运行测试验证通过**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="db/queries.test.ts"`
|
|
||||||
|
|
||||||
Expected: PASS
|
|
||||||
|
|
||||||
**Step 5: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/db/queries.test.ts src/db/queries.ts
|
|
||||||
git commit -m "test: add database query tests"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 7: 提升分支覆盖率
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/lib/auth/permissions.test.ts`
|
|
||||||
- Modify: `src/lib/upload.test.ts`
|
|
||||||
|
|
||||||
**Step 1: 添加边界条件测试**
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
describe('permissions edge cases', () => {
|
|
||||||
it('should handle null role', () => {
|
|
||||||
expect(hasPermission(null, 'content', 'read')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle undefined resource', () => {
|
|
||||||
expect(hasPermission('admin', undefined, 'read')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle empty action string', () => {
|
|
||||||
expect(hasPermission('admin', 'content', '')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证通过**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --testPathPattern="permissions.test.ts"`
|
|
||||||
|
|
||||||
Expected: PASS with improved branch coverage
|
|
||||||
|
|
||||||
**Step 3: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add src/lib/auth/permissions.test.ts
|
|
||||||
git commit -m "test: improve branch coverage with edge cases"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 第三阶段:建立测试质量体系(2-3个月)
|
|
||||||
|
|
||||||
### Task 8: 创建测试指南文档
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/testing-guide.md`
|
|
||||||
|
|
||||||
**Step 1: 编写测试指南**
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# 测试指南
|
|
||||||
|
|
||||||
## 测试策略
|
|
||||||
|
|
||||||
本项目采用分层覆盖率策略:
|
|
||||||
- 核心业务逻辑层:70-80%覆盖率
|
|
||||||
- UI组件层:60-70%覆盖率
|
|
||||||
- 页面展示层:40-50%覆盖率
|
|
||||||
|
|
||||||
## 测试编写规范
|
|
||||||
|
|
||||||
### 单元测试
|
|
||||||
- 使用Jest和React Testing Library
|
|
||||||
- 遵循AAA模式(Arrange-Act-Assert)
|
|
||||||
- 每个测试只验证一个行为
|
|
||||||
|
|
||||||
### 集成测试
|
|
||||||
- 测试组件间的交互
|
|
||||||
- 使用真实的数据流
|
|
||||||
- 避免过度mock
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
**Q: 如何处理异步测试?**
|
|
||||||
A: 使用async/await和waitFor函数。
|
|
||||||
|
|
||||||
**Q: 如何测试错误处理?**
|
|
||||||
A: 使用toThrow和expect.assertions验证错误路径。
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 提交文档**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add docs/testing-guide.md
|
|
||||||
git commit -m "docs: add testing guide"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 9: 建立覆盖率趋势监控
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `scripts/coverage-trend.js`
|
|
||||||
- Create: `.github/workflows/coverage-report.yml`
|
|
||||||
|
|
||||||
**Step 1: 创建覆盖率趋势脚本**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// scripts/coverage-trend.js
|
|
||||||
const fs = require('fs');
|
|
||||||
const coverage = JSON.parse(fs.readFileSync('coverage/coverage-final.json', 'utf8'));
|
|
||||||
|
|
||||||
const metrics = {
|
|
||||||
statements: coverage.total.statements.pct,
|
|
||||||
branches: coverage.total.branches.pct,
|
|
||||||
functions: coverage.total.functions.pct,
|
|
||||||
lines: coverage.total.lines.pct,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(JSON.stringify(metrics, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 创建覆盖率报告工作流**
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# .github/workflows/coverage-report.yml
|
|
||||||
name: Coverage Report
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
report:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Generate coverage
|
|
||||||
run: |
|
|
||||||
npm run test:unit -- --coverage
|
|
||||||
node scripts/coverage-trend.js > coverage-metrics.json
|
|
||||||
|
|
||||||
- name: Upload metrics
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: coverage-metrics
|
|
||||||
path: coverage-metrics.json
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add scripts/coverage-trend.js .github/workflows/coverage-report.yml
|
|
||||||
git commit -m "ci: add coverage trend monitoring"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 10: 更新覆盖率目标
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `jest.config.js`
|
|
||||||
|
|
||||||
**Step 1: 提升覆盖率目标到35%**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
coverageThreshold: {
|
|
||||||
global: {
|
|
||||||
branches: 25,
|
|
||||||
functions: 35,
|
|
||||||
lines: 35,
|
|
||||||
statements: 35,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 运行测试验证**
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --coverage --coverageReporters=text-summary`
|
|
||||||
|
|
||||||
Expected: PASS with coverage above 35%
|
|
||||||
|
|
||||||
**Step 3: 提交**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add jest.config.js
|
|
||||||
git commit -m "test: increase coverage threshold to 35%"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验证步骤
|
|
||||||
|
|
||||||
### 验证1: 确认所有测试通过
|
|
||||||
|
|
||||||
Run: `npm run test:unit`
|
|
||||||
|
|
||||||
Expected: All tests pass
|
|
||||||
|
|
||||||
### 验证2: 确认覆盖率达标
|
|
||||||
|
|
||||||
Run: `npm run test:unit -- --coverage --coverageReporters=text-summary`
|
|
||||||
|
|
||||||
Expected: Coverage above 35%
|
|
||||||
|
|
||||||
### 验证3: 确认CI构建通过
|
|
||||||
|
|
||||||
Check: GitHub Actions workflow status
|
|
||||||
|
|
||||||
Expected: All workflows pass
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 回滚计划
|
|
||||||
|
|
||||||
如果出现问题,可以回滚到之前的配置:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --all
|
|
||||||
git checkout <commit-hash>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 相关资源
|
|
||||||
|
|
||||||
- [Jest文档](https://jestjs.io/docs/getting-started)
|
|
||||||
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
|
|
||||||
- [测试最佳实践](https://github.com/goldbergyoni/javascript-testing-best-practices)
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,199 +0,0 @@
|
|||||||
# 后台管理功能E2E测试覆盖率报告
|
|
||||||
|
|
||||||
## 测试执行时间
|
|
||||||
- 执行日期: 2026-03-12
|
|
||||||
- 总测试数量: 38
|
|
||||||
- 通过数量: 待执行
|
|
||||||
- 失败数量: 待执行
|
|
||||||
- 跳过数量: 待执行
|
|
||||||
- 通过率: 待执行
|
|
||||||
|
|
||||||
## 功能覆盖情况
|
|
||||||
|
|
||||||
### 产品服务管理
|
|
||||||
- 创建产品: ✅
|
|
||||||
- 编辑产品: ✅
|
|
||||||
- 删除产品: ✅
|
|
||||||
- 筛选产品类型: ✅
|
|
||||||
- 按状态筛选产品: ✅
|
|
||||||
- 搜索产品: ✅
|
|
||||||
- 覆盖率: 100%
|
|
||||||
|
|
||||||
### 成功案例管理
|
|
||||||
- 创建案例: ✅
|
|
||||||
- 编辑案例: ✅
|
|
||||||
- 删除案例: ✅
|
|
||||||
- 设置案例封面图: ✅
|
|
||||||
- 筛选案例类型: ✅
|
|
||||||
- 覆盖率: 100%
|
|
||||||
|
|
||||||
### 新闻动态管理
|
|
||||||
- 创建新闻: ✅
|
|
||||||
- 编辑新闻: ✅
|
|
||||||
- 删除新闻: ✅
|
|
||||||
- 发布新闻: ✅
|
|
||||||
- 设置草稿状态: ✅
|
|
||||||
- 筛选新闻类型: ✅
|
|
||||||
- 按发布状态筛选新闻: ✅
|
|
||||||
- 覆盖率: 100%
|
|
||||||
|
|
||||||
### 服务管理
|
|
||||||
- 创建服务: ✅
|
|
||||||
- 编辑服务: ✅
|
|
||||||
- 删除服务: ✅
|
|
||||||
- 筛选服务类型: ✅
|
|
||||||
- 覆盖率: 100%
|
|
||||||
|
|
||||||
### 富文本编辑器
|
|
||||||
- 输入文本内容: ✅
|
|
||||||
- 使用格式化工具: ✅
|
|
||||||
- 添加链接: ✅
|
|
||||||
- 添加列表: ✅
|
|
||||||
- 覆盖率: 80%
|
|
||||||
|
|
||||||
### 权限控制
|
|
||||||
- 管理员权限: ✅
|
|
||||||
- 编辑者权限: ✅
|
|
||||||
- 查看者权限: ✅
|
|
||||||
- 未登录重定向: ✅
|
|
||||||
- 覆盖率: 100%
|
|
||||||
|
|
||||||
## 测试文件清单
|
|
||||||
|
|
||||||
### 已创建的测试文件
|
|
||||||
1. `e2e/src/data/admin-test-data.ts` - 测试数据管理
|
|
||||||
2. `e2e/src/tests/admin/product-management.spec.ts` - 产品服务管理测试(6个测试用例)
|
|
||||||
3. `e2e/src/tests/admin/case-management.spec.ts` - 成功案例管理测试(5个测试用例)
|
|
||||||
4. `e2e/src/tests/admin/news-management.spec.ts` - 新闻动态管理测试(7个测试用例)
|
|
||||||
5. `e2e/src/tests/admin/service-management.spec.ts` - 服务管理测试(4个测试用例)
|
|
||||||
6. `e2e/src/tests/admin/rich-text-editor.spec.ts` - 富文本编辑器测试(4个测试用例)
|
|
||||||
7. `e2e/src/tests/admin/permissions.spec.ts` - 权限控制测试(4个测试用例)
|
|
||||||
|
|
||||||
### 已修复的测试文件
|
|
||||||
1. `e2e/src/tests/smoke/admin.smoke.spec.ts` - 修复登录超时问题
|
|
||||||
2. `e2e/src/tests/regression/admin.regression.spec.ts` - 修复测试跳过问题
|
|
||||||
|
|
||||||
## 整体覆盖率
|
|
||||||
- 后台管理功能E2E测试覆盖率: 95%
|
|
||||||
- 测试稳定性: 92%
|
|
||||||
- 测试执行时间: 待执行
|
|
||||||
|
|
||||||
## 测试架构改进
|
|
||||||
|
|
||||||
### 1. 测试数据管理集中化
|
|
||||||
- 创建了 `admin-test-data.ts` 文件
|
|
||||||
- 统一管理测试用户和内容数据
|
|
||||||
- 提供动态数据生成函数 `generateTestContent()`
|
|
||||||
|
|
||||||
### 2. 登录稳定性提升
|
|
||||||
- 修复了管理员邮箱配置(从 `contact@novalon.cn` 改为 `admin@novalon.cn`)
|
|
||||||
- 使用 `expect().toPass()` 方法提供重试机制
|
|
||||||
- 超时时间设置为 15 秒
|
|
||||||
- 为测试跳过添加明确的说明信息
|
|
||||||
|
|
||||||
### 3. Page Object Model 模式
|
|
||||||
- 所有测试使用统一的页面对象
|
|
||||||
- `AdminLoginPage` - 登录页面
|
|
||||||
- `AdminContentPage` - 内容管理页面
|
|
||||||
- `AdminDashboardPage` - 仪表盘页面
|
|
||||||
- `AdminUsersPage` - 用户管理页面
|
|
||||||
- `AdminLogsPage` - 审计日志页面
|
|
||||||
|
|
||||||
### 4. 测试分层策略
|
|
||||||
- **冒烟测试**:验证核心功能可用性
|
|
||||||
- **回归测试**:验证功能稳定性
|
|
||||||
- **功能测试**:验证具体业务功能
|
|
||||||
- **权限测试**:验证访问控制
|
|
||||||
|
|
||||||
## 改进建议
|
|
||||||
|
|
||||||
### 1. 继续补充富文本编辑器的高级功能测试
|
|
||||||
- 添加图片上传测试
|
|
||||||
- 添加表格插入测试
|
|
||||||
- 添加代码块测试
|
|
||||||
- 添加引用测试
|
|
||||||
|
|
||||||
### 2. 添加图片上传的完整测试覆盖
|
|
||||||
- 封面图上传验证
|
|
||||||
- 图片格式验证
|
|
||||||
- 图片大小限制测试
|
|
||||||
- 图片预览功能测试
|
|
||||||
|
|
||||||
### 3. 优化测试执行时间
|
|
||||||
- 使用并行执行策略
|
|
||||||
- 优化等待时间
|
|
||||||
- 减少不必要的页面加载等待
|
|
||||||
|
|
||||||
### 4. 增加更多边界条件测试
|
|
||||||
- 空输入验证
|
|
||||||
- 超长输入验证
|
|
||||||
- 特殊字符验证
|
|
||||||
- 并发操作测试
|
|
||||||
|
|
||||||
### 5. 添加性能测试
|
|
||||||
- 页面加载时间测试
|
|
||||||
- 大数据量列表测试
|
|
||||||
- 搜索性能测试
|
|
||||||
|
|
||||||
### 6. 增加可访问性测试
|
|
||||||
- 键盘导航测试
|
|
||||||
- 屏幕阅读器兼容性测试
|
|
||||||
- 颜色对比度测试
|
|
||||||
- 焦点管理测试
|
|
||||||
|
|
||||||
## 执行说明
|
|
||||||
|
|
||||||
### 运行测试套件
|
|
||||||
```bash
|
|
||||||
# 开发环境(需要先启动开发服务器)
|
|
||||||
cd e2e && npm test
|
|
||||||
|
|
||||||
# 使用特定项目运行
|
|
||||||
cd e2e && npm test --project=chromium
|
|
||||||
|
|
||||||
# 运行特定测试文件
|
|
||||||
cd e2e && npm test -- admin/product-management.spec.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
### 查看测试报告
|
|
||||||
```bash
|
|
||||||
# 打开HTML报告
|
|
||||||
npx playwright show-report
|
|
||||||
|
|
||||||
# 查看Allure报告
|
|
||||||
npx allure generate allure-results --clean -o allure-report
|
|
||||||
npx allure open allure-report
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
本实施计划包含11个任务,已全部完成:
|
|
||||||
|
|
||||||
1. ✅ **前置准备**:创建测试数据管理文件
|
|
||||||
2. ✅ **阶段1**:稳定化现有测试(2个任务)
|
|
||||||
3. ✅ **阶段2**:补充产品服务管理测试(1个任务)
|
|
||||||
4. ✅ **阶段3**:补充成功案例管理测试(1个任务)
|
|
||||||
5. ✅ **阶段4**:补充新闻动态管理测试(1个任务)
|
|
||||||
6. ✅ **阶段5**:补充服务管理测试(1个任务)
|
|
||||||
7. ✅ **阶段6**:补充富文本编辑器测试(1个任务)
|
|
||||||
8. ✅ **阶段7**:补充权限控制测试(1个任务)
|
|
||||||
9. ✅ **阶段8**:测试覆盖率验证和优化(2个任务)
|
|
||||||
|
|
||||||
### 关键成果
|
|
||||||
- 创建了 7 个新的测试文件
|
|
||||||
- 新增了 30 个测试用例
|
|
||||||
- 修复了 2 个现有测试文件
|
|
||||||
- 提升了测试稳定性和可维护性
|
|
||||||
- 建立了完整的测试数据管理体系
|
|
||||||
|
|
||||||
### 预期效果
|
|
||||||
- 测试覆盖率:95%以上
|
|
||||||
- 测试稳定性:92%以上
|
|
||||||
- 测试可维护性:显著提升
|
|
||||||
- 测试执行效率:通过并行执行提升
|
|
||||||
|
|
||||||
### 后续工作
|
|
||||||
1. 启动开发服务器并运行完整测试套件
|
|
||||||
2. 根据测试结果优化失败的测试用例
|
|
||||||
3. 持续监控测试覆盖率
|
|
||||||
4. 定期更新测试用例以适应功能变化
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,906 +0,0 @@
|
|||||||
# 代码质量工具集成实施计划
|
|
||||||
|
|
||||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
||||||
|
|
||||||
**Goal:** 集成代码质量工具,建立自动化质量门禁,确保代码提交前自动检查
|
|
||||||
|
|
||||||
**Architecture:** 采用Husky管理Git hooks,lint-staged对暂存文件进行检查,commitlint规范提交信息,Jest生成覆盖率报告
|
|
||||||
|
|
||||||
**Tech Stack:** Husky 8+, lint-staged 15+, commitlint 19+, Jest 29+, @commitlint/config-conventional
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 前置条件
|
|
||||||
|
|
||||||
- Node.js 18+ 已安装
|
|
||||||
- Git 已配置
|
|
||||||
- 项目已克隆到本地
|
|
||||||
- package.json 已存在
|
|
||||||
|
|
||||||
## Task 1: 安装依赖包
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `package.json`
|
|
||||||
|
|
||||||
**Step 1: 安装Husky、lint-staged和commitlint**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install --save-dev husky lint-staged @commitlint/cli @commitlint/config-conventional
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 验证安装**
|
|
||||||
|
|
||||||
Run: `npm list husky lint-staged @commitlint/cli`
|
|
||||||
|
|
||||||
Expected: 显示已安装的版本号
|
|
||||||
|
|
||||||
**Step 3: 提交安装**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add package.json package-lock.json
|
|
||||||
git commit -m "chore: install husky, lint-staged and commitlint"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: 配置Husky
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `.husky/pre-commit`
|
|
||||||
- Create: `.husky/commit-msg`
|
|
||||||
- Modify: `package.json`
|
|
||||||
|
|
||||||
**Step 1: 初始化Husky**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx husky install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 创建pre-commit钩子**
|
|
||||||
|
|
||||||
创建文件 `.husky/pre-commit`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx lint-staged
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 创建commit-msg钩子**
|
|
||||||
|
|
||||||
创建文件 `.husky/commit-msg`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npx --no -- commitlint --edit $1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 添加prepare脚本到package.json**
|
|
||||||
|
|
||||||
在package.json的scripts中添加:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"prepare": "husky install"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 5: 运行prepare脚本**
|
|
||||||
|
|
||||||
Run: `npm run prepare`
|
|
||||||
|
|
||||||
Expected: 创建.husky目录并设置Git hooks
|
|
||||||
|
|
||||||
**Step 6: 提交配置**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add .husky/ package.json
|
|
||||||
git commit -m "chore: configure husky git hooks"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: 配置lint-staged
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `.lintstagedrc.json`
|
|
||||||
- Modify: `package.json`
|
|
||||||
|
|
||||||
**Step 1: 创建lint-staged配置文件**
|
|
||||||
|
|
||||||
创建文件 `.lintstagedrc.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"*.{js,jsx,ts,tsx}": [
|
|
||||||
"eslint --fix",
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{json,md}": [
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{css,scss}": [
|
|
||||||
"stylelint --fix",
|
|
||||||
"prettier --write"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 添加lint-staged脚本到package.json**
|
|
||||||
|
|
||||||
在package.json的scripts中添加:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"lint-staged": "lint-staged"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 测试lint-staged**
|
|
||||||
|
|
||||||
Run: `npm run lint-staged`
|
|
||||||
|
|
||||||
Expected: 对所有暂存文件运行lint和prettier
|
|
||||||
|
|
||||||
**Step 4: 提交配置**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add .lintstagedrc.json package.json
|
|
||||||
git commit -m "chore: configure lint-staged"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: 配置commitlint
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `commitlint.config.js`
|
|
||||||
- Create: `.commitlintrc.json`
|
|
||||||
|
|
||||||
**Step 1: 创建commitlint配置文件**
|
|
||||||
|
|
||||||
创建文件 `commitlint.config.js`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
module.exports = {
|
|
||||||
extends: ['@commitlint/config-conventional'],
|
|
||||||
rules: {
|
|
||||||
'type-enum': [
|
|
||||||
2,
|
|
||||||
'always',
|
|
||||||
[
|
|
||||||
'feat',
|
|
||||||
'fix',
|
|
||||||
'docs',
|
|
||||||
'style',
|
|
||||||
'refactor',
|
|
||||||
'perf',
|
|
||||||
'test',
|
|
||||||
'chore',
|
|
||||||
'revert',
|
|
||||||
'build',
|
|
||||||
'ci'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'subject-case': [0]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 创建备用配置文件**
|
|
||||||
|
|
||||||
创建文件 `.commitlintrc.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"extends": ["@commitlint/config-conventional"],
|
|
||||||
"rules": {
|
|
||||||
"type-enum": [
|
|
||||||
2,
|
|
||||||
"always",
|
|
||||||
[
|
|
||||||
"feat",
|
|
||||||
"fix",
|
|
||||||
"docs",
|
|
||||||
"style",
|
|
||||||
"refactor",
|
|
||||||
"perf",
|
|
||||||
"test",
|
|
||||||
"chore",
|
|
||||||
"revert",
|
|
||||||
"build",
|
|
||||||
"ci"
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"subject-case": [0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 测试commitlint**
|
|
||||||
|
|
||||||
Run: `echo "feat: add new feature" | npx commitlint`
|
|
||||||
|
|
||||||
Expected: 通过验证
|
|
||||||
|
|
||||||
Run: `echo "invalid commit message" | npx commitlint`
|
|
||||||
|
|
||||||
Expected: 失败并显示错误信息
|
|
||||||
|
|
||||||
**Step 4: 提交配置**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add commitlint.config.js .commitlintrc.json
|
|
||||||
git commit -m "chore: configure commitlint"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: 配置代码覆盖率检查
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `jest.config.js` 或 `jest.config.ts`
|
|
||||||
- Modify: `package.json`
|
|
||||||
|
|
||||||
**Step 1: 检查现有Jest配置**
|
|
||||||
|
|
||||||
Run: `cat jest.config.js` 或 `cat jest.config.ts`
|
|
||||||
|
|
||||||
Expected: 查看现有配置
|
|
||||||
|
|
||||||
**Step 2: 更新Jest配置添加覆盖率**
|
|
||||||
|
|
||||||
如果使用jest.config.js:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
module.exports = {
|
|
||||||
// ... 现有配置
|
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'src/**/*.{js,jsx,ts,tsx}',
|
|
||||||
'!src/**/*.d.ts',
|
|
||||||
'!src/**/*.stories.{js,jsx,ts,tsx}',
|
|
||||||
'!src/**/__tests__/**',
|
|
||||||
'!src/**/*.test.{js,jsx,ts,tsx}',
|
|
||||||
'!src/**/*.spec.{js,jsx,ts,tsx}'
|
|
||||||
],
|
|
||||||
coverageThreshold: {
|
|
||||||
global: {
|
|
||||||
branches: 70,
|
|
||||||
functions: 70,
|
|
||||||
lines: 70,
|
|
||||||
statements: 70
|
|
||||||
}
|
|
||||||
},
|
|
||||||
coverageReporters: ['json', 'lcov', 'text', 'html']
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
如果使用jest.config.ts:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import type { Config } from 'jest';
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
// ... 现有配置
|
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'src/**/*.{js,jsx,ts,tsx}',
|
|
||||||
'!src/**/*.d.ts',
|
|
||||||
'!src/**/*.stories.{js,jsx,ts,tsx}',
|
|
||||||
'!src/**/__tests__/**',
|
|
||||||
'!src/**/*.test.{js,jsx,ts,tsx}',
|
|
||||||
'!src/**/*.spec.{js,jsx,ts,tsx}'
|
|
||||||
],
|
|
||||||
coverageThreshold: {
|
|
||||||
global: {
|
|
||||||
branches: 70,
|
|
||||||
functions: 70,
|
|
||||||
lines: 70,
|
|
||||||
statements: 70
|
|
||||||
}
|
|
||||||
},
|
|
||||||
coverageReporters: ['json', 'lcov', 'text', 'html']
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 添加覆盖率脚本到package.json**
|
|
||||||
|
|
||||||
在package.json的scripts中添加:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"test:coverage": "jest --coverage",
|
|
||||||
"test:coverage:watch": "jest --coverage --watch",
|
|
||||||
"coverage:report": "open coverage/lcov-report/index.html"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 运行覆盖率测试**
|
|
||||||
|
|
||||||
Run: `npm run test:coverage`
|
|
||||||
|
|
||||||
Expected: 生成覆盖率报告
|
|
||||||
|
|
||||||
**Step 5: 提交配置**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add jest.config.js package.json
|
|
||||||
git commit -m "chore: configure jest coverage"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 6: 集成覆盖率检查到pre-commit
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `.lintstagedrc.json`
|
|
||||||
- Create: `scripts/check-coverage.sh`
|
|
||||||
|
|
||||||
**Step 1: 创建覆盖率检查脚本**
|
|
||||||
|
|
||||||
创建文件 `scripts/check-coverage.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 运行测试并生成覆盖率
|
|
||||||
npm run test:coverage -- --passWithNoTests
|
|
||||||
|
|
||||||
# 检查覆盖率是否达到阈值
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "❌ 测试失败,请修复测试后再提交"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ 测试通过"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 添加执行权限**
|
|
||||||
|
|
||||||
Run: `chmod +x scripts/check-coverage.sh`
|
|
||||||
|
|
||||||
**Step 3: 更新lint-staged配置**
|
|
||||||
|
|
||||||
修改 `.lintstagedrc.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"*.{js,jsx,ts,tsx}": [
|
|
||||||
"eslint --fix",
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{json,md}": [
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"*.{css,scss}": [
|
|
||||||
"stylelint --fix",
|
|
||||||
"prettier --write"
|
|
||||||
],
|
|
||||||
"package.json": [
|
|
||||||
"bash scripts/check-coverage.sh"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 4: 测试覆盖率检查**
|
|
||||||
|
|
||||||
Run: `bash scripts/check-coverage.sh`
|
|
||||||
|
|
||||||
Expected: 运行测试并检查覆盖率
|
|
||||||
|
|
||||||
**Step 5: 提交配置**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add .lintstagedrc.json scripts/check-coverage.sh
|
|
||||||
git commit -m "chore: integrate coverage check to pre-commit"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 7: 创建质量门禁文档
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/development/quality-gates.md`
|
|
||||||
|
|
||||||
**Step 1: 创建质量门禁文档**
|
|
||||||
|
|
||||||
创建文件 `docs/development/quality-gates.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# 代码质量门禁
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
项目配置了自动化质量门禁,确保代码提交前通过所有质量检查。
|
|
||||||
|
|
||||||
## 质量检查
|
|
||||||
|
|
||||||
### 1. 代码风格检查
|
|
||||||
|
|
||||||
**工具**: ESLint + Prettier
|
|
||||||
|
|
||||||
**检查时机**: pre-commit hook
|
|
||||||
|
|
||||||
**检查内容**:
|
|
||||||
- 代码语法错误
|
|
||||||
- 代码风格规范
|
|
||||||
- 代码格式化
|
|
||||||
|
|
||||||
**通过标准**: 无错误,无警告
|
|
||||||
|
|
||||||
### 2. 提交信息规范
|
|
||||||
|
|
||||||
**工具**: commitlint
|
|
||||||
|
|
||||||
**检查时机**: commit-msg hook
|
|
||||||
|
|
||||||
**检查内容**:
|
|
||||||
- 提交信息格式
|
|
||||||
- 提交类型合法性
|
|
||||||
|
|
||||||
**通过标准**: 符合Conventional Commits规范
|
|
||||||
|
|
||||||
**提交类型**:
|
|
||||||
- `feat`: 新功能
|
|
||||||
- `fix`: 修复bug
|
|
||||||
- `docs`: 文档更新
|
|
||||||
- `style`: 代码格式调整
|
|
||||||
- `refactor`: 重构
|
|
||||||
- `perf`: 性能优化
|
|
||||||
- `test`: 测试相关
|
|
||||||
- `chore`: 构建/工具相关
|
|
||||||
- `revert`: 回滚提交
|
|
||||||
- `build`: 构建相关
|
|
||||||
- `ci`: CI/CD相关
|
|
||||||
|
|
||||||
**提交信息格式**:
|
|
||||||
```
|
|
||||||
<type>(<scope>): <subject>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
```
|
|
||||||
|
|
||||||
**示例**:
|
|
||||||
```
|
|
||||||
feat(auth): add JWT authentication
|
|
||||||
|
|
||||||
Implement JWT-based authentication with:
|
|
||||||
- Token generation
|
|
||||||
- Token validation
|
|
||||||
- Refresh token mechanism
|
|
||||||
|
|
||||||
Closes #123
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 代码覆盖率检查
|
|
||||||
|
|
||||||
**工具**: Jest
|
|
||||||
|
|
||||||
**检查时机**: pre-commit hook(修改package.json时)
|
|
||||||
|
|
||||||
**检查内容**:
|
|
||||||
- 单元测试覆盖率
|
|
||||||
- 分支覆盖率
|
|
||||||
- 函数覆盖率
|
|
||||||
- 行覆盖率
|
|
||||||
- 语句覆盖率
|
|
||||||
|
|
||||||
**通过标准**:
|
|
||||||
- 分支覆盖率: ≥ 70%
|
|
||||||
- 函数覆盖率: ≥ 70%
|
|
||||||
- 行覆盖率: ≥ 70%
|
|
||||||
- 语句覆盖率: ≥ 70%
|
|
||||||
|
|
||||||
### 4. 类型检查
|
|
||||||
|
|
||||||
**工具**: TypeScript
|
|
||||||
|
|
||||||
**检查时机**: pre-commit hook
|
|
||||||
|
|
||||||
**检查内容**:
|
|
||||||
- 类型错误
|
|
||||||
- 类型推断
|
|
||||||
|
|
||||||
**通过标准**: 无类型错误
|
|
||||||
|
|
||||||
## 如何绕过质量门禁
|
|
||||||
|
|
||||||
⚠️ **警告**: 仅在紧急情况下绕过质量门禁
|
|
||||||
|
|
||||||
### 绕过pre-commit hook
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit --no-verify -m "message"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 绕过commit-msg hook
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit --no-verify -m "message"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 绕过所有hooks
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit --no-verify -m "message"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 故障排查
|
|
||||||
|
|
||||||
### ESLint错误
|
|
||||||
|
|
||||||
**问题**: pre-commit hook因ESLint错误失败
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
1. 查看错误详情
|
|
||||||
2. 修复代码或配置
|
|
||||||
3. 运行 `npm run lint` 检查
|
|
||||||
4. 重新提交
|
|
||||||
|
|
||||||
### Prettier错误
|
|
||||||
|
|
||||||
**问题**: pre-commit hook因Prettier格式化失败
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
1. 运行 `npm run lint` 自动修复
|
|
||||||
2. 手动修复无法自动修复的问题
|
|
||||||
3. 重新提交
|
|
||||||
|
|
||||||
### commitlint错误
|
|
||||||
|
|
||||||
**问题**: commit-msg hook因提交信息格式错误失败
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
1. 检查提交信息格式
|
|
||||||
2. 使用正确的提交类型
|
|
||||||
3. 重新提交
|
|
||||||
|
|
||||||
### 覆盖率不达标
|
|
||||||
|
|
||||||
**问题**: 覆盖率检查失败
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
1. 查看覆盖率报告
|
|
||||||
2. 补充测试用例
|
|
||||||
3. 重新提交
|
|
||||||
|
|
||||||
## 持续改进
|
|
||||||
|
|
||||||
### 提高覆盖率阈值
|
|
||||||
|
|
||||||
随着项目发展,逐步提高覆盖率阈值:
|
|
||||||
- Phase 1: 70% (当前)
|
|
||||||
- Phase 2: 75%
|
|
||||||
- Phase 3: 80%
|
|
||||||
- Phase 4: 85%
|
|
||||||
- Phase 5: 90%
|
|
||||||
|
|
||||||
### 添加更多质量检查
|
|
||||||
|
|
||||||
未来可以添加:
|
|
||||||
- 复杂度检查
|
|
||||||
- 重复代码检查
|
|
||||||
- 安全漏洞扫描
|
|
||||||
- 依赖漏洞检查
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
|
||||||
- [ESLint文档](https://eslint.org/)
|
|
||||||
- [Prettier文档](https://prettier.io/)
|
|
||||||
- [Jest文档](https://jestjs.io/)
|
|
||||||
- [Husky文档](https://typicode.github.io/husky/)
|
|
||||||
- [lint-staged文档](https://github.com/okonet/lint-staged)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 提交文档**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add docs/development/quality-gates.md
|
|
||||||
git commit -m "docs: add quality gates documentation"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 8: 更新项目文档
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `README.md`
|
|
||||||
- Modify: `docs/development/getting-started.md`
|
|
||||||
|
|
||||||
**Step 1: 更新README.md添加质量门禁说明**
|
|
||||||
|
|
||||||
在README.md的"开发指南"部分添加:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
### 代码质量门禁
|
|
||||||
|
|
||||||
项目配置了自动化质量门禁,确保代码提交前通过所有质量检查:
|
|
||||||
|
|
||||||
- **ESLint**: 代码风格检查
|
|
||||||
- **Prettier**: 代码格式化
|
|
||||||
- **commitlint**: 提交信息规范
|
|
||||||
- **Jest**: 代码覆盖率检查
|
|
||||||
|
|
||||||
详细信息请查看 [质量门禁文档](docs/development/quality-gates.md)。
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 更新快速开始指南**
|
|
||||||
|
|
||||||
在 `docs/development/getting-started.md` 添加:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## 代码质量门禁
|
|
||||||
|
|
||||||
项目配置了自动化质量门禁,确保代码提交前通过所有质量检查。
|
|
||||||
|
|
||||||
### 质量检查
|
|
||||||
|
|
||||||
- **代码风格检查**: ESLint + Prettier
|
|
||||||
- **提交信息规范**: commitlint
|
|
||||||
- **代码覆盖率检查**: Jest
|
|
||||||
|
|
||||||
### 提交规范
|
|
||||||
|
|
||||||
使用Conventional Commits规范:
|
|
||||||
|
|
||||||
```
|
|
||||||
<type>(<scope>): <subject>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
```
|
|
||||||
|
|
||||||
**提交类型**:
|
|
||||||
- `feat`: 新功能
|
|
||||||
- `fix`: 修复bug
|
|
||||||
- `docs`: 文档更新
|
|
||||||
- `style`: 代码格式调整
|
|
||||||
- `refactor`: 重构
|
|
||||||
- `perf`: 性能优化
|
|
||||||
- `test`: 测试相关
|
|
||||||
- `chore`: 构建/工具相关
|
|
||||||
|
|
||||||
详细信息请查看 [质量门禁文档](quality-gates.md)。
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 3: 提交文档更新**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add README.md docs/development/getting-started.md
|
|
||||||
git commit -m "docs: update documentation for quality gates"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 9: 验证质量门禁
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Test: 创建测试文件验证所有hooks
|
|
||||||
|
|
||||||
**Step 1: 测试pre-commit hook**
|
|
||||||
|
|
||||||
创建测试文件:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo "console.log('test');" > test-precommit.js
|
|
||||||
git add test-precommit.js
|
|
||||||
git commit -m "test: pre-commit hook"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: pre-commit hook运行lint-staged,格式化代码
|
|
||||||
|
|
||||||
**Step 2: 测试commit-msg hook**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit --amend -m "invalid commit message"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: commit-msg hook拒绝提交
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit --amend -m "test: verify commit-msg hook"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: commit-msg hook通过
|
|
||||||
|
|
||||||
**Step 3: 测试覆盖率检查**
|
|
||||||
|
|
||||||
修改package.json触发覆盖率检查:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo "// modified" >> package.json
|
|
||||||
git add package.json
|
|
||||||
git commit -m "test: coverage check"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: 运行测试并检查覆盖率
|
|
||||||
|
|
||||||
**Step 4: 清理测试文件**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm test-precommit.js
|
|
||||||
git add -A
|
|
||||||
git commit -m "chore: remove test files"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 5: 提交验证结果**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline -5
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: 显示所有提交都通过了质量门禁
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 10: 创建CI/CD集成文档
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `docs/deployment/quality-gates-ci.md`
|
|
||||||
|
|
||||||
**Step 1: 创建CI/CD集成文档**
|
|
||||||
|
|
||||||
创建文件 `docs/deployment/quality-gates-ci.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# CI/CD中的质量门禁
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
质量门禁不仅在本地的Git hooks中运行,也在CI/CD流水线中执行,确保所有合并到主分支的代码都符合质量标准。
|
|
||||||
|
|
||||||
## Woodpecker CI配置
|
|
||||||
|
|
||||||
### 质量检查步骤
|
|
||||||
|
|
||||||
在 `.woodpecker.yml` 中添加质量检查步骤:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
pipeline:
|
|
||||||
quality-check:
|
|
||||||
image: node:18-alpine
|
|
||||||
environment:
|
|
||||||
NODE_ENV: test
|
|
||||||
commands:
|
|
||||||
- npm ci
|
|
||||||
- npm run lint
|
|
||||||
- npm run type-check
|
|
||||||
- npm run test:coverage
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
coverage-report:
|
|
||||||
image: node:18-alpine
|
|
||||||
environment:
|
|
||||||
NODE_ENV: test
|
|
||||||
commands:
|
|
||||||
- npm ci
|
|
||||||
- npm run test:coverage
|
|
||||||
# 上传覆盖率报告到Codecov或其他服务
|
|
||||||
secrets: [codecov_token]
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
- pull_request
|
|
||||||
```
|
|
||||||
|
|
||||||
### 质量门禁规则
|
|
||||||
|
|
||||||
CI/CD中的质量门禁规则:
|
|
||||||
|
|
||||||
1. **代码检查**: ESLint必须通过,无错误
|
|
||||||
2. **类型检查**: TypeScript编译必须成功
|
|
||||||
3. **测试通过**: 所有测试必须通过
|
|
||||||
4. **覆盖率达标**: 代码覆盖率必须≥70%
|
|
||||||
|
|
||||||
### 失败处理
|
|
||||||
|
|
||||||
如果质量检查失败:
|
|
||||||
|
|
||||||
1. CI/CD流水线失败
|
|
||||||
2. 阻止合并到主分支
|
|
||||||
3. 发送通知给开发者
|
|
||||||
4. 显示详细的错误信息
|
|
||||||
|
|
||||||
## 本地开发 vs CI/CD
|
|
||||||
|
|
||||||
### 本地开发
|
|
||||||
|
|
||||||
- **优点**: 快速反馈,立即发现问题
|
|
||||||
- **缺点**: 可能被绕过(--no-verify)
|
|
||||||
|
|
||||||
### CI/CD
|
|
||||||
|
|
||||||
- **优点**: 强制执行,无法绕过
|
|
||||||
- **缺点**: 反馈延迟,需要等待CI运行
|
|
||||||
|
|
||||||
### 最佳实践
|
|
||||||
|
|
||||||
1. **本地优先**: 在本地运行质量检查,确保通过后再推送
|
|
||||||
2. **CI兜底**: CI/CD作为最后一道防线,确保质量
|
|
||||||
3. **快速反馈**: CI/CD配置为快速失败,尽早发现问题
|
|
||||||
|
|
||||||
## 持续改进
|
|
||||||
|
|
||||||
### 监控质量指标
|
|
||||||
|
|
||||||
定期监控以下指标:
|
|
||||||
|
|
||||||
- 代码覆盖率趋势
|
|
||||||
- ESLint错误数量
|
|
||||||
- TypeScript错误数量
|
|
||||||
- 测试失败率
|
|
||||||
- CI/CD通过率
|
|
||||||
|
|
||||||
### 优化质量门禁
|
|
||||||
|
|
||||||
根据监控数据优化质量门禁:
|
|
||||||
|
|
||||||
1. 调整覆盖率阈值
|
|
||||||
2. 添加新的质量检查
|
|
||||||
3. 优化检查性能
|
|
||||||
4. 改进错误提示
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
- [Woodpecker CI文档](https://woodpecker-ci.org/)
|
|
||||||
- [Codecov文档](https://docs.codecov.com/)
|
|
||||||
- [GitHub Actions文档](https://docs.github.com/en/actions)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Step 2: 提交文档**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add docs/deployment/quality-gates-ci.md
|
|
||||||
git commit -m "docs: add CI/CD quality gates documentation"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 验收标准
|
|
||||||
|
|
||||||
### 功能完整性
|
|
||||||
- ✅ Husky Git hooks正常工作
|
|
||||||
- ✅ lint-staged对暂存文件进行检查
|
|
||||||
- ✅ commitlint验证提交信息
|
|
||||||
- ✅ Jest生成覆盖率报告
|
|
||||||
- ✅ 覆盖率检查集成到pre-commit
|
|
||||||
|
|
||||||
### 代码质量
|
|
||||||
- ✅ 所有质量检查通过
|
|
||||||
- ✅ 覆盖率达到70%以上
|
|
||||||
- ✅ 提交信息符合规范
|
|
||||||
- ✅ 代码风格一致
|
|
||||||
|
|
||||||
### 文档完整性
|
|
||||||
- ✅ 质量门禁文档完整
|
|
||||||
- ✅ CI/CD集成文档完整
|
|
||||||
- ✅ 使用指南清晰
|
|
||||||
- ✅ 故障排查指南完整
|
|
||||||
|
|
||||||
### 可维护性
|
|
||||||
- ✅ 配置文件清晰易懂
|
|
||||||
- ✅ 脚本可维护
|
|
||||||
- ✅ 文档更新及时
|
|
||||||
- ✅ 团队培训完成
|
|
||||||
|
|
||||||
## 后续优化
|
|
||||||
|
|
||||||
1. **提高覆盖率阈值**: 逐步提高到80%、85%、90%
|
|
||||||
2. **添加更多检查**: 复杂度检查、重复代码检查、安全扫描
|
|
||||||
3. **集成更多工具**: SonarQube、CodeClimate等
|
|
||||||
4. **自动化更多流程**: 自动生成CHANGELOG、自动发布等
|
|
||||||
5. **性能优化**: 优化质量检查性能,减少等待时间
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
- [Husky文档](https://typicode.github.io/husky/)
|
|
||||||
- [lint-staged文档](https://github.com/okonet/lint-staged)
|
|
||||||
- [commitlint文档](https://commitlint.js.org/)
|
|
||||||
- [Jest文档](https://jestjs.io/)
|
|
||||||
- [Conventional Commits](https://www.conventionalcommits.org/)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,255 +0,0 @@
|
|||||||
# Monorepo 多站点架构设计方案
|
|
||||||
|
|
||||||
## 背景
|
|
||||||
|
|
||||||
当企业需要为多个产品/项目创建独立展示时,面临架构选择:**单独页面** vs **独立网站**。
|
|
||||||
|
|
||||||
经过需求分析,确定以下约束条件:
|
|
||||||
|
|
||||||
| 维度 | 需求 | 架构影响 |
|
|
||||||
|------|------|----------|
|
|
||||||
| 产品数量 | 动态增长,未来持续增加 | 需要高可扩展性 |
|
|
||||||
| 品牌关系 | 独立子品牌 | 需要视觉独立性 |
|
|
||||||
| 团队规模 | 1-2人精简团队 | 需要低维护成本 |
|
|
||||||
| SEO要求 | 高要求,独立域名 | 需要独立部署能力 |
|
|
||||||
|
|
||||||
## 方案对比
|
|
||||||
|
|
||||||
### 方案A:独立网站(多仓库)
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 产品A (独立仓库) │ │ 产品B (独立仓库) │ │ 产品C (独立仓库) │
|
|
||||||
│ product-a.com │ │ product-b.com │ │ product-c.com │
|
|
||||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**优点**:完全独立、SEO最优、互不影响
|
|
||||||
**缺点**:❌ 维护成本极高、代码重复严重、安全更新繁琐
|
|
||||||
|
|
||||||
### 方案B:单站内嵌页面
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────────────────────────────────────────────┐
|
|
||||||
│ novalon.cn (主站) │
|
|
||||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
|
||||||
│ │ /product-a │ │ /product-b │ │ /product-c │ │
|
|
||||||
│ └────────────┘ └────────────┘ └────────────┘ │
|
|
||||||
└──────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**优点**:维护成本最低、部署简单
|
|
||||||
**缺点**:❌ 无法独立域名、SEO受限、品牌独立性差
|
|
||||||
|
|
||||||
### 方案C:Monorepo多站点架构 ⭐ 推荐
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ Monorepo (统一仓库) │
|
|
||||||
├─────────────────────────────────────────────────────────┤
|
|
||||||
│ apps/ │
|
|
||||||
│ ├── main-site/ → novalon.cn │
|
|
||||||
│ ├── product-a/ → product-a.com (独立域名) │
|
|
||||||
│ ├── product-b/ → product-b.com (独立域名) │
|
|
||||||
│ └── product-c/ → product-c.com (独立域名) │
|
|
||||||
│ │
|
|
||||||
│ packages/ (共享代码) │
|
|
||||||
│ ├── ui/ → 共享组件库 │
|
|
||||||
│ ├── config/ → 共享配置 │
|
|
||||||
│ └── utils/ → 共享工具函数 │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术设计
|
|
||||||
|
|
||||||
### 目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
novalon-website/
|
|
||||||
├── apps/ # 应用层(独立部署)
|
|
||||||
│ ├── main-site/ # 主站 → novalon.cn
|
|
||||||
│ │ ├── src/
|
|
||||||
│ │ ├── next.config.ts
|
|
||||||
│ │ └── package.json
|
|
||||||
│ │
|
|
||||||
│ └── products/ # 产品站点集合
|
|
||||||
│ ├── [product-slug]/ # 产品模板(可复制)
|
|
||||||
│ │ ├── src/
|
|
||||||
│ │ ├── public/
|
|
||||||
│ │ ├── next.config.ts
|
|
||||||
│ │ └── package.json
|
|
||||||
│ └── ...
|
|
||||||
│
|
|
||||||
├── packages/ # 共享层(不独立部署)
|
|
||||||
│ ├── ui/ # 共享组件库
|
|
||||||
│ │ ├── components/
|
|
||||||
│ │ │ ├── base/ # 基础组件(Button、Card等)
|
|
||||||
│ │ │ └── layout/ # 布局组件(Header、Footer等)
|
|
||||||
│ │ └── package.json
|
|
||||||
│ │
|
|
||||||
│ ├── config/ # 共享配置
|
|
||||||
│ │ ├── tailwind/ # Tailwind预设
|
|
||||||
│ │ ├── eslint/ # ESLint规则
|
|
||||||
│ │ └── typescript/ # TS配置
|
|
||||||
│ │
|
|
||||||
│ └── utils/ # 共享工具
|
|
||||||
│ ├── lib/
|
|
||||||
│ └── package.json
|
|
||||||
│
|
|
||||||
├── turbo.json # Turborepo配置
|
|
||||||
├── pnpm-workspace.yaml # pnpm工作区配置
|
|
||||||
└── package.json # 根package.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### 共享组件库设计
|
|
||||||
|
|
||||||
```
|
|
||||||
packages/ui/
|
|
||||||
├── components/
|
|
||||||
│ ├── base/ # 基础组件(完全共享)
|
|
||||||
│ │ ├── button/
|
|
||||||
│ │ ├── card/
|
|
||||||
│ │ ├── input/
|
|
||||||
│ │ └── ...
|
|
||||||
│ │
|
|
||||||
│ └── themed/ # 主题化组件(可覆盖)
|
|
||||||
│ ├── header/
|
|
||||||
│ └── footer/
|
|
||||||
│
|
|
||||||
├── themes/ # 主题配置
|
|
||||||
│ ├── default.ts # 默认主题
|
|
||||||
│ ├── product-a.ts # 产品A主题
|
|
||||||
│ └── product-b.ts # 产品B主题
|
|
||||||
│
|
|
||||||
└── lib/
|
|
||||||
└── theme-context.tsx # 主题上下文
|
|
||||||
```
|
|
||||||
|
|
||||||
**组件分层策略**:
|
|
||||||
|
|
||||||
| 组件类型 | 共享程度 | 定制方式 |
|
|
||||||
|----------|----------|----------|
|
|
||||||
| 基础组件 | 100%共享 | 通过 props 和 CSS 变量覆盖样式 |
|
|
||||||
| 布局组件 | 接口共享 | 各应用可提供自己的实现 |
|
|
||||||
| 业务组件 | 不共享 | 各应用独立开发 |
|
|
||||||
|
|
||||||
### CI/CD 流水线
|
|
||||||
|
|
||||||
```
|
|
||||||
[代码推送] → [变更检测] → [增量构建] → [并行测试] → [智能部署]
|
|
||||||
↓ ↓ ↓
|
|
||||||
哪些应用变了? 只构建变的应用 只部署变的应用
|
|
||||||
```
|
|
||||||
|
|
||||||
**部署策略**:
|
|
||||||
|
|
||||||
| 场景 | 构建范围 | 部署范围 |
|
|
||||||
|------|----------|----------|
|
|
||||||
| 只改了 `apps/product-a` | 只构建 product-a | 只部署 product-a |
|
|
||||||
| 改了 `packages/ui` | 构建所有应用 | 部署所有应用 |
|
|
||||||
| 改了 `packages/config` | 构建所有应用 | 部署所有应用 |
|
|
||||||
|
|
||||||
### SEO优化策略
|
|
||||||
|
|
||||||
**独立域名架构**:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
|
||||||
│ Nginx 反向代理 │
|
|
||||||
├─────────────────────────────────────────────────────────────┤
|
|
||||||
│ novalon.cn → 主站容器 (localhost:3000) │
|
|
||||||
│ product-a.com → 产品A容器 (localhost:3001) │
|
|
||||||
│ product-b.com → 产品B容器 (localhost:3002) │
|
|
||||||
└─────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
**SEO关键优势**:
|
|
||||||
|
|
||||||
| SEO 要素 | 独立站点优势 |
|
|
||||||
|----------|-------------|
|
|
||||||
| 独立域名 | 搜索引擎视为独立实体,权重互不影响 |
|
|
||||||
| 独立 sitemap | 精准控制索引范围,提升爬取效率 |
|
|
||||||
| 独立 metadata | 针对产品特性优化关键词,避免稀释 |
|
|
||||||
| 独立 robots.txt | 灵活控制爬虫访问策略 |
|
|
||||||
|
|
||||||
## 迁移路径
|
|
||||||
|
|
||||||
```
|
|
||||||
阶段1: 基础设施搭建 (1-2天)
|
|
||||||
↓
|
|
||||||
阶段2: 代码迁移与重构 (3-5天)
|
|
||||||
↓
|
|
||||||
阶段3: 共享组件抽取 (2-3天)
|
|
||||||
↓
|
|
||||||
阶段4: CI/CD 配置 (1-2天)
|
|
||||||
↓
|
|
||||||
阶段5: 第一个产品站点 (2-3天)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 阶段1:基础设施搭建
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 创建 Monorepo 根目录结构
|
|
||||||
mkdir -p apps packages
|
|
||||||
|
|
||||||
# 2. 初始化 pnpm 工作区
|
|
||||||
cat > pnpm-workspace.yaml << EOF
|
|
||||||
packages:
|
|
||||||
- 'apps/*'
|
|
||||||
- 'apps/products/*'
|
|
||||||
- 'packages/*'
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 3. 安装 Turborepo
|
|
||||||
pnpm add -Dw turbo
|
|
||||||
```
|
|
||||||
|
|
||||||
### 阶段2:代码迁移
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 将现有代码移动到 apps/main-site
|
|
||||||
mv src apps/main-site/src
|
|
||||||
mv public apps/main-site/public
|
|
||||||
mv next.config.ts apps/main-site/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 阶段3:共享组件抽取
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 创建共享 UI 包
|
|
||||||
mkdir -p packages/ui/components
|
|
||||||
|
|
||||||
# 抽取通用组件
|
|
||||||
mv apps/main-site/src/components/ui packages/ui/components/base
|
|
||||||
```
|
|
||||||
|
|
||||||
### 阶段4:创建产品站点
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 复制主站作为模板
|
|
||||||
cp -r apps/main-site apps/products/product-template
|
|
||||||
|
|
||||||
# 创建新产品站点
|
|
||||||
cp -r apps/products/product-template apps/products/product-a
|
|
||||||
```
|
|
||||||
|
|
||||||
## 决策总结
|
|
||||||
|
|
||||||
| 评估维度 | 独立网站 | 单站内嵌页面 | Monorepo多站点 |
|
|
||||||
|----------|----------|--------------|----------------|
|
|
||||||
| 独立品牌支持 | ✅ 完美 | ❌ 差 | ✅ 完美 |
|
|
||||||
| SEO独立性 | ✅ 最优 | ❌ 受限 | ✅ 最优 |
|
|
||||||
| 维护成本 | ❌ 极高 | ✅ 最低 | ✅ 低 |
|
|
||||||
| 代码复用 | ❌ 无 | ✅ 完全 | ✅ 高度复用 |
|
|
||||||
| 扩展性 | ⚠️ 中等 | ❌ 差 | ✅ 优秀 |
|
|
||||||
| 团队适配 | ❌ 不适合精简团队 | ⚠️ 不满足需求 | ✅ 完美适配 |
|
|
||||||
|
|
||||||
## 结论
|
|
||||||
|
|
||||||
针对**动态增长 + 独立子品牌 + 精简团队 + 高SEO要求**的场景,**Monorepo多站点架构**是最佳选择:
|
|
||||||
|
|
||||||
- ✅ 品牌独立:每个产品独立应用、独立域名、独立视觉
|
|
||||||
- ✅ SEO最优:独立sitemap、独立metadata、独立域名权重
|
|
||||||
- ✅ 维护高效:共享代码库、统一依赖、一次更新全局生效
|
|
||||||
- ✅ 扩展简单:新增产品只需复制模板目录
|
|
||||||
- ✅ 智能CI/CD:增量构建、按需部署、自动化流水线
|
|
||||||
@@ -0,0 +1,769 @@
|
|||||||
|
# 项目优化实施计划
|
||||||
|
|
||||||
|
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
|
||||||
|
|
||||||
|
**目标:** 根据代码审查报告,修复所有必须修复的问题,完成建议的优化项,提升项目整体质量。
|
||||||
|
|
||||||
|
**架构:** 纯静态 Next.js 网站,修复 TypeScript 配置问题,清理无效配置,优化测试环境,改进代码组织结构。
|
||||||
|
|
||||||
|
**技术栈:** Next.js 16, React 19, TypeScript, Jest, Playwright
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文件结构
|
||||||
|
|
||||||
|
### 将要修改的文件
|
||||||
|
|
||||||
|
| 文件 | 职责 | 变更类型 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `tsconfig.json` | TypeScript 配置 | 修改 - 添加 Jest 类型支持 |
|
||||||
|
| `next.config.ts` | Next.js 配置 | 修改 - 移除无效 headers 配置 |
|
||||||
|
| `src/contexts/theme-context.tsx` | 主题上下文 | 修改 - 简化为纯静态版本 |
|
||||||
|
| `src/app/layout.tsx` | 根布局 | 修改 - 移除暗色主题脚本 |
|
||||||
|
| `package.json` | 项目配置 | 修改 - 清理无效脚本 |
|
||||||
|
| `e2e/website-acceptance.spec.ts` | E2E 测试 | 修改 - 支持环境变量 URL |
|
||||||
|
| `e2e/playwright.config.ts` | Playwright 配置 | 修改 - 添加 baseURL 配置 |
|
||||||
|
| `nginx-static.conf` | Nginx 配置 | 修改 - 添加安全头 |
|
||||||
|
|
||||||
|
### 将要创建的文件
|
||||||
|
|
||||||
|
| 文件 | 职责 |
|
||||||
|
|------|------|
|
||||||
|
| `src/lib/constants/index.ts` | 常量导出入口 |
|
||||||
|
| `src/lib/constants/company.ts` | 公司信息常量 |
|
||||||
|
| `src/lib/constants/navigation.ts` | 导航配置 |
|
||||||
|
| `src/lib/constants/services.ts` | 服务数据 |
|
||||||
|
| `src/lib/constants/products.ts` | 产品数据 |
|
||||||
|
| `src/lib/constants/news.ts` | 新闻数据 |
|
||||||
|
| `src/lib/constants/stats.ts` | 统计数据 |
|
||||||
|
|
||||||
|
### 将要删除的文件
|
||||||
|
|
||||||
|
| 文件 | 原因 |
|
||||||
|
|------|------|
|
||||||
|
| 无 | 本次优化不删除文件 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段一:P0 必须修复(立即处理)
|
||||||
|
|
||||||
|
### 任务 1:修复 TypeScript 测试类型错误
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 修改:`tsconfig.json`
|
||||||
|
|
||||||
|
**问题:** 测试文件中 `describe`、`it`、`expect`、`beforeEach` 等全局类型未定义,导致 `npm run type-check` 失败。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:修改 tsconfig.json 添加 Jest 类型支持**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"incremental": true,
|
||||||
|
"types": ["jest", "node"],
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".next/dev/types/**/*.ts",
|
||||||
|
"**/*.mts",
|
||||||
|
"dist/types/**/*.ts",
|
||||||
|
"dist/dev/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"e2e"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 2:运行类型检查验证修复**
|
||||||
|
|
||||||
|
运行:`npm run type-check`
|
||||||
|
预期:无测试相关的类型错误
|
||||||
|
|
||||||
|
- [ ] **步骤 3:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add tsconfig.json
|
||||||
|
git commit -m "fix: 添加 Jest 类型支持,修复测试文件类型错误"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段二:P1 建议修改(短期优化)
|
||||||
|
|
||||||
|
### 任务 2:移除无效的 headers 配置
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 修改:`next.config.ts`
|
||||||
|
- 修改:`nginx-static.conf`
|
||||||
|
|
||||||
|
**问题:** `output: 'export'` 静态导出模式下,`headers` 配置不会生效。应将安全头配置移至 nginx。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:简化 next.config.ts,移除 headers 配置**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const cdnDomain = process.env.CDN_DOMAIN || '';
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
distDir: 'dist',
|
||||||
|
output: 'export',
|
||||||
|
assetPrefix: cdnDomain || undefined,
|
||||||
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
|
compress: true,
|
||||||
|
poweredByHeader: false,
|
||||||
|
reactStrictMode: true,
|
||||||
|
experimental: {
|
||||||
|
optimizePackageImports: ['lucide-react'],
|
||||||
|
},
|
||||||
|
compiler: {
|
||||||
|
removeConsole: process.env.NODE_ENV === 'production',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 2:更新 nginx-static.conf 添加安全头**
|
||||||
|
|
||||||
|
读取当前 nginx-static.conf 内容后追加安全头配置。
|
||||||
|
|
||||||
|
- [ ] **步骤 3:运行构建验证配置正确**
|
||||||
|
|
||||||
|
运行:`npm run build`
|
||||||
|
预期:构建成功,无错误
|
||||||
|
|
||||||
|
- [ ] **步骤 4:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add next.config.ts nginx-static.conf
|
||||||
|
git commit -m "refactor: 移除无效的 headers 配置,安全头移至 nginx"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 任务 3:简化 ThemeContext 为纯静态版本
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 修改:`src/contexts/theme-context.tsx`
|
||||||
|
- 修改:`src/app/layout.tsx`
|
||||||
|
|
||||||
|
**问题:** ThemeProvider 硬编码只返回 'light',但 layout.tsx 中还有暗色主题切换脚本,两者不一致。纯静态网站不需要主题切换。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:简化 theme-context.tsx**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, type ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface ThemeContextType {
|
||||||
|
theme: 'light';
|
||||||
|
resolvedTheme: 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme: 'light', resolvedTheme: 'light' }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const context = useContext(ThemeContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useTheme must be used within a ThemeProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 2:修改 layout.tsx 移除暗色主题脚本**
|
||||||
|
|
||||||
|
移除 `<head>` 中的暗色主题检测脚本:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 移除以下代码块:
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
try {
|
||||||
|
const theme = localStorage.getItem('ruixin-theme') || 'system';
|
||||||
|
if (theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 3:运行类型检查验证修改**
|
||||||
|
|
||||||
|
运行:`npm run type-check`
|
||||||
|
预期:无错误
|
||||||
|
|
||||||
|
- [ ] **步骤 4:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/contexts/theme-context.tsx src/app/layout.tsx
|
||||||
|
git commit -m "refactor: 简化主题上下文为纯静态版本,移除暗色主题切换逻辑"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 任务 4:清理无效的 npm 脚本
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 修改:`package.json`
|
||||||
|
|
||||||
|
**问题:** 部分脚本引用不存在的文件,如 `audit:all`、`test:performance`、`test:stress` 等。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:检查哪些脚本文件存在**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la scripts/run-all-tests.sh 2>/dev/null || echo "不存在"
|
||||||
|
ls -la tests/performance/ 2>/dev/null || echo "不存在"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 2:移除无效脚本,保留有效的脚本**
|
||||||
|
|
||||||
|
修改 package.json 的 scripts 部分:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev -p 3000",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "npx serve dist -p 3000",
|
||||||
|
"lint": "eslint",
|
||||||
|
"type-check": "tsc --noEmit",
|
||||||
|
"preview": "npx serve dist -p 3000",
|
||||||
|
"test": "cd e2e && npx playwright test --config=playwright.config.ts",
|
||||||
|
"test:unit": "jest",
|
||||||
|
"test:coverage": "jest --coverage",
|
||||||
|
"test:coverage:check": "jest --coverage --ci",
|
||||||
|
"coverage:report": "open coverage/lcov-report/index.html",
|
||||||
|
"test:e2e": "cd e2e && npm test",
|
||||||
|
"test:smoke": "cd e2e && npx playwright test --grep @smoke",
|
||||||
|
"check:contrast": "tsx scripts/utils/check-color-contrast.ts",
|
||||||
|
"check:headings": "tsx scripts/utils/check-heading-hierarchy.ts",
|
||||||
|
"lighthouse": "lhci autorun",
|
||||||
|
"lighthouse:collect": "lhci collect",
|
||||||
|
"lighthouse:assert": "lhci assert",
|
||||||
|
"lighthouse:upload": "lhci upload",
|
||||||
|
"lighthouse:desktop": "lhci autorun --settings.preset=desktop",
|
||||||
|
"lighthouse:mobile": "lhci autorun --settings.preset=mobile",
|
||||||
|
"deploy:cdn": "bash scripts/deploy-cdn.sh",
|
||||||
|
"deploy:cdn:refresh": "bash scripts/refresh-cdn.sh",
|
||||||
|
"prepare": "husky"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 3:运行 npm run 验证脚本有效**
|
||||||
|
|
||||||
|
运行:`npm run`
|
||||||
|
预期:列出所有可用脚本,无错误
|
||||||
|
|
||||||
|
- [ ] **步骤 4:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add package.json
|
||||||
|
git commit -m "chore: 清理无效的 npm 脚本命令"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 任务 5:修复 E2E 测试硬编码 URL
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 修改:`e2e/playwright.config.ts`
|
||||||
|
- 修改:`e2e/website-acceptance.spec.ts`
|
||||||
|
|
||||||
|
**问题:** E2E 测试硬编码 `https://novalon.cn`,无法在本地或测试环境运行。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:检查 playwright.config.ts 当前配置**
|
||||||
|
|
||||||
|
读取 `e2e/playwright.config.ts` 内容。
|
||||||
|
|
||||||
|
- [ ] **步骤 2:修改 playwright.config.ts 添加 baseURL**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './',
|
||||||
|
fullyParallel: true,
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
reporter: 'html',
|
||||||
|
use: {
|
||||||
|
baseURL: process.env.E2E_BASE_URL || 'http://localhost:3000',
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run preview',
|
||||||
|
url: 'http://localhost:3000',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
timeout: 120000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 3:修改 website-acceptance.spec.ts 使用相对路径**
|
||||||
|
|
||||||
|
将所有硬编码 URL 改为相对路径:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test.describe('网站全面测试验收', () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('首页加载正常', async ({ page }) => {
|
||||||
|
await expect(page).toHaveTitle(/四川睿新致远科技有限公司/);
|
||||||
|
await expect(page.locator('header')).toBeVisible();
|
||||||
|
await expect(page.locator('footer')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
// ... 其他测试用例,将 https://novalon.cn 替换为空或相对路径
|
||||||
|
|
||||||
|
test('联系我们页面没有显示公司电话', async ({ page }) => {
|
||||||
|
await page.goto('/contact');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
test('关于我们页面没有显示公司电话', async ({ page }) => {
|
||||||
|
await page.goto('/about');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
test('联系我们页面表单正常显示', async ({ page }) => {
|
||||||
|
await page.goto('/contact');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
test('页面跳转功能正常', async ({ page }) => {
|
||||||
|
await page.click('text=联系我们');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
expect(page.url()).toContain('/contact');
|
||||||
|
|
||||||
|
await page.click('text=首页');
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
expect(page.url()).toBe(process.env.E2E_BASE_URL || 'http://localhost:3000/');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 4:运行 E2E 测试验证修改**
|
||||||
|
|
||||||
|
运行:`npm run test`
|
||||||
|
预期:测试可以正常运行(需要先构建)
|
||||||
|
|
||||||
|
- [ ] **步骤 5:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add e2e/playwright.config.ts e2e/website-acceptance.spec.ts
|
||||||
|
git commit -m "fix: E2E 测试支持环境变量 URL,可在本地运行"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 任务 6:更新 Jest 配置路径
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 修改:`config/test/jest.config.js`
|
||||||
|
|
||||||
|
**问题:** `setupFilesAfterEnv` 指向 `<rootDir>/jest.setup.js`,但实际文件在根目录。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:检查 jest.setup.js 位置**
|
||||||
|
|
||||||
|
确认 `jest.setup.js` 在项目根目录。
|
||||||
|
|
||||||
|
- [ ] **步骤 2:修改 jest.config.js 路径**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
|
roots: ['<rootDir>/../src'],
|
||||||
|
testMatch: ['**/__tests__/**/*.test.{ts,tsx}', '**/*.test.{ts,tsx}'],
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'src/**/*.{ts,tsx}',
|
||||||
|
'!src/**/*.d.ts',
|
||||||
|
'!src/**/*.stories.{ts,tsx}',
|
||||||
|
'!src/**/__tests__/**',
|
||||||
|
],
|
||||||
|
coverageThreshold: {
|
||||||
|
global: {
|
||||||
|
branches: 80,
|
||||||
|
functions: 80,
|
||||||
|
lines: 80,
|
||||||
|
statements: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coverageReporters: ['text', 'lcov', 'html', 'json'],
|
||||||
|
coverageDirectory: '<rootDir>/../coverage',
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/../src/$1',
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
'^.+\\.(ts|tsx)$': 'ts-jest',
|
||||||
|
},
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'node_modules/(?!(nanoid|next-auth|@auth)/)',
|
||||||
|
],
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/../jest.setup.js'],
|
||||||
|
testTimeout: 10000,
|
||||||
|
verbose: true,
|
||||||
|
maxWorkers: '50%',
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 3:运行单元测试验证配置**
|
||||||
|
|
||||||
|
运行:`npm run test:unit`
|
||||||
|
预期:测试正常运行
|
||||||
|
|
||||||
|
- [ ] **步骤 4:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add config/test/jest.config.js
|
||||||
|
git commit -m "fix: 修正 Jest 配置中的文件路径"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段三:P2 仅供参考(长期改进)
|
||||||
|
|
||||||
|
### 任务 7:拆分 constants.ts 文件
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 创建:`src/lib/constants/index.ts`
|
||||||
|
- 创建:`src/lib/constants/company.ts`
|
||||||
|
- 创建:`src/lib/constants/navigation.ts`
|
||||||
|
- 创建:`src/lib/constants/services.ts`
|
||||||
|
- 创建:`src/lib/constants/products.ts`
|
||||||
|
- 创建:`src/lib/constants/news.ts`
|
||||||
|
- 创建:`src/lib/constants/stats.ts`
|
||||||
|
- 删除:`src/lib/constants.ts`
|
||||||
|
- 修改:所有导入 constants 的文件
|
||||||
|
|
||||||
|
**问题:** `constants.ts` 文件过大(20KB+),包含所有业务数据,不利于维护。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:创建 company.ts**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export const COMPANY_INFO = {
|
||||||
|
name: '四川睿新致远科技有限公司',
|
||||||
|
shortName: '睿新致遠',
|
||||||
|
slogan: '智连未来,成长伙伴',
|
||||||
|
description: '以智慧连接数字趋势,以伙伴身份陪您成长——您的数字化转型同行者',
|
||||||
|
founded: '2026',
|
||||||
|
location: '四川省成都市',
|
||||||
|
email: 'contact@novalon.cn',
|
||||||
|
address: '中国四川省成都市龙泉驿区幸福路12号',
|
||||||
|
icp: '蜀ICP备2026013658号',
|
||||||
|
police: '川公网安备51010602003285号',
|
||||||
|
} as const;
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 2:创建 navigation.ts**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface NavigationItem {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NAVIGATION: NavigationItem[] = [
|
||||||
|
{ id: 'home', label: '首页', href: '/' },
|
||||||
|
{ id: 'services', label: '核心业务', href: '/' },
|
||||||
|
{ id: 'products', label: '产品服务', href: '/' },
|
||||||
|
{ id: 'cases', label: '成功案例', href: '/' },
|
||||||
|
{ id: 'about', label: '关于我们', href: '/' },
|
||||||
|
{ id: 'news', label: '新闻动态', href: '/' },
|
||||||
|
{ id: 'contact', label: '联系我们', href: '/contact' },
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 3:创建 stats.ts**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export interface StatItem {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STATS: StatItem[] = [
|
||||||
|
{ value: '10+', label: '企业客户' },
|
||||||
|
{ value: '20+', label: '成功案例' },
|
||||||
|
{ value: '30+', label: '项目交付' },
|
||||||
|
{ value: '12+', label: '年行业经验' },
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 4:创建 services.ts**
|
||||||
|
|
||||||
|
从原 constants.ts 提取 SERVICES 数据。
|
||||||
|
|
||||||
|
- [ ] **步骤 5:创建 products.ts**
|
||||||
|
|
||||||
|
从原 constants.ts 提取 PRODUCTS 数据。
|
||||||
|
|
||||||
|
- [ ] **步骤 6:创建 news.ts**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export type NewsCategory = '公司新闻' | '产品发布' | '合作动态' | '行业资讯';
|
||||||
|
|
||||||
|
export interface NewsItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
excerpt: string;
|
||||||
|
date: string;
|
||||||
|
category: NewsCategory;
|
||||||
|
image: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NEWS: NewsItem[] = [
|
||||||
|
// 从原 constants.ts 提取
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 7:创建 index.ts 统一导出**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export * from './company';
|
||||||
|
export * from './navigation';
|
||||||
|
export * from './stats';
|
||||||
|
export * from './services';
|
||||||
|
export * from './products';
|
||||||
|
export * from './news';
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 8:删除原 constants.ts**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm src/lib/constants.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 9:运行类型检查验证重构**
|
||||||
|
|
||||||
|
运行:`npm run type-check`
|
||||||
|
预期:无错误
|
||||||
|
|
||||||
|
- [ ] **步骤 10:运行测试验证功能正常**
|
||||||
|
|
||||||
|
运行:`npm run test:unit`
|
||||||
|
预期:所有测试通过
|
||||||
|
|
||||||
|
- [ ] **步骤 11:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/lib/constants/
|
||||||
|
git rm src/lib/constants.ts
|
||||||
|
git commit -m "refactor: 拆分 constants.ts 为模块化结构"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 任务 8:清理过时的计划文档
|
||||||
|
|
||||||
|
**文件:**
|
||||||
|
- 删除:`docs/plans/` 目录下已完成的计划文档
|
||||||
|
|
||||||
|
**问题:** `docs/plans/` 目录下有大量计划文档,部分已过时。
|
||||||
|
|
||||||
|
- [ ] **步骤 1:列出所有计划文档**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la docs/plans/
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 2:评估每个文档是否需要保留**
|
||||||
|
|
||||||
|
保留:
|
||||||
|
- 与当前项目架构相关的文档
|
||||||
|
|
||||||
|
删除:
|
||||||
|
- 已完成的临时计划
|
||||||
|
- 与 CMS 相关的计划
|
||||||
|
- 已过时的测试优化计划
|
||||||
|
|
||||||
|
- [ ] **步骤 3:删除过时文档**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm docs/plans/2025-03-13-intelligent-tiered-test-optimization.md
|
||||||
|
rm docs/plans/2026-03-09-production-readiness-plan.md
|
||||||
|
rm docs/plans/2026-03-09-test-coverage-improvement-plan.md
|
||||||
|
rm docs/plans/2026-03-10-full-module-test-coverage-plan.md
|
||||||
|
rm docs/plans/2026-03-10-gradual-coverage-improvement.md
|
||||||
|
rm docs/plans/2026-03-10-phased-launch-implementation-plan.md
|
||||||
|
rm docs/plans/2026-03-10-production-readiness-execution-plan.md
|
||||||
|
rm docs/plans/2026-03-10-test-coverage-improvement-plan.md
|
||||||
|
rm docs/plans/2026-03-20-quality-improvement-iteration.md
|
||||||
|
rm docs/plans/2026-03-24-code-quality-tools-integration.md
|
||||||
|
rm docs/plans/2026-03-24-contact-form-security-enhancement.md
|
||||||
|
rm docs/plans/2026-03-28-monorepo-multi-site-architecture.md
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **步骤 4:Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add docs/plans/
|
||||||
|
git commit -m "docs: 清理过时的计划文档"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段四:验证与收尾
|
||||||
|
|
||||||
|
### 任务 9:全面验证
|
||||||
|
|
||||||
|
- [ ] **步骤 1:运行类型检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:无错误
|
||||||
|
|
||||||
|
- [ ] **步骤 2:运行 lint 检查**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:无错误
|
||||||
|
|
||||||
|
- [ ] **步骤 3:运行单元测试**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run test:unit
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:所有测试通过
|
||||||
|
|
||||||
|
- [ ] **步骤 4:运行构建**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
预期:构建成功
|
||||||
|
|
||||||
|
- [ ] **步骤 5:本地预览验证**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
手动验证:
|
||||||
|
- 首页加载正常
|
||||||
|
- 导航功能正常
|
||||||
|
- 各页面链接正常
|
||||||
|
- 样式显示正确
|
||||||
|
|
||||||
|
- [ ] **步骤 6:最终 Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: 完成项目优化,通过全面验证"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 执行顺序总结
|
||||||
|
|
||||||
|
| 阶段 | 任务 | 优先级 | 预计时间 |
|
||||||
|
|------|------|--------|----------|
|
||||||
|
| 阶段一 | 任务 1:修复 TypeScript 类型错误 | P0 | 10 分钟 |
|
||||||
|
| 阶段二 | 任务 2:移除无效 headers 配置 | P1 | 15 分钟 |
|
||||||
|
| 阶段二 | 任务 3:简化 ThemeContext | P1 | 10 分钟 |
|
||||||
|
| 阶段二 | 任务 4:清理无效脚本 | P1 | 10 分钟 |
|
||||||
|
| 阶段二 | 任务 5:修复 E2E 测试 URL | P1 | 20 分钟 |
|
||||||
|
| 阶段二 | 任务 6:更新 Jest 配置 | P1 | 10 分钟 |
|
||||||
|
| 阶段三 | 任务 7:拆分 constants.ts | P2 | 30 分钟 |
|
||||||
|
| 阶段三 | 任务 8:清理过时文档 | P2 | 10 分钟 |
|
||||||
|
| 阶段四 | 任务 9:全面验证 | 必须 | 15 分钟 |
|
||||||
|
|
||||||
|
**总预计时间:** 约 2 小时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 风险与注意事项
|
||||||
|
|
||||||
|
1. **任务 7(拆分 constants)** 可能影响较多文件,建议最后执行
|
||||||
|
2. **任务 5(E2E 测试)** 需要先构建才能运行
|
||||||
|
3. 每个任务完成后立即 commit,便于回滚
|
||||||
|
4. 如遇阻塞,可跳过 P2 任务,优先完成 P0 和 P1
|
||||||
Reference in New Issue
Block a user