test: 测试体系全面优化,修复 Windows SQLite EBUSY 和前端产品缺陷
测试基础设施 - 统一 SQLite 测试 DB/临时目录 helper(tests/helpers.ts),支持 Windows EBUSY 重试清理 - 测试库使用 PRAGMA journal_mode=DELETE 避免 WAL 句柄延迟 - 路由 handler 测试改用 createMigratedMemoryTestDatabase 避免 File DB 锁 - SQLite 聚焦 --rerun-each=20 全部通过(720 pass) 后端测试补强 - 新增 tests/server/app.test.ts 真实 startServer 集成测试 - 覆盖 /api/meta、项目 CRUD、错误路径、静态 fallback、安全 header - bootstrap/logger 测试捕获预期输出,消除测试噪音 前端测试补强 - 移除 .ant-* 内部类名依赖,改为角色/文本/导航/请求契约断言 - 项目页补充搜索、Tab 切换、表单、表格操作、错误反馈行为测试 - 新增 hooks(use-theme-preference、use-sidebar-collapsed、use-projects)纯逻辑测试 - 新增 ErrorBoundary 错误展示和刷新按钮测试 - 新增搜索清空行为测试 - 测试 setup 过滤 antd/rc-trigger NaN height warning 产品修复(测试暴露) - 修复 ProjectToolbar 搜索框无法输入(新增 draftKeyword 状态) - 加固 ProjectFormModal 表单字段同步(useEffect 替代不可靠的 afterOpenChange) - 清理 ProjectFormModal 冗余 afterOpenChange 同步逻辑 重构与合规 - ProjectContext 拆分为三文件满足 React Fast Refresh 规则 - use-projects.ts 导出内部 helper 函数供测试验证 - scripts/build.ts 提取纯生成函数供测试使用,修复构建步骤日志编号 - 修复 build 测试覆盖真实生成逻辑 文档同步 - 更新后端/前端/开发文档测试规范、质量门禁和 helper 使用说明
This commit is contained in:
62
tests/web/components/ErrorBoundary.test.tsx
Normal file
62
tests/web/components/ErrorBoundary.test.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { App as AntApp, ConfigProvider } from "antd";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createElement } from "react";
|
||||
|
||||
import { ErrorBoundary } from "../../../src/web/components/ErrorBoundary";
|
||||
|
||||
function BrokenChild(): never {
|
||||
throw new Error("render failed");
|
||||
}
|
||||
|
||||
function captureConsoleError(callback: () => void): string[] {
|
||||
const originalError = console.error;
|
||||
const errors: string[] = [];
|
||||
console.error = (...args: unknown[]) => {
|
||||
errors.push(args.map(String).join(" "));
|
||||
};
|
||||
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
console.error = originalError;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
describe("ErrorBoundary", () => {
|
||||
test("子组件渲染失败后展示错误结果并隔离 console.error", () => {
|
||||
const errors = captureConsoleError(() => {
|
||||
render(
|
||||
createElement(
|
||||
ConfigProvider,
|
||||
null,
|
||||
createElement(AntApp, null, createElement(ErrorBoundary, null, createElement(BrokenChild))),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByText("渲染错误")).not.toBeNull();
|
||||
expect(screen.getByText("页面渲染出现异常,请刷新重试")).not.toBeNull();
|
||||
expect(screen.getByRole("button", { name: "刷新页面" })).not.toBeNull();
|
||||
expect(errors.some((line) => line.includes("渲染错误:"))).toBe(true);
|
||||
});
|
||||
|
||||
test("点击刷新页面按钮不会破坏错误兜底界面", () => {
|
||||
captureConsoleError(() => {
|
||||
render(
|
||||
createElement(
|
||||
ConfigProvider,
|
||||
null,
|
||||
createElement(AntApp, null, createElement(ErrorBoundary, null, createElement(BrokenChild))),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
captureConsoleError(() => fireEvent.click(screen.getByRole("button", { name: "刷新页面" })));
|
||||
}).not.toThrow();
|
||||
expect(screen.getByText("渲染错误")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,17 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import { fireEvent, screen } from "@testing-library/react";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createElement } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
|
||||
import { Sidebar } from "../../../../src/web/components/Sidebar";
|
||||
import { ADMIN_MENU_ITEMS } from "../../../../src/web/consoles/admin/menu";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
|
||||
function LocationProbe() {
|
||||
const location = useLocation();
|
||||
return <span>当前路径:{location.pathname}</span>;
|
||||
}
|
||||
|
||||
describe("Sidebar", () => {
|
||||
test("渲染 Admin 菜单项", () => {
|
||||
renderWithProviders(createElement(Sidebar, { menuItems: ADMIN_MENU_ITEMS }));
|
||||
@@ -14,23 +20,21 @@ describe("Sidebar", () => {
|
||||
expect(screen.getByText("项目管理")).not.toBeNull();
|
||||
});
|
||||
|
||||
test("项目管理菜单项可导航到 /projects", () => {
|
||||
renderWithProviders(createElement(Sidebar, { menuItems: ADMIN_MENU_ITEMS }), {
|
||||
initialRoute: "/projects",
|
||||
});
|
||||
test("点击项目管理菜单项导航到 /projects", () => {
|
||||
renderWithProviders(
|
||||
createElement("div", null, createElement(Sidebar, { menuItems: ADMIN_MENU_ITEMS }), createElement(LocationProbe)),
|
||||
);
|
||||
|
||||
const activeItem = document.querySelector(".ant-menu-item-selected");
|
||||
expect(activeItem).not.toBeNull();
|
||||
expect(activeItem?.textContent).toContain("项目管理");
|
||||
fireEvent.click(screen.getByText("项目管理"));
|
||||
|
||||
expect(screen.getByText("当前路径:/projects")).not.toBeNull();
|
||||
});
|
||||
|
||||
test("高亮当前路由对应的总览菜单项", () => {
|
||||
test("当前路由仍展示对应菜单项", () => {
|
||||
renderWithProviders(createElement(Sidebar, { menuItems: ADMIN_MENU_ITEMS }), {
|
||||
initialRoute: "/",
|
||||
});
|
||||
|
||||
const activeItem = document.querySelector(".ant-menu-item-selected");
|
||||
expect(activeItem).not.toBeNull();
|
||||
expect(activeItem?.textContent).toContain("总览");
|
||||
expect(screen.getByText("总览")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user