129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
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();
|
||
}
|
||
});
|
||
}
|