From 37cdeebb95eb56f9b2772001e2cd5de78623b733 Mon Sep 17 00:00:00 2001 From: iven Date: Sun, 3 May 2026 23:05:46 +0800 Subject: [PATCH] =?UTF-8?q?test(web):=20=E6=B7=BB=E5=8A=A0=20createListPag?= =?UTF-8?q?eTests=20=E5=B7=A5=E5=8E=82=20=E2=80=94=206=20=E7=B1=BB?= =?UTF-8?q?=E6=A0=87=E5=87=86=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/factories/listPageTests.test.tsx | 8 ++ apps/web/src/test/factories/listPageTests.tsx | 128 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 apps/web/src/test/factories/listPageTests.test.tsx create mode 100644 apps/web/src/test/factories/listPageTests.tsx diff --git a/apps/web/src/test/factories/listPageTests.test.tsx b/apps/web/src/test/factories/listPageTests.test.tsx new file mode 100644 index 0000000..706673d --- /dev/null +++ b/apps/web/src/test/factories/listPageTests.test.tsx @@ -0,0 +1,8 @@ +import { describe, it, expect } from 'vitest'; +import { createListPageTests } from './listPageTests'; + +describe('createListPageTests', () => { + it('is a function that returns a describe block', () => { + expect(typeof createListPageTests).toBe('function'); + }); +}); diff --git a/apps/web/src/test/factories/listPageTests.tsx b/apps/web/src/test/factories/listPageTests.tsx new file mode 100644 index 0000000..1363b4c --- /dev/null +++ b/apps/web/src/test/factories/listPageTests.tsx @@ -0,0 +1,128 @@ +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(); + } + }); +}