feat(workbench): 新增收集箱页面 — 素材列表/详情分栏布局 + 新增/选中/删除 mock 交互

This commit is contained in:
2026-06-03 08:36:38 +08:00
parent 83349bf01b
commit 2cdbe474ce
15 changed files with 607 additions and 1 deletions

View File

@@ -0,0 +1,77 @@
import { fireEvent, screen, waitFor } from "@testing-library/react";
import { describe, expect, test, vi } from "bun:test";
import { createElement } from "react";
import type { Material } from "../../../../src/web/features/inbox/types";
import { AddMaterialModal } from "../../../../src/web/features/inbox/components/AddMaterialModal";
import { renderWithProviders } from "../../test-utils";
describe("AddMaterialModal", () => {
test("打开时渲染表单字段", () => {
renderWithProviders(
createElement(AddMaterialModal, {
onAdd: vi.fn(),
onOpenChange: vi.fn(),
open: true,
}),
);
expect(screen.getByText("新增素材")).not.toBeNull();
expect(screen.getByText("描述")).not.toBeNull();
expect(screen.getByText("关联时间")).not.toBeNull();
});
test("关闭时不渲染", () => {
renderWithProviders(
createElement(AddMaterialModal, {
onAdd: vi.fn(),
onOpenChange: vi.fn(),
open: false,
}),
);
expect(screen.queryByText("新增素材")).toBeNull();
});
test("提交空表单显示必填校验", async () => {
renderWithProviders(
createElement(AddMaterialModal, {
onAdd: vi.fn(),
onOpenChange: vi.fn(),
open: true,
}),
);
fireEvent.click(screen.getByText("确 定"));
await waitFor(() => {
expect(screen.getByText("请输入描述")).not.toBeNull();
});
});
test("点击确定触发表单提交", async () => {
const onAdd = vi.fn<(material: Material) => void>();
renderWithProviders(
createElement(AddMaterialModal, {
onAdd,
onOpenChange: vi.fn(),
open: true,
}),
);
const textarea = screen.getByPlaceholderText("请输入素材描述");
fireEvent.change(textarea, { target: { value: "测试描述" } });
fireEvent.click(screen.getByText("确 定"));
await waitFor(() => {
expect(onAdd).toHaveBeenCalledTimes(1);
});
const callArgs = onAdd.mock.calls[0];
expect(callArgs).toBeDefined();
const calledMaterial = callArgs![0];
expect(calledMaterial.description).toBe("测试描述");
expect(calledMaterial.associatedDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
expect(calledMaterial.id).toBeTruthy();
});
});

View File

@@ -0,0 +1,63 @@
import { fireEvent, screen, waitFor } from "@testing-library/react";
import { describe, expect, test } from "bun:test";
import { createElement } from "react";
import { InboxPage } from "../../../../src/web/features/inbox";
import { renderWithProviders } from "../../test-utils";
describe("InboxPage", () => {
test("初始状态显示空状态提示", () => {
renderWithProviders(createElement(InboxPage));
expect(screen.getByText("暂无素材")).not.toBeNull();
expect(screen.getByText("请在左侧选择素材")).not.toBeNull();
});
test("新增素材后列表追加且自动选中", async () => {
renderWithProviders(createElement(InboxPage));
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(() => {
expect(screen.getByText("新增的素材")).not.toBeNull();
});
expect(screen.getByText("素材详情")).not.toBeNull();
expect(screen.queryByText("暂无素材")).toBeNull();
expect(screen.queryByText("请在左侧选择素材")).toBeNull();
});
test("删除素材后列表更新", async () => {
renderWithProviders(createElement(InboxPage));
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(() => {
expect(screen.getByText("待删除的素材")).not.toBeNull();
});
fireEvent.click(screen.getByLabelText("删除"));
await waitFor(() => {
expect(screen.getByText("暂无素材")).not.toBeNull();
expect(screen.getByText("请在左侧选择素材")).not.toBeNull();
});
});
});

View File

@@ -0,0 +1,74 @@
import { fireEvent, screen } from "@testing-library/react";
import { describe, expect, test, vi } from "bun:test";
import { createElement } from "react";
import type { Material } from "../../../../src/web/features/inbox/types";
import { MaterialCard } from "../../../../src/web/features/inbox/components/MaterialCard";
import { renderWithProviders } from "../../test-utils";
const MOCK_MATERIAL: Material = {
associatedDate: "2026-06-03",
createdAt: new Date().toISOString(),
description: "测试素材描述",
id: "test-id",
};
describe("MaterialCard", () => {
test("渲染素材描述和日期信息", () => {
renderWithProviders(
createElement(MaterialCard, {
material: MOCK_MATERIAL,
onDelete: vi.fn(),
onSelect: vi.fn(),
selected: false,
}),
);
expect(screen.getByText("测试素材描述")).not.toBeNull();
expect(screen.getByText(/2026-06-03/)).not.toBeNull();
});
test("点击卡片触发 onSelect", () => {
const onSelect = vi.fn();
renderWithProviders(
createElement(MaterialCard, {
material: MOCK_MATERIAL,
onDelete: vi.fn(),
onSelect,
selected: false,
}),
);
const card = screen.getByText("测试素材描述").closest(".ant-card")!;
fireEvent.click(card);
expect(onSelect).toHaveBeenCalledTimes(1);
});
test("点击删除按钮触发 onDelete 且不触发 onSelect", () => {
const onDelete = vi.fn();
const onSelect = vi.fn();
renderWithProviders(
createElement(MaterialCard, {
material: MOCK_MATERIAL,
onDelete,
onSelect,
selected: false,
}),
);
fireEvent.click(screen.getByLabelText("删除"));
expect(onDelete).toHaveBeenCalledTimes(1);
expect(onSelect).not.toHaveBeenCalled();
});
test("选中状态添加 app-inbox-card-selected 类", () => {
renderWithProviders(
createElement(MaterialCard, {
material: MOCK_MATERIAL,
onDelete: vi.fn(),
onSelect: vi.fn(),
selected: true,
}),
);
const card = screen.getByText("测试素材描述").closest(".app-inbox-card-selected");
expect(card).not.toBeNull();
});
});

View File

@@ -0,0 +1,29 @@
import { screen } from "@testing-library/react";
import { describe, expect, test } from "bun:test";
import { createElement } from "react";
import type { Material } from "../../../../src/web/features/inbox/types";
import { MaterialContent } from "../../../../src/web/features/inbox/components/MaterialContent";
import { renderWithProviders } from "../../test-utils";
const MOCK_MATERIAL: Material = {
associatedDate: "2026-06-03",
createdAt: new Date().toISOString(),
description: "详细描述内容",
id: "test-id",
};
describe("MaterialContent", () => {
test("未选中时显示空状态提示", () => {
renderWithProviders(createElement(MaterialContent, { material: null }));
expect(screen.getByText("请在左侧选择素材")).not.toBeNull();
});
test("选中时展示素材详情", () => {
renderWithProviders(createElement(MaterialContent, { material: MOCK_MATERIAL }));
expect(screen.getByText("素材详情")).not.toBeNull();
expect(screen.getByText("详细描述内容")).not.toBeNull();
expect(screen.getByText("2026-06-03")).not.toBeNull();
});
});

View File

@@ -0,0 +1,67 @@
import { screen } from "@testing-library/react";
import { describe, expect, test, vi } from "bun:test";
import { createElement } from "react";
import type { Material } from "../../../../src/web/features/inbox/types";
import { MaterialList } from "../../../../src/web/features/inbox/components/MaterialList";
import { renderWithProviders } from "../../test-utils";
const MOCK_MATERIALS: Material[] = [
{
associatedDate: "2026-06-03",
createdAt: new Date().toISOString(),
description: "素材一",
id: "id-1",
},
{
associatedDate: "2026-06-02",
createdAt: new Date().toISOString(),
description: "素材二",
id: "id-2",
},
];
describe("MaterialList", () => {
test("列表为空时显示暂无素材", () => {
renderWithProviders(
createElement(MaterialList, {
materials: [],
onAddClick: vi.fn(),
onDelete: vi.fn(),
onSelect: vi.fn(),
selectedId: null,
}),
);
expect(screen.getByText("暂无素材")).not.toBeNull();
});
test("渲染素材卡片列表", () => {
renderWithProviders(
createElement(MaterialList, {
materials: MOCK_MATERIALS,
onAddClick: vi.fn(),
onDelete: vi.fn(),
onSelect: vi.fn(),
selectedId: null,
}),
);
expect(screen.getByText("素材一")).not.toBeNull();
expect(screen.getByText("素材二")).not.toBeNull();
});
test("点击新增素材按钮触发 onAddClick", () => {
const onAddClick = vi.fn();
renderWithProviders(
createElement(MaterialList, {
materials: [],
onAddClick,
onDelete: vi.fn(),
onSelect: vi.fn(),
selectedId: null,
}),
);
screen.getByText("新增素材").click();
expect(onAddClick).toHaveBeenCalledTimes(1);
});
});

View File

@@ -120,4 +120,34 @@ describe("Workbench 路由", () => {
{ timeout: 10000 },
);
});
test("Workbench 收集箱路由可达", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}/inbox`,
});
await waitFor(
() => {
expect(screen.getByText("新增素材")).not.toBeNull();
},
{ timeout: 10000 },
);
});
test("Workbench 显示收集箱菜单", async () => {
createMockHandler();
renderWithProviders(createElement(App), {
initialRoute: `/workbench/${MOCK_PROJECT.id}`,
});
await waitFor(
() => {
expect(screen.getByText("收集箱")).not.toBeNull();
},
{ timeout: 10000 },
);
});
});