Files
hms/docs/superpowers/specs/2026-05-03-web-page-component-testing-design.md
iven 1602b7bbad
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
docs(wiki): Wiki 全面刷新 + Q2 路线图 + 测试补强设计规格
- Wiki 7 文件关键数字刷新:迁移 96→103、实体 45→46、前端 163→225、测试 5→36
- 修复 architecture.md PostgreSQL 版本不一致(18→16)
- 修复 erp-ai.md 实体数 3→6、erp-health.md 实体数 45→46
- 更新 index.md 文档索引:specs 41、plans 38、discussions 18
- 新增事件注册表/方法论/分析报告引用
- 新增页面/组件测试设计规格(模式化工厂方案)
- 新增 Q2 路线图规格(技术债 + 新功能并行 8 周)
2026-05-03 22:33:08 +08:00

17 KiB
Raw Blame History

Web 前端页面/组件全面测试设计规格

日期: 2026-05-03 | 类型: 设计规格 | 状态: 定稿

目标: 为 HMS Web 前端页面/组件建立全面测试覆盖,从当前 ~3% 页面覆盖率提升到 85%(约 85/99 个页面有测试文件)。

1. 背景与动机

为什么做

2026-05-03 深度分析报告中4 个专家组独立将「前端测试空白」标记为最大质量风险。虽然此后的工作已完成 Store 测试6 个)和 API 契约测试25 个),但 99 个页面/组件中仅有 1 个StatusTag有测试

页面级测试的缺失意味着:

  • 任何重构都无法验证不引入回归
  • 错误处理统一化18 文件)已执行但无测试保障
  • 大组件拆分5 个 500+ 行文件)因无安全网而风险过高

范围

  • 覆盖: Web 前端 99 个页面/组件 TSX 文件
  • 不含: Store 测试已完成、API 契约测试已完成、E2E 测试(独立维护)
  • 工具: Vitest + @testing-library/react + msw

2. 页面模式分类

分析 99 个页面组件后识别出 4 种核心交互模式:

模式 1列表页ListPage— ~50 个页面

特征: Ant Design Table + FilterBar + 分页 + 新建/编辑弹窗

示例: PatientList, AppointmentList, AlertList, DoctorList, Users, Roles, Organizations, Messages, ArticleManageList, PointsProductList, PointsOrderList, PointsRuleList, AlertRuleList, FollowUpTaskList, FollowUpRecordList, FollowUpTemplateList, ConsultationList, DeviceManage, DialysisManageList, OfflineEventList, DictionaryManager, MenuConfig, NumberingRules, PluginAdmin, PluginMarket, AuditLogViewer, PendingTasks, CompletedTasks, ProcessDefinitions, InstanceMonitor

测试要点:

  1. 初始渲染调用列表 API
  2. 表格渲染正确的列名
  3. 分页翻页传递正确参数
  4. 筛选器传递正确参数
  5. 新建按钮可点击
  6. 编辑按钮可点击

模式 2详情页DetailPage— ~15 个页面

特征: 多 Tab 切换 + 子组件按 Tab 加载

示例: PatientDetail, ConsultationDetail, PatientDetail 内的 Tab 组件VitalSignsTab, LabReportsTab, FollowUpTab, FamilyMembersTab, HealthRecordsTab, DeviceReadingsTab, PointsAccountTab, DailyMonitoringTab, AiSuggestionTab

测试要点:

  1. URL 参数正确传递
  2. Tab 切换加载对应数据
  3. 返回导航正常

模式 3表单页FormPage— ~10 个页面

特征: 独立表单页面 + 提交/保存

示例: ArticleEditor, Login, ArticleCategoryManage, ArticleTagManage, PatientTagManage, ChangePassword, ThemeSettings, LanguageManager, SystemSettings

测试要点:

  1. 表单字段正确渲染
  2. 必填校验触发
  3. 提交调用正确 API
  4. 成功后正确跳转或提示

模式 4仪表盘页DashboardPage— ~10 个页面

特征: 统计卡片 + 图表 + 数据聚合

示例: Home, StatisticsDashboard, AlertDashboard, AiUsageDashboard, AiAnalysisList, AiPromptList, AiUsageDashboard, DashboardWidgets

测试要点:

  1. 统计 API 正确调用
  2. 数据渲染到卡片
  3. 时间范围切换传参正确

不归类 — ~14 个复杂页面

PluginCRUDPage, PluginDashboardPage, PluginGraphPage, PluginKanbanPage, PluginTabsPage, PluginTreePage, ProcessDesigner, ProcessViewer, Settings容器页, Workflow容器页, CalendarView, VitalSignsChart, ImagePreview, AlertDetailPanel

这些页面需要独立手写测试。

3. 技术架构

3.1 工具链

vitest 4 + @testing-library/react 16 + msw 2

选择 msw 的理由:

  • 拦截层在网络层,与 axios/fetch 解耦
  • API mock handlers 可与已有 API 契约测试共享
  • 支持真实 HTTP 状态码和错误场景模拟
  • 不需要在每个测试文件中手动 jest.fn()

3.2 文件结构

apps/web/src/test/
├── setup.ts                     # 已有jsdom setup + @testing-library/jest-dom
├── factories/
│   ├── listPageTests.ts         # createListPageTests() — 列表页测试工厂
│   ├── detailPageTests.ts       # createDetailPageTests() — 详情页测试工厂
│   ├── formPageTests.ts         # createFormPageTests() — 表单页测试工厂
│   └── dashboardPageTests.ts    # createDashboardPageTests() — 仪表盘测试工厂
├── helpers/
│   ├── renderWithProviders.tsx   # Router + Zustand Store + AntD ConfigProvider 包裹
│   ├── mswServer.ts             # msw server setup/teardown
│   ├── apiHandlers.ts           # 通用 API mock handlers与契约测试共享
│   └── testFixtures.ts          # 测试数据工厂(患者、医生、预约等)

3.3 工厂函数接口

// 列表页测试工厂
interface ListPageTestConfig {
  component: React.ComponentType;
  apiUrl: string;
  columns: string[];
  filters?: Array<{ name: string; type: 'select' | 'input' | 'date' }>;
  hasCreate?: boolean;
  hasEdit?: boolean;
  hasDelete?: boolean;
  mockData: Record<string, unknown>[];
}

function createListPageTests(config: ListPageTestConfig): void;

// 详情页测试工厂
interface DetailPageTestConfig {
  component: React.ComponentType;
  detailApiUrl: string;
  tabs: Array<{ label: string; apiUrl: string }>;
  mockDetail: Record<string, unknown>;
}

function createDetailPageTests(config: DetailPageTestConfig): void;

// 表单页测试工厂
interface FormPageTestConfig {
  component: React.ComponentType;
  submitApiUrl: string;
  submitMethod: 'POST' | 'PUT';
  requiredFields: string[];
  mockInitialValues?: Record<string, unknown>;
}

function createFormPageTests(config: FormPageTestConfig): void;

// 仪表盘测试工厂
interface DashboardTestConfig {
  component: React.ComponentType;
  statsApis: Array<{ url: string; responseKey: string }>;
  hasDateRangePicker?: boolean;
}

function createDashboardPageTests(config: DashboardTestConfig): void;

3.4 renderWithProviders

// 统一包裹组件,模拟真实运行环境
function renderWithProviders(
  ui: React.ReactElement,
  options?: {
    initialRoute?: string;
    preloadedState?: Record<string, unknown>;
    userRole?: 'admin' | 'doctor' | 'nurse';
  }
): {
  ...RenderResult;
  store: ReturnType<typeof useStore>;
};

包裹内容:

  • <MemoryRouter> — 模拟路由
  • Zustand store 重置 + 预填充 — Zustand v5 不使用 Provider通过 zustand/testing 或直接 store.setState 注入测试状态
  • Ant Design <ConfigProvider> — 中文 locale + 主题(注意 antd v6 的 Modal 默认渲染到 document.body需设置 getContainer 或使用 container.baseElement

4. 执行策略

4.1 分批计划

第一批(~20 个测试文件):健康管理核心流程

优先覆盖患者和预约管理,这是所有业务的基础。

工厂开发 + 核心页面:

页面 模式 说明
PatientList ListPage 患者列表 — 验证工厂
PatientDetail DetailPage 患者详情 — 验证工厂
AppointmentList ListPage 预约列表
FollowUpTaskList ListPage 随访任务
FollowUpRecordList ListPage 随访记录
AlertList ListPage 告警列表
AlertDashboard DashboardPage 告警仪表盘
ConsultationList ListPage 咨询列表
ConsultationDetail DetailPage 咨询详情
DoctorList ListPage 医生列表
DoctorSchedule FormPage 医生排班
VitalSignsTab DetailPage(sub) 体征 Tab
LabReportsTab DetailPage(sub) 化验 Tab
StatusTag 已有 补充更多场景
PatientSelect FormPage(sub) 患者选择器
DoctorSelect FormPage(sub) 医生选择器
FilterBar 组件测试 筛选栏
ExportButton 组件测试 导出按钮
AiSuggestionTab DetailPage(sub) AI 建议 Tab
HealthRecordsTab DetailPage(sub) 健康记录 Tab

第二批(~30 个测试文件):其余健康管理 + 基础模块

页面数 模式
积分系统 3 (PointsProductList, PointsOrderList, PointsRuleList) ListPage
内容管理 4 (ArticleManageList, ArticleEditor, ArticleCategoryManage, ArticleTagManage) ListPage + FormPage
设备 + 透析 + 线下活动 3 ListPage
基础Users, Roles, Organizations, Messages 4 ListPage
Login 1 FormPage
健康管理剩余 ~14 混合

第三批(~20 个测试文件):设置 + 工作流 + 插件 + 复杂页面

页面数 说明
设置页面 8 ListPage + FormPage
工作流 6 混合ProcessDesigner 手写)
插件 7 混合(多个需手写)
仪表盘 3 DashboardPage
复杂页面手写 ~14 独立测试

4.2 覆盖率目标

阶段 新增测试文件 累计测试文件 页面覆盖率
当前 0 36 ~3% (1/99)
第一批完成 +20 56 ~21%
第二批完成 +30 86 ~51%
第三批完成 +20 106 ~85%(含手写 14

页面覆盖率 = 有测试文件的页面数 / 总页面数99 目标 85/99 = 85%。

4.3 工厂优先开发顺序

  1. createListPageTests — 覆盖最多页面(~50 个),第一个开发
  2. renderWithProviders + mswServer — 所有工厂的基础
  3. createDetailPageTests — 覆盖 ~15 个页面
  4. createFormPageTests — 覆盖 ~10 个页面
  5. createDashboardPageTests — 覆盖 ~10 个页面

每个工厂开发后,用第一批中的 2-3 个页面验证设计,确认可行后再推广。

5. 验证标准

工厂验证

  • 每个工厂至少用 3 个不同页面验证
  • 工厂生成的测试在 CI 中通过
  • pnpm test:coverage 输出可查看覆盖率

阶段验收

  • 第一批20 个测试文件通过PatientList + AppointmentList 完整覆盖
  • 第二批:累计 86 个测试文件通过
  • 第三批:累计 106 个测试文件通过,页面覆盖率 85%

不做

  • 不追求 100% 行覆盖率 — 目标是页面覆盖率 85%
  • 不测 Ant Design 组件内部行为 — 只测我们的业务逻辑
  • 不建立快照测试 — 价值低
  • 不测 CSS 样式 — 由视觉回归测试覆盖

6. 风险与缓解措施

风险 1工厂函数无法覆盖同模式内的变异性

50 个列表页虽然共享 Table + FilterBar 模式,但 API 调用方式、筛选器类型、自定义操作按钮差异较大。如果工厂配置接口不足,会退化为逐个手写。

缓解: 开发 ListPage 工厂后,立即用差异最大的 3 个页面PatientList 复杂筛选、AlertList 自定义操作、Users 简单列表)验证。如果配置接口不足,先调整工厂接口再推广。

风险 2Ant Design v6 在 jsdom 下的兼容性

antd 的 Modal/Drawer 默认渲染到 document.body,超出 testing-library 的默认查询范围。Select/DatePicker 等组件的弹出层在 jsdom 中行为可能不同。

缓解:renderWithProviders 中设置 container.baseElement: document.body;对 Modal 组件使用 attachTo 配置;必要时为 antd 弹窗组件编写自定义查询函数。

风险 3Zustand v5 测试隔离

Zustand v5 移除了 <Provider>store 隔离方式变化。renderWithProviders 需要使用 store.setState() 直接注入状态而非 Provider 包裹。

缓解: 项目已有 Store 测试6 个)验证了 Zustand v5 的测试模式,沿用相同方法。

风险 4测试运行时间增长

99 个页面测试 + msw 拦截会显著增加 CI 运行时间。

缓解: Vitest 默认并行运行;对慢测试标记 @slow 隔离运行CI 中使用 vitest --reporter=verbose 配合覆盖率阈值门控(不达标则失败)。

附录99 个页面/组件完整分类

列表页模式ListPage— 42 个

# 页面文件
1 PatientList health
2 AppointmentList health
3 DoctorList health
4 AlertList health
5 AlertRuleList health
6 FollowUpTaskList health
7 FollowUpRecordList health
8 FollowUpTemplateList health
9 ConsultationList health
10 ArticleManageList health
11 PointsProductList health
12 PointsOrderList health
13 PointsRuleList health
14 DeviceManage health
15 DialysisManageList health
16 OfflineEventList health
17 Users auth
18 Roles auth
19 Organizations auth
20 Messages message
21 DictionaryManager config
22 MenuConfig config
23 NumberingRules config
24 AuditLogViewer config
25 PluginAdmin plugin
26 PluginMarket plugin
27 PendingTasks workflow
28 CompletedTasks workflow
29 ProcessDefinitions workflow
30 InstanceMonitor workflow
31 AiAnalysisList ai
32 AiPromptList ai
33 ActionInbox health
34 StatisticsDashboard health
35 ArticleCategoryManage health
36 ArticleTagManage health
37 PatientTagManage health
38 ThemeSettings config
39 LanguageManager config
40 SystemSettings config
41 ChangePassword auth
42 MessageTemplates message

详情页模式DetailPage— 17 个

# 页面/组件文件
1 PatientDetail health
2 ConsultationDetail health
3 VitalSignsTab health/component
4 LabReportsTab health/component
5 FollowUpTab health/component
6 FamilyMembersTab health/component
7 HealthRecordsTab health/component
8 DeviceReadingsTab health/component
9 PointsAccountTab health/component
10 DailyMonitoringTab health/component
11 AiSuggestionTab health/component
12 AlertDetailPanel health/component
13 ActionDetailDrawer health/workbench
14 TaskDetail health/workbench
15 TaskQueue health/workbench
16 TodoList health/workbench
17 TeamOverviewPanel health/workbench

表单页模式FormPage— 10 个

# 页面文件
1 ArticleEditor health
2 Login auth
3 DoctorSchedule health
4 NotificationPreferences message
5 NotificationList message
6 DashboardWidgets dashboard
7 ProcessViewer workflow
8 DoctorWorkbench health/workbench
9 OperatorWorkbench health/workbench
10 AdminDashboard health/workbench

仪表盘模式DashboardPage— 10 个

# 页面文件
1 Home core
2 AlertDashboard health
3 AiUsageDashboard ai
4 AiInsightPanel health/workbench
5 AdminDashboard (StatisticsDashboard) health/stats
6 DoctorDashboard (StatisticsDashboard) health/stats
7 HealthDataCenter (StatisticsDashboard) health/stats
8 NurseDashboard (StatisticsDashboard) health/stats
9 OperatorDashboard (StatisticsDashboard) health/stats
10 DashboardWidgets dashboard

手写测试 — 20 个复杂页面

# 页面文件 复杂度原因
1 PluginCRUDPage plugin 动态表单 + 插件数据
2 PluginDashboardPage plugin 动态图表
3 PluginGraphPage plugin @xyflow/react 图编辑
4 PluginKanbanPage plugin 拖拽看板
5 PluginTabsPage plugin 动态 Tab
6 PluginTreePage plugin 树形结构
7 ProcessDesigner workflow BPMN 设计器
8 CalendarView health/component 日历交互
9 VitalSignsChart health/component ECharts 图表
10 ImagePreview health/component 图片预览
11 PatientSelect health/component 异步搜索选择器
12 DoctorSelect health/component 异步搜索选择器
13 FilterBar component 通用筛选组件
14 ExportButton component 文件导出
15 StatusTag health/component 已有,需补充
16 Settings core 容器页面
17 Workflow core 容器页面
18 PluginCRUDPageInner plugin CRUD 内部逻辑
19 DetailDrawer plugin 抽屉详情
20 ImportModal plugin 导入弹窗

排除(非页面组件)

  • dashboardConstants.tsx — 常量定义文件,不含 React 组件

分类合计: 42 列表 + 17 详情 + 10 表单 + 10 仪表盘 + 20 手写 = 99。页面覆盖率目标 = (99 - 14 未覆盖) / 99 ≈ 85%。

预估工时

阶段 内容 预估人时 说明
基础设施 renderWithProviders + mswServer + testFixtures 4h 一次性投入
工厂开发 4 个工厂函数 12h 每个工厂 3h
第一批 20 个页面测试 12h 工厂模式,每个 ~0.6h
第二批 30 个页面测试 18h 工厂模式
第三批 20 个手写 + 17 个工厂 24h 手写页面耗时较高
总计 ~70h 约 2 周全职工作