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

23 KiB
Raw Blame History

全模块测试覆盖实施计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: 补充所有未覆盖模块的测试,将测试覆盖率从42.57%提升到70%

Architecture: 采用TDD方法,优先补充核心业务模块测试,确保所有页面和API功能正常

Tech Stack: Jest + React Testing Library + TypeScript


📊 当前状态

当前覆盖率: 42.57%

未覆盖模块(0%):

  • 页面详情页:cases/[id], news/[slug], products/[id], services/[id], solutions
  • 管理后台:admin主页, content/[id], logs
  • 管理后台APIconfig, content/[id], logs, upload, users/[id]
  • 其他页面:privacy, terms, preview/effects
  • 组件:admin, analytics, effects, examples, seo

🎯 目标

覆盖率目标: 70%

任务总数: 25个

预计时间: 2-3周


Phase 1: 页面详情页测试(提升到55%)

Task 1: 案例详情页测试

Files:

  • Create: src/app/(marketing)/cases/[id]/page.test.tsx
  • Test: src/app/(marketing)/cases/[id]/page.tsx

Step 1: 查看案例详情页源码

Run:

cat src/app/\(marketing\)/cases/\[id\]/page.tsx | head -100

Expected: 显示案例详情页结构和功能

Step 2: 编写案例详情页测试

Create: src/app/(marketing)/cases/[id]/page.test.tsx

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:

npm run test:unit -- src/app/\(marketing\)/cases/\[id\]/page.test.tsx

Expected: 所有测试通过

Step 4: 提交代码

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:

cat src/app/\(marketing\)/news/\[slug\]/page.tsx | head -100

Expected: 显示新闻详情页结构和功能

Step 2: 编写新闻详情页测试

Create: src/app/(marketing)/news/[slug]/page.test.tsx

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:

npm run test:unit -- src/app/\(marketing\)/news/\[slug\]/page.test.tsx

Expected: 所有测试通过

Step 4: 提交代码

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:

cat src/app/admin/page.tsx | head -100

Expected: 显示管理后台主页结构和功能

Step 2: 编写管理后台主页测试

Create: src/app/admin/page.test.tsx

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:

npm run test:unit -- src/app/admin/page.test.tsx

Expected: 所有测试通过

Step 4: 提交代码

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:

cat src/app/api/admin/config/route.ts | head -100

Expected: 显示配置管理API结构和功能

Step 2: 编写配置管理API测试

Create: src/app/api/admin/config/route.test.ts

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:

npm run test:unit -- src/app/api/admin/config/route.test.ts

Expected: 所有测试通过

Step 4: 提交代码

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:

cat src/app/privacy/page.tsx | head -50

Expected: 显示隐私政策页结构和功能

Step 2: 编写隐私政策页测试

Create: src/app/privacy/page.test.tsx

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:

npm run test:unit -- src/app/privacy/page.test.tsx

Expected: 所有测试通过

Step 4: 提交代码

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:

cat src/app/preview/effects/page.tsx | head -100

Expected: 显示特效预览页结构和功能

Step 2: 编写特效预览页测试

Create: src/app/preview/effects/page.test.tsx

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:

npm run test:unit -- src/app/preview/effects/page.test.tsx

Expected: 所有测试通过

Step 4: 提交代码

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:

cat src/components/admin/RichTextEditor.tsx | head -100

Expected: 显示富文本编辑器组件结构和功能

Step 2: 编写富文本编辑器组件测试

Create: src/components/admin/RichTextEditor.test.tsx

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:

npm run test:unit -- src/components/admin/RichTextEditor.test.tsx

Expected: 所有测试通过

Step 4: 提交代码

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:

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

coverageThreshold: {
  global: {
    branches: 70,
    functions: 70,
    lines: 70,
    statements: 70
  }
}

Step 2: 更新CI/CD质量门禁

Modify: .woodpecker/quality-gate.yml

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: 提交配置

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: 提交文档

git add docs/TESTING_GUIDELINES.md
git commit -m "docs: add testing guidelines"

Task 25: 最终验收

Step 1: 运行完整测试套件

Run:

npm run test:unit -- --coverage

Expected: 覆盖率达到70%以上,所有测试通过

Step 2: 运行CI/CD流水线

Run:

npm run lint && npm run typecheck && npm run test:unit

Expected: 所有检查通过

Step 3: 生成最终报告

Run:

npm run test:unit -- --coverage --coverageReporters=html

Expected: 生成HTML覆盖率报告

Step 4: 提交最终完成标记

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. 测试规范文档完整

计划创建完成!