Files
hms/apps/web/src/test/factories/listPageTests.tsx

129 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, unknown>[];
/** 额外测试用例 */
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<string, unknown>[], 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(<Component />, { route });
await waitFor(() => {
const table = document.querySelector('.ant-table');
expect(table).toBeInTheDocument();
});
});
it('表格包含正确的列名', async () => {
setupMock(config.mockItems);
renderWithProviders(<Component />, { 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(<Component />, { route });
await waitFor(() => {
const pagination = document.querySelector('.ant-pagination');
expect(pagination).toBeInTheDocument();
});
});
}
if (hasCreateButton) {
it('显示新建按钮', async () => {
setupMock(config.mockItems);
renderWithProviders(<Component />, { route });
await waitFor(() => {
const btn = screen.getByRole('button', { name: new RegExp(createButtonText) });
expect(btn).toBeInTheDocument();
});
});
}
if (hasSearch) {
it('搜索/筛选区域存在', async () => {
setupMock(config.mockItems);
renderWithProviders(<Component />, { 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();
}
});
}