Files
Alfred/tests/web/components/ChatPanel.test.tsx
lanyuanxiaoyao 5b09a16bc3 refactor(web): React 最佳实践优化 — memo/callback + 目录边界 + 路由增强
- useLogger: useMemo + JSON.stringify 替代 useState 派生
- useIsDark: effectiveTheme 替代 token 色值比较
- useCurrentProject: layouts/ 提升到 shared/hooks/
- ConsoleShell: locale useMemo 缓存
- ConsoleOutlet: 添加 Suspense 边界
- routes: 添加 layout 级 errorElement
- Table 组件: operationColumn useMemo + useCallback
- ChatPanel: footer 合并为 useCallback, props 传入模型数据
- ChatPage: textModels/conversations useMemo 缓存
2026-06-03 11:32:28 +08:00

175 lines
5.1 KiB
TypeScript

import { fireEvent, screen, waitFor } from "@testing-library/react";
import { describe, expect, mock, test } from "bun:test";
import { createElement } from "react";
import type { Conversation, Model } from "../../../src/shared/api";
import { ChatPanel } from "../../../src/web/features/chat/ChatPanel";
import { installFetchMock, jsonResponse, renderWithProviders } from "../test-utils";
const PROJECT_ID = "proj-1";
const TEXT_MODEL: Model = {
capabilities: ["text"],
contextLength: null,
createdAt: "2024-01-01T00:00:00.000Z",
id: "model-1",
maxOutputTokens: null,
modelId: "gpt-4o",
name: "GPT-4o",
providerId: "pv1",
updatedAt: "2024-01-01T00:00:00.000Z",
};
const CONVERSATION: Conversation = {
createdAt: "2024-01-01T00:00:00.000Z",
id: "conv-1",
modelId: "model-1",
projectId: PROJECT_ID,
title: "新会话",
updatedAt: "2024-01-01T00:00:00.000Z",
};
const noop = () => {
return undefined;
};
void mock.module("@ai-sdk/react", () => ({
useChat: () => ({
messages: [],
regenerate: noop,
sendMessage: noop,
setMessages: (msgs: unknown) => msgs,
status: "ready",
stop: noop,
}),
}));
void mock.module("ai", () => ({
DefaultChatTransport: function () {
return undefined;
},
}));
function getSenderTextarea() {
return screen.getByPlaceholderText("输入消息...");
}
function setupFetchMock() {
return installFetchMock((call) => {
if (call.url.includes("/models")) {
return jsonResponse({ items: [TEXT_MODEL], total: 1 });
}
if (call.url.endsWith("/conversations") && call.method === "POST") {
return jsonResponse({ conversation: { ...CONVERSATION, id: "conv-new" } }, { status: 201 });
}
if (/\/conversations\/conv-1$/.exec(call.url)) {
return jsonResponse({ conversation: CONVERSATION });
}
if (call.url.includes("/messages")) {
return jsonResponse({ items: [], total: 0 });
}
if (/\/conversations$/.exec(call.url) && call.method === "GET") {
return jsonResponse({ items: [], total: 0 });
}
return jsonResponse({ error: "not found" }, { status: 404 });
});
}
describe("ChatPanel", () => {
describe("欢迎页", () => {
test("无会话时显示欢迎页和输入框", () => {
setupFetchMock();
renderWithProviders(
createElement(ChatPanel, {
conversationId: null,
defaultModelId: "model-1",
onConversationCreated: noop,
projectId: PROJECT_ID,
textModels: [TEXT_MODEL],
}),
);
expect(screen.getByText("你好,我是阿福")).toBeTruthy();
expect(screen.getByText("有什么我可以帮助你的吗?")).toBeTruthy();
expect(getSenderTextarea()).toBeTruthy();
});
});
describe("自动创建会话", () => {
test("输入并发送后自动创建会话并通知父组件", async () => {
const calls = setupFetchMock();
const onCreated = mock<(id: string) => void>(() => undefined);
renderWithProviders(
createElement(ChatPanel, {
conversationId: null,
defaultModelId: "model-1",
onConversationCreated: onCreated,
projectId: PROJECT_ID,
textModels: [TEXT_MODEL],
}),
);
const textarea = getSenderTextarea();
fireEvent.change(textarea, { target: { value: "你好" } });
fireEvent.keyDown(textarea, { key: "Enter" });
await waitFor(() => {
expect(onCreated).toHaveBeenCalledWith("conv-new");
});
const createCall = calls.find((c) => c.url.endsWith("/conversations") && c.method === "POST");
expect(createCall).toBeTruthy();
});
test("创建会话失败时恢复输入文本", async () => {
installFetchMock((call) => {
if (call.url.includes("/models")) {
return jsonResponse({ items: [TEXT_MODEL], total: 1 });
}
if (call.url.endsWith("/conversations") && call.method === "POST") {
return jsonResponse({ error: "服务器错误" }, { status: 500 });
}
return jsonResponse({ error: "not found" }, { status: 404 });
});
renderWithProviders(
createElement(ChatPanel, {
conversationId: null,
defaultModelId: "model-1",
onConversationCreated: noop,
projectId: PROJECT_ID,
textModels: [TEXT_MODEL],
}),
);
const textarea = getSenderTextarea();
fireEvent.change(textarea, { target: { value: "测试输入" } });
fireEvent.keyDown(textarea, { key: "Enter" });
await waitFor(() => {
expect((textarea as HTMLTextAreaElement).value).toBe("测试输入");
});
});
});
describe("聊天面板", () => {
test("选中会话时显示消息列表区域", () => {
setupFetchMock();
renderWithProviders(
createElement(ChatPanel, {
conversationId: "conv-1",
defaultModelId: "model-1",
onConversationCreated: noop,
projectId: PROJECT_ID,
textModels: [TEXT_MODEL],
}),
);
expect(screen.queryByText("你好,我是阿福")).toBeNull();
expect(getSenderTextarea()).toBeTruthy();
});
});
});