- 抽取 ConsoleShell 共享外壳(Layout/Header/Sider/主题切换/侧边栏折叠) - Sidebar 纯化为接受 menuItems prop 的展示组件 - Admin 管理台:/ 总览 + /projects 项目管理 - Workbench 工作台:/workbench/:projectId 项目作用域 - WorkbenchProjectGate 入口守卫(loading/error/archived/不存在拦截) - ProjectContext 提供当前项目上下文 - 项目管理表格 active 行增加'进入工作台'按钮 - 项目名称 trim 后最多 10 字符(前后端一致) - Workbench 总览页展示项目 Descriptions - Header 区分:管理台显示副标题,工作台显示项目名 + 返回管理台按钮 - 28/28 前端测试通过 - 文档更新:frontend.md ConsoleShell 规范、usage.md 双入口说明
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
import { screen, waitFor } from "@testing-library/react";
|
||
import { describe, expect, test } from "bun:test";
|
||
import { createElement } from "react";
|
||
|
||
import { App } from "../../../src/web/app";
|
||
import { renderWithProviders } from "../test-utils";
|
||
|
||
const ACTIVE_PROJECT = {
|
||
createdAt: "2024-01-01T00:00:00.000Z",
|
||
description: "",
|
||
id: "p1",
|
||
name: "活跃项目",
|
||
status: "active",
|
||
updatedAt: "2024-01-01T00:00:00.000Z",
|
||
};
|
||
|
||
const ARCHIVED_PROJECT = {
|
||
createdAt: "2024-01-01T00:00:00.000Z",
|
||
description: "",
|
||
id: "p2",
|
||
name: "归档项目",
|
||
status: "archived",
|
||
updatedAt: "2024-01-01T00:00:00.000Z",
|
||
};
|
||
|
||
function createMockHandler(projectList?: unknown[]) {
|
||
const handler = (input: RequestInfo | URL) => {
|
||
const url = input instanceof Request ? input.url : typeof input === "string" ? input : input.toString();
|
||
if (url.includes("/api/meta")) {
|
||
return new Response(
|
||
JSON.stringify({
|
||
ok: true,
|
||
service: "test-app",
|
||
timestamp: new Date().toISOString(),
|
||
version: "0.1.0",
|
||
}),
|
||
{ headers: { "Content-Type": "application/json" }, status: 200 },
|
||
);
|
||
}
|
||
if (url.includes("/api/projects")) {
|
||
const items = projectList ?? [];
|
||
return new Response(JSON.stringify({ items, page: 1, pageSize: 10, total: items.length }), {
|
||
headers: { "Content-Type": "application/json" },
|
||
status: 200,
|
||
});
|
||
}
|
||
return new Response(JSON.stringify({ error: "Not Found" }), {
|
||
status: 404,
|
||
});
|
||
};
|
||
const mocked = handler as unknown as typeof fetch;
|
||
globalThis.fetch = mocked;
|
||
window.fetch = mocked;
|
||
}
|
||
|
||
describe("ProjectsPage", () => {
|
||
test("渲染 Tab、搜索框、新建按钮和表格", async () => {
|
||
createMockHandler();
|
||
|
||
renderWithProviders(createElement(App), { initialRoute: "/projects" });
|
||
|
||
await waitFor(
|
||
() => {
|
||
expect(screen.getByText("进行中")).not.toBeNull();
|
||
},
|
||
{ timeout: 10000 },
|
||
);
|
||
expect(screen.getByText("已归档")).not.toBeNull();
|
||
expect(screen.getByText("新建项目")).not.toBeNull();
|
||
expect(screen.getByPlaceholderText("搜索项目名称或描述")).not.toBeNull();
|
||
});
|
||
|
||
test("新建按钮点击打开弹窗", async () => {
|
||
createMockHandler();
|
||
|
||
renderWithProviders(createElement(App), { initialRoute: "/projects" });
|
||
|
||
await waitFor(
|
||
() => {
|
||
expect(screen.getByText("进行中")).not.toBeNull();
|
||
},
|
||
{ timeout: 10000 },
|
||
);
|
||
|
||
const createBtn = screen.getByRole("button", { name: /新建项目/ });
|
||
createBtn.click();
|
||
|
||
await waitFor(
|
||
() => {
|
||
expect(document.body.querySelector(".ant-modal")).not.toBeNull();
|
||
},
|
||
{ timeout: 10000 },
|
||
);
|
||
});
|
||
|
||
test("active 项目行显示'进入工作台',archived 行不显示", async () => {
|
||
createMockHandler([ACTIVE_PROJECT, ARCHIVED_PROJECT]);
|
||
|
||
renderWithProviders(createElement(App), { initialRoute: "/projects" });
|
||
|
||
await waitFor(
|
||
() => {
|
||
expect(screen.queryByText("活跃项目")).not.toBeNull();
|
||
},
|
||
{ timeout: 10000 },
|
||
);
|
||
|
||
const enterBtns = screen.getAllByText("进入工作台");
|
||
expect(enterBtns.length).toBe(1);
|
||
|
||
const archivedRow = screen.getByText("归档项目").closest("tr");
|
||
expect(archivedRow?.textContent).not.toContain("进入工作台");
|
||
});
|
||
});
|