import { describe, expect, test } from "bun:test"; import { fireEvent, screen, waitFor } from "@testing-library/react"; import { createElement } from "react"; import type { Project } from "../../../../src/shared/api"; import { InboxPage } from "../../../../src/web/features/inbox"; import { ProjectContext } from "../../../../src/web/shared/hooks/use-current-project"; import { installFetchMock, jsonResponse, renderWithProviders } from "../../test-utils"; const MOCK_PROJECT: Project = { createdAt: "2026-01-01T00:00:00.000Z", description: "", id: "project-1", name: "测试项目", status: "active", updatedAt: "2026-01-01T00:00:00.000Z", }; const EMPTY_LIST = { items: [], page: 1, pageSize: 20, total: 0 }; function makeMaterial(overrides: Partial<{ description: string; id: string }> = {}) { return { associatedDate: "2026-06-03", createdAt: "2026-06-03T00:00:00.000Z", description: overrides.description ?? "测试素材", id: overrides.id ?? "mat-1", projectId: "project-1", status: "pending", updatedAt: "2026-06-03T00:00:00.000Z", }; } function renderInboxPage() { return renderWithProviders( createElement(ProjectContext.Provider, { children: createElement(InboxPage), value: MOCK_PROJECT, }), ); } describe("InboxPage", () => { test("初始状态显示空列表和空详情", async () => { const calls = installFetchMock((call) => { if (call.url.includes("/materials")) return jsonResponse(EMPTY_LIST); return jsonResponse({}); }); renderInboxPage(); await waitFor(() => { expect(screen.getByText("暂无素材")).not.toBeNull(); }); expect(screen.getByText("请在左侧选择素材")).not.toBeNull(); const listCall = calls.find((c) => c.url.includes("/materials") && c.method === "GET"); expect(listCall).toBeDefined(); }); test("新增素材后列表追加且自动选中", async () => { const createdId = "mat-new"; const created = makeMaterial({ description: "新增的素材", id: createdId }); installFetchMock((call) => { if (call.method === "POST" && call.url.includes("/materials")) { return jsonResponse({ material: created }, { status: 201 }); } if (call.method === "GET" && call.url.includes("/materials/" + createdId)) { return jsonResponse({ material: created }); } if (call.method === "GET" && call.url.includes("/materials")) { return jsonResponse({ ...EMPTY_LIST, items: [created], total: 1 }); } return jsonResponse({}); }); renderInboxPage(); await waitFor(() => { expect(screen.getByRole("button", { name: /新增素材/ })).not.toBeNull(); }); fireEvent.click(screen.getByRole("button", { name: /新增素材/ })); await waitFor(() => { expect(screen.getByText("新增素材", { selector: ".ant-modal-title" })).not.toBeNull(); }); const textarea = screen.getByPlaceholderText("请输入素材描述"); fireEvent.change(textarea, { target: { value: "新增的素材" } }); fireEvent.click(screen.getByText("确 定")); await waitFor(() => { const cards = screen.getAllByText("新增的素材"); expect(cards.length).toBeGreaterThanOrEqual(1); }); }); test("删除素材后列表更新", async () => { let deleted = false; const material = makeMaterial({ description: "待删除的素材", id: "mat-del" }); installFetchMock((call) => { if (call.method === "DELETE" && call.url.includes("/materials/" + material.id)) { deleted = true; return new Response(null, { status: 204 }); } if (call.method === "GET" && call.url.includes("/materials")) { if (deleted) return jsonResponse(EMPTY_LIST); return jsonResponse({ ...EMPTY_LIST, items: [material], total: 1 }); } return jsonResponse({}); }); renderInboxPage(); await waitFor(() => { expect(screen.getByText("待删除的素材")).not.toBeNull(); }); fireEvent.click(screen.getByLabelText("删除")); await waitFor(() => { expect(screen.getByText("确认删除该素材?")).not.toBeNull(); }); fireEvent.click(screen.getByText("删 除")); await waitFor(() => { expect(screen.getByText("暂无素材")).not.toBeNull(); }); }); });