test(web): 添加 createListPageTests 工厂 — 6 类标准测试用例自动生成

This commit is contained in:
iven
2026-05-03 23:05:46 +08:00
parent c93ae0bc66
commit 37cdeebb95
2 changed files with 136 additions and 0 deletions

View File

@@ -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');
});
});

View File

@@ -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<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();
}
});
}