- 新增 materials 表(id/projectId/description/associatedDate/status/createdAt/updatedAt) - 新增 4 个后端 API 路由(list/create/get/delete)+ 13 个测试 - 新增 use-materials hooks(TanStack Query) - 收集箱页面重构为三层架构(InboxPage + MaterialSidebar + MaterialDetailPanel) - MaterialCard: Popconfirm 删除确认 + 粗粒度时间格式 - MaterialContent: 展示状态标签 + createdAt - 更新开发文档 backend.md / frontend.md
137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
import { describe, expect, test } from "bun:test";
|
|
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 = {
|
|
archivedAt: null,
|
|
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();
|
|
});
|
|
});
|
|
});
|