import { describe, it, expect } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { server } from '../mocks/server'; import type { ComponentType } from 'react'; import { renderWithProviders } from '../utils/renderWithProviders'; export interface ListPageTestConfig { /** 页面组件 */ Component: ComponentType; /** 列表 API 路径(如 '/api/v1/health/patients') */ apiPath: string; /** 表格列名数组 — 用于验证表头 */ columns: string[]; /** 第一条 mock 数据中会被渲染到表格的文本 */ firstRowTexts: string[]; /** mock 数据总条数 */ totalItems: number; /** 是否有「新建」按钮 */ hasCreateButton?: boolean; /** 新建按钮文本(默认 "新建") */ createButtonText?: string; /** 是否有搜索/筛选 */ hasSearch?: boolean; /** 是否有分页(默认 true) */ hasPagination?: boolean; /** 自定义路由(默认 '/') */ route?: string; /** 自定义 mock 数据(默认使用空数组 + totalItems) */ mockItems?: Record[]; /** 额外测试用例 */ extraTests?: () => void; } export function createListPageTests(config: ListPageTestConfig) { const { Component, apiPath, columns, totalItems, hasCreateButton = true, createButtonText = '新建', hasSearch = true, hasPagination = true, route = '/', extraTests, } = config; describe(`${Component.displayName || Component.name || 'ListPage'}`, () => { function setupMock(items?: Record[], total?: number) { const mockData = items ?? config.mockItems ?? []; const mockTotal = total ?? totalItems; server.use( http.get(apiPath, () => HttpResponse.json({ success: true, data: { data: mockData, total: mockTotal, page: 1, page_size: 20, total_pages: Math.ceil(mockTotal / 20) }, }), ), ); } it('渲染加载状态并显示数据', async () => { setupMock(config.mockItems); renderWithProviders(, { route }); await waitFor(() => { const table = document.querySelector('.ant-table'); expect(table).toBeInTheDocument(); }); }); it('表格包含正确的列名', async () => { setupMock(config.mockItems); renderWithProviders(, { route }); await waitFor(() => { const headers = document.querySelectorAll('th'); const headerTexts = Array.from(headers).map((h) => h.textContent?.trim()); for (const col of columns) { expect(headerTexts.some((t) => t?.includes(col))).toBe(true); } }); }); if (hasPagination && totalItems > 20) { it('分页器显示正确的总数', async () => { setupMock(config.mockItems, totalItems); renderWithProviders(, { route }); await waitFor(() => { const pagination = document.querySelector('.ant-pagination'); expect(pagination).toBeInTheDocument(); }); }); } if (hasCreateButton) { it('显示新建按钮', async () => { setupMock(config.mockItems); renderWithProviders(, { route }); await waitFor(() => { const btn = screen.getByRole('button', { name: new RegExp(createButtonText) }); expect(btn).toBeInTheDocument(); }); }); } if (hasSearch) { it('搜索/筛选区域存在', async () => { setupMock(config.mockItems); renderWithProviders(, { route }); await waitFor(() => { const table = document.querySelector('.ant-table'); expect(table).toBeInTheDocument(); }); const inputs = document.querySelectorAll('input, .ant-select, .ant-picker'); expect(inputs.length).toBeGreaterThan(0); }); } if (extraTests) { extraTests(); } }); }