refactor: 统一管理页面布局 — FilterToolbar + usePageSearchParams + parseListParams

This commit is contained in:
2026-06-04 17:25:36 +08:00
parent 61b479e2be
commit 6f547560d1
40 changed files with 1805 additions and 628 deletions

View File

@@ -310,8 +310,9 @@ describe("ModelListPage", () => {
renderWithProviders(createElement(App), { initialRoute: "/models" });
await waitFor(() => expect(screen.getByText("GPT-4o")).not.toBeNull());
fireEvent.change(screen.getByPlaceholderText("搜索模型名称或 ID"), { target: { value: "gpt" } });
fireEvent.click(screen.getByRole("button", { name: /搜\s*索/ }));
const input = screen.getByPlaceholderText("搜索模型名称或 ID");
fireEvent.change(input, { target: { value: "gpt" } });
fireEvent.keyDown(input, { key: "Enter" });
await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=gpt"))).toBe(true));
}, 15000);

View File

@@ -114,7 +114,7 @@ function LocationProbe() {
}
describe("ProjectsPage", () => {
test("渲染项目管理入口并按状态请求项目列表", async () => {
test("渲染项目管理入口并展示项目列表", async () => {
const calls = createProjectFetchMock();
renderWithProviders(createElement(App), { initialRoute: "/projects" });
@@ -123,27 +123,34 @@ describe("ProjectsPage", () => {
expect(screen.getByText("活跃项目")).not.toBeNull();
});
expect(screen.getByText("归档")).not.toBeNull();
expect(screen.getByText("归档项目")).not.toBeNull();
expect(screen.getByRole("button", { name: /新建项目/ })).not.toBeNull();
expect(screen.getByPlaceholderText("搜索名称或描述")).not.toBeNull();
expect(calls.some((call) => call.url.includes("status=active"))).toBe(true);
expect(calls.filter((call) => !call.url.includes("/api/meta")).length).toBeGreaterThan(0);
});
test("搜索和切换 Tab 会更新请求参数与用户可见结果", async () => {
test("搜索和状态筛选会更新请求参数与用户可见结果", async () => {
const calls = createProjectFetchMock();
renderWithProviders(createElement(App), { initialRoute: "/projects" });
await waitFor(() => expect(screen.getByText("活跃项目")).not.toBeNull());
fireEvent.change(screen.getByPlaceholderText("搜索名称或描述"), { target: { value: "归档" } });
fireEvent.click(screen.getByRole("button", { name: /搜\s*索/ }));
const searchInput1 = screen.getByPlaceholderText("搜索名称或描述");
fireEvent.change(searchInput1, { target: { value: "归档" } });
fireEvent.keyDown(searchInput1, { key: "Enter" });
await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=%E5%BD%92%E6%A1%A3"))).toBe(true));
fireEvent.click(screen.getByText("已归档"));
const statusLabels = screen.getAllByText("状态");
const selectLabel = statusLabels.find((el) => el.closest(".ant-select"));
if (selectLabel) fireEvent.mouseDown(selectLabel);
await waitFor(() => {
const archivedOptions = screen.getAllByText("已归档");
const dropdownOption = archivedOptions.find((el) => el.closest(".ant-select-item"));
if (dropdownOption) fireEvent.click(dropdownOption);
});
await waitFor(() => expect(calls.some((call) => call.url.includes("status=archived"))).toBe(true));
await waitFor(() => expect(screen.getByText("归档项目")).not.toBeNull());
expect(calls.some((call) => call.url.includes("keyword=%E5%BD%92%E6%A1%A3"))).toBe(true);
expect(calls.some((call) => call.url.includes("status=archived"))).toBe(true);
});
test("清空搜索条件复位请求参数并重新展示全部项目", async () => {
@@ -152,12 +159,19 @@ describe("ProjectsPage", () => {
renderWithProviders(createElement(App), { initialRoute: "/projects" });
await waitFor(() => expect(screen.getByText("活跃项目")).not.toBeNull());
fireEvent.change(screen.getByPlaceholderText("搜索名称或描述"), { target: { value: "归档" } });
fireEvent.click(screen.getByRole("button", { name: /搜\s*索/ }));
const searchInput2 = screen.getByPlaceholderText("搜索名称或描述");
fireEvent.change(searchInput2, { target: { value: "归档" } });
fireEvent.keyDown(searchInput2, { key: "Enter" });
await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=%E5%BD%92%E6%A1%A3"))).toBe(true));
fireEvent.change(screen.getByPlaceholderText("搜索名称或描述"), { target: { value: "" } });
fireEvent.click(screen.getByRole("button", { name: /搜\s*索/ }));
const searchInput3 = screen.getByPlaceholderText("搜索名称或描述");
const clearButton = searchInput3.closest(".ant-input-search")?.querySelector(".ant-input-clear-icon");
if (clearButton) fireEvent.click(clearButton);
await waitFor(() => {
const lastProjectCall = [...calls].reverse().find((call) => call.url.includes("/api/projects"));
expect(lastProjectCall && !lastProjectCall.url.includes("keyword=")).toBe(true);
});
await waitFor(() => expect(screen.getByText("活跃项目")).not.toBeNull());
});
@@ -249,13 +263,12 @@ describe("ProjectsPage", () => {
data: { items: [ACTIVE_PROJECT, ARCHIVED_PROJECT], page: 1, pageSize: 20, total: 2 },
loading: false,
onArchive,
onChange: () => undefined,
onDelete,
onEdit: () => undefined,
onPageChange: () => undefined,
onRestore,
page: 1,
pageSize: 20,
status: "active",
}),
),
);

View File

@@ -205,8 +205,9 @@ describe("ProviderListPage", () => {
renderWithProviders(createElement(App), { initialRoute: "/models/providers" });
await waitFor(() => expect(screen.getAllByText("OpenAI").length).toBeGreaterThan(0));
fireEvent.change(screen.getByPlaceholderText("搜索供应商名称"), { target: { value: "Open" } });
fireEvent.click(screen.getByRole("button", { name: /搜\s*索/ }));
const input = screen.getByPlaceholderText("搜索供应商名称");
fireEvent.change(input, { target: { value: "Open" } });
fireEvent.keyDown(input, { key: "Enter" });
await waitFor(() => expect(calls.some((call) => call.url.includes("keyword=Open"))).toBe(true));
}, 15000);