781 lines
22 KiB
TypeScript
781 lines
22 KiB
TypeScript
"use client";
|
|
|
|
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
|
|
import { useAgentChatSession } from "./useAgentChatSession";
|
|
import {
|
|
abortAgentChat,
|
|
forkAgentChat,
|
|
replyAgentPermission,
|
|
replyAgentQuestion,
|
|
resumeAgentChatStream,
|
|
streamAgentChat,
|
|
} from "@/lib/chatStream";
|
|
import type { StreamEvent } from "@/lib/chatStream";
|
|
|
|
jest.mock("@/lib/chatStream", () => ({
|
|
abortAgentChat: jest.fn(async () => undefined),
|
|
forkAgentChat: jest.fn(async () => "forked-session"),
|
|
replyAgentPermission: jest.fn(async () => undefined),
|
|
replyAgentQuestion: jest.fn(async () => undefined),
|
|
resumeAgentChatStream: jest.fn(async () => undefined),
|
|
streamAgentChat: jest.fn(async () => undefined),
|
|
}));
|
|
|
|
const listChatSessions = jest.fn();
|
|
const deleteChatSession = jest.fn();
|
|
const updateChatSessionTitle = jest.fn();
|
|
|
|
jest.mock("../chatStorage", () => ({
|
|
createEmptyChatState: jest.fn(() => ({
|
|
title: undefined,
|
|
isTitleManuallyEdited: false,
|
|
messages: [],
|
|
sessionId: undefined,
|
|
})),
|
|
deleteChatSession: (...args: unknown[]) => deleteChatSession(...args),
|
|
listChatSessions: (...args: unknown[]) => listChatSessions(...args),
|
|
loadChatSessionById: jest.fn(async () => ({
|
|
title: "已存在会话",
|
|
isTitleManuallyEdited: false,
|
|
messages: [],
|
|
sessionId: "session-loaded",
|
|
})),
|
|
updateChatSessionTitle: (...args: unknown[]) => updateChatSessionTitle(...args),
|
|
}));
|
|
|
|
describe("useAgentChatSession", () => {
|
|
beforeEach(() => {
|
|
listChatSessions.mockReset();
|
|
deleteChatSession.mockReset();
|
|
updateChatSessionTitle.mockReset();
|
|
jest.mocked(abortAgentChat).mockReset();
|
|
jest.mocked(forkAgentChat).mockReset();
|
|
jest.mocked(replyAgentPermission).mockReset();
|
|
jest.mocked(replyAgentQuestion).mockReset();
|
|
jest.mocked(resumeAgentChatStream).mockReset();
|
|
jest.mocked(streamAgentChat).mockReset();
|
|
jest.mocked(abortAgentChat).mockImplementation(async () => undefined);
|
|
jest.mocked(forkAgentChat).mockImplementation(async () => "forked-session");
|
|
jest.mocked(replyAgentPermission).mockImplementation(async () => undefined);
|
|
jest.mocked(replyAgentQuestion).mockImplementation(async () => undefined);
|
|
jest.mocked(resumeAgentChatStream).mockImplementation(async () => undefined);
|
|
jest.mocked(streamAgentChat).mockImplementation(async () => undefined);
|
|
deleteChatSession.mockImplementation(async () => undefined);
|
|
updateChatSessionTitle.mockImplementation(async () => undefined);
|
|
});
|
|
|
|
describe("useAgentChatSession lifecycle and resume", () => {
|
|
it("does not add a new empty session to history until there is actual chat content", async () => {
|
|
listChatSessions.mockResolvedValue([]);
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
act(() => {
|
|
void result.current.createSession();
|
|
});
|
|
|
|
await waitFor(() => expect(result.current.sessionTitle).toBe("新对话"));
|
|
expect(result.current.chatSessions).toEqual([]);
|
|
expect(result.current.activeSessionId).toBeUndefined();
|
|
expect(result.current.messages).toEqual([]);
|
|
expect(result.current.isStreaming).toBe(false);
|
|
expect(listChatSessions).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("keeps existing history entries when creating a blank new session", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-1",
|
|
title: "已有会话",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
},
|
|
]);
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
act(() => {
|
|
void result.current.createSession();
|
|
});
|
|
|
|
expect(result.current.chatSessions).toEqual([
|
|
{
|
|
id: "session-1",
|
|
title: "已有会话",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("removes a deleted history entry before the backend delete finishes", async () => {
|
|
const initialSessions = [
|
|
{
|
|
id: "session-1",
|
|
title: "第一段会话",
|
|
createdAt: 2,
|
|
updatedAt: 2,
|
|
},
|
|
{
|
|
id: "session-2",
|
|
title: "第二段会话",
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
},
|
|
];
|
|
let resolveDelete: ((nextActiveSessionId?: string) => void) | undefined;
|
|
|
|
listChatSessions.mockResolvedValue(initialSessions);
|
|
deleteChatSession.mockImplementationOnce(
|
|
() =>
|
|
new Promise<string | undefined>((resolve) => {
|
|
resolveDelete = resolve;
|
|
}),
|
|
);
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
act(() => {
|
|
void result.current.removeSession("session-2");
|
|
});
|
|
|
|
expect(result.current.chatSessions).toEqual([
|
|
expect.objectContaining({ id: "session-1" }),
|
|
]);
|
|
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-1",
|
|
title: "第一段会话",
|
|
createdAt: 2,
|
|
updatedAt: 2,
|
|
},
|
|
]);
|
|
|
|
await act(async () => {
|
|
resolveDelete?.();
|
|
await Promise.resolve();
|
|
});
|
|
|
|
await waitFor(() =>
|
|
expect(result.current.chatSessions).toEqual([
|
|
expect.objectContaining({ id: "session-1" }),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("does not autosave full messages after the stream is done", async () => {
|
|
listChatSessions.mockResolvedValue([]);
|
|
let emitStreamEvent: ((event: StreamEvent) => void) | undefined;
|
|
jest.mocked(streamAgentChat).mockImplementationOnce(async ({ onEvent }) => {
|
|
emitStreamEvent = onEvent;
|
|
await new Promise<void>(() => undefined);
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
jest.useFakeTimers();
|
|
try {
|
|
await act(async () => {
|
|
void result.current.sendPrompt("第一条消息");
|
|
await Promise.resolve();
|
|
});
|
|
|
|
expect(result.current.isStreaming).toBe(true);
|
|
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(200);
|
|
});
|
|
|
|
act(() => {
|
|
emitStreamEvent?.({
|
|
type: "token",
|
|
sessionId: "chat-stream-1",
|
|
content: "收到",
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(200);
|
|
});
|
|
|
|
act(() => {
|
|
emitStreamEvent?.({
|
|
type: "done",
|
|
sessionId: "chat-stream-1",
|
|
});
|
|
});
|
|
|
|
await act(async () => {
|
|
jest.advanceTimersByTime(200);
|
|
});
|
|
|
|
expect(result.current.messages).toEqual([
|
|
expect.objectContaining({ role: "user", content: "第一条消息" }),
|
|
expect.objectContaining({ role: "assistant", content: "收到" }),
|
|
]);
|
|
expect(result.current.activeSessionId).toBe("chat-stream-1");
|
|
} finally {
|
|
jest.useRealTimers();
|
|
}
|
|
});
|
|
|
|
it("shows shared todo state only on the latest assistant message in a session", async () => {
|
|
listChatSessions.mockResolvedValue([]);
|
|
jest.mocked(streamAgentChat)
|
|
.mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "todo_update",
|
|
sessionId: "session-1",
|
|
todos: [
|
|
{
|
|
id: "todo-1",
|
|
content: "创建任务列表",
|
|
status: "in_progress",
|
|
},
|
|
],
|
|
createdAt: 1000,
|
|
});
|
|
onEvent({
|
|
type: "done",
|
|
sessionId: "session-1",
|
|
});
|
|
})
|
|
.mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "todo_update",
|
|
sessionId: "session-1",
|
|
todos: [
|
|
{
|
|
id: "todo-1",
|
|
content: "创建任务列表",
|
|
status: "completed",
|
|
},
|
|
{
|
|
id: "todo-2",
|
|
content: "更新任务状态",
|
|
status: "in_progress",
|
|
},
|
|
],
|
|
createdAt: 2000,
|
|
});
|
|
onEvent({
|
|
type: "done",
|
|
sessionId: "session-1",
|
|
});
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
await act(async () => {
|
|
await result.current.sendPrompt("创建任务");
|
|
});
|
|
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
|
|
await act(async () => {
|
|
await result.current.sendPrompt("更新任务");
|
|
});
|
|
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
|
|
const assistantMessages = result.current.messages.filter(
|
|
(message) => message.role === "assistant",
|
|
);
|
|
|
|
expect(assistantMessages).toHaveLength(2);
|
|
expect(assistantMessages[0].todos).toBeUndefined();
|
|
expect(assistantMessages[1].todos).toEqual(
|
|
expect.objectContaining({
|
|
sessionId: "session-1",
|
|
createdAt: 2000,
|
|
todos: [
|
|
expect.objectContaining({
|
|
id: "todo-1",
|
|
status: "completed",
|
|
}),
|
|
expect.objectContaining({
|
|
id: "todo-2",
|
|
status: "in_progress",
|
|
}),
|
|
],
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("hydrates a backend streaming session and resumes its stream", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
runStatus: "running",
|
|
},
|
|
]);
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
expect(result.current.isStreaming).toBe(true);
|
|
expect(result.current.activeSessionId).toBe("session-loaded");
|
|
expect(resumeAgentChatStream).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sessionId: "session-loaded",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("updates resumed messages from state, token, and done events", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
},
|
|
]);
|
|
jest.mocked(resumeAgentChatStream).mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "state",
|
|
sessionId: "session-loaded",
|
|
messages: [
|
|
{ id: "u1", role: "user", content: "继续分析" },
|
|
{ id: "a1", role: "assistant", content: "已有" },
|
|
],
|
|
isStreaming: true,
|
|
runStatus: "running",
|
|
});
|
|
onEvent({
|
|
type: "token",
|
|
sessionId: "session-loaded",
|
|
content: "输出",
|
|
});
|
|
onEvent({
|
|
type: "done",
|
|
sessionId: "session-loaded",
|
|
});
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
await waitFor(() => expect(result.current.isStreaming).toBe(false));
|
|
|
|
expect(result.current.messages).toEqual([
|
|
expect.objectContaining({ id: "u1", role: "user", content: "继续分析" }),
|
|
expect.objectContaining({ id: "a1", role: "assistant", content: "已有输出" }),
|
|
]);
|
|
});
|
|
|
|
it("applies question responses to the message that owns the request", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
},
|
|
]);
|
|
jest.mocked(resumeAgentChatStream).mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "state",
|
|
sessionId: "session-loaded",
|
|
messages: [
|
|
{ id: "u1", role: "user", content: "继续分析" },
|
|
{
|
|
id: "a1",
|
|
role: "assistant",
|
|
content: "需要确认",
|
|
questions: [
|
|
{
|
|
requestId: "q-1",
|
|
sessionId: "session-loaded",
|
|
questions: [
|
|
{
|
|
header: "范围",
|
|
question: "选择范围",
|
|
options: [],
|
|
custom: true,
|
|
},
|
|
],
|
|
createdAt: 123,
|
|
status: "pending",
|
|
},
|
|
],
|
|
},
|
|
{ id: "a2", role: "assistant", content: "后续消息" },
|
|
],
|
|
isStreaming: true,
|
|
runStatus: "running",
|
|
});
|
|
onEvent({
|
|
type: "question_response",
|
|
sessionId: "session-loaded",
|
|
requestId: "q-1",
|
|
answers: [["城区"]],
|
|
rejected: false,
|
|
});
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
expect(result.current.messages[1].questions?.[0]).toEqual(
|
|
expect.objectContaining({
|
|
requestId: "q-1",
|
|
status: "answered",
|
|
answers: [["城区"]],
|
|
}),
|
|
);
|
|
expect(result.current.messages[2].questions).toBeUndefined();
|
|
});
|
|
|
|
it("deduplicates question requests across assistant messages", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
},
|
|
]);
|
|
jest.mocked(resumeAgentChatStream).mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "state",
|
|
sessionId: "session-loaded",
|
|
messages: [
|
|
{ id: "u1", role: "user", content: "继续分析" },
|
|
{
|
|
id: "a1",
|
|
role: "assistant",
|
|
content: "需要确认",
|
|
questions: [
|
|
{
|
|
requestId: "question-1",
|
|
sessionId: "session-loaded",
|
|
questions: [
|
|
{
|
|
header: "测试问题",
|
|
question: "你觉得这个 question 工具好用吗?",
|
|
options: [
|
|
{
|
|
label: "非常好用",
|
|
description: "交互清晰,选项方便",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
tool: {
|
|
messageID: "message-1",
|
|
callID: "call-1",
|
|
},
|
|
createdAt: 123,
|
|
status: "pending",
|
|
},
|
|
],
|
|
},
|
|
{ id: "a2", role: "assistant", content: "后续消息" },
|
|
],
|
|
isStreaming: true,
|
|
runStatus: "running",
|
|
});
|
|
onEvent({
|
|
type: "question_request",
|
|
sessionId: "session-loaded",
|
|
requestId: "call-1",
|
|
questions: [
|
|
{
|
|
header: "测试问题",
|
|
question: "你觉得这个 question 工具好用吗?",
|
|
options: [
|
|
{
|
|
label: "非常好用",
|
|
description: "交互清晰,选项方便",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
tool: {
|
|
messageID: "message-1",
|
|
callID: "call-1",
|
|
},
|
|
createdAt: 456,
|
|
});
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
const allQuestions = result.current.messages.flatMap(
|
|
(message) => message.questions ?? [],
|
|
);
|
|
expect(allQuestions).toHaveLength(1);
|
|
expect(result.current.messages[1].questions?.[0]).toEqual(
|
|
expect.objectContaining({
|
|
requestId: "question-1",
|
|
tool: expect.objectContaining({ callID: "call-1" }),
|
|
}),
|
|
);
|
|
expect(result.current.messages[2].questions).toBeUndefined();
|
|
});
|
|
|
|
it("keeps the actionable question request id when a tool-part duplicate arrives later", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
},
|
|
]);
|
|
jest.mocked(resumeAgentChatStream).mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "state",
|
|
sessionId: "session-loaded",
|
|
messages: [
|
|
{ id: "u1", role: "user", content: "继续分析" },
|
|
{
|
|
id: "a1",
|
|
role: "assistant",
|
|
content: "需要确认",
|
|
questions: [
|
|
{
|
|
requestId: "question-1",
|
|
sessionId: "session-loaded",
|
|
questions: [
|
|
{
|
|
header: "测试问题",
|
|
question: "你觉得这个 question 工具好用吗?",
|
|
options: [
|
|
{
|
|
label: "非常好用",
|
|
description: "交互清晰,选项方便",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
tool: {
|
|
messageID: "message-1",
|
|
callID: "call-1",
|
|
},
|
|
createdAt: 123,
|
|
status: "pending",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
isStreaming: true,
|
|
runStatus: "running",
|
|
});
|
|
onEvent({
|
|
type: "question_request",
|
|
sessionId: "session-loaded",
|
|
requestId: "call-1",
|
|
questions: [
|
|
{
|
|
header: "测试问题",
|
|
question: "你觉得这个 question 工具好用吗?",
|
|
options: [
|
|
{
|
|
label: "非常好用",
|
|
description: "交互清晰,选项方便",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
tool: {
|
|
messageID: "message-1",
|
|
callID: "call-1",
|
|
},
|
|
createdAt: 456,
|
|
});
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
const allQuestions = result.current.messages.flatMap(
|
|
(message) => message.questions ?? [],
|
|
);
|
|
expect(allQuestions).toHaveLength(1);
|
|
expect(allQuestions[0]).toEqual(
|
|
expect.objectContaining({
|
|
requestId: "question-1",
|
|
tool: expect.objectContaining({ callID: "call-1" }),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("deduplicates persisted duplicate questions from state events", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
},
|
|
]);
|
|
const duplicateQuestion = {
|
|
sessionId: "session-loaded",
|
|
questions: [
|
|
{
|
|
header: "测试问题",
|
|
question: "你觉得这个 question 工具好用吗?",
|
|
options: [
|
|
{
|
|
label: "非常好用",
|
|
description: "交互清晰,选项方便",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
tool: {
|
|
messageID: "message-1",
|
|
callID: "call-1",
|
|
},
|
|
createdAt: 123,
|
|
status: "pending" as const,
|
|
};
|
|
jest.mocked(resumeAgentChatStream).mockImplementationOnce(async ({ onEvent }) => {
|
|
onEvent({
|
|
type: "state",
|
|
sessionId: "session-loaded",
|
|
messages: [
|
|
{ id: "u1", role: "user", content: "继续分析" },
|
|
{
|
|
id: "a1",
|
|
role: "assistant",
|
|
content: "需要确认",
|
|
questions: [{ ...duplicateQuestion, requestId: "question-1" }],
|
|
},
|
|
{
|
|
id: "a2",
|
|
role: "assistant",
|
|
content: "后续消息",
|
|
questions: [{ ...duplicateQuestion, requestId: "call-1" }],
|
|
},
|
|
],
|
|
isStreaming: true,
|
|
runStatus: "running",
|
|
});
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isHydrating).toBe(false));
|
|
|
|
expect(
|
|
result.current.messages.flatMap((message) => message.questions ?? []),
|
|
).toHaveLength(1);
|
|
expect(result.current.messages[1].questions).toHaveLength(1);
|
|
expect(result.current.messages[2].questions).toBeUndefined();
|
|
});
|
|
|
|
it("aborts a resumed streaming session through the backend abort endpoint", async () => {
|
|
listChatSessions.mockResolvedValue([
|
|
{
|
|
id: "session-streaming",
|
|
title: "运行中",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
isStreaming: true,
|
|
},
|
|
]);
|
|
jest.mocked(resumeAgentChatStream).mockImplementationOnce(async () => {
|
|
await new Promise<void>(() => undefined);
|
|
});
|
|
|
|
const { result } = renderHook(() =>
|
|
useAgentChatSession({
|
|
projectId: "project-1",
|
|
onToolCall: jest.fn(),
|
|
}),
|
|
);
|
|
|
|
await waitFor(() => expect(result.current.isStreaming).toBe(true));
|
|
|
|
act(() => {
|
|
result.current.abort();
|
|
});
|
|
|
|
expect(abortAgentChat).toHaveBeenCalledWith("session-loaded");
|
|
});
|
|
|
|
});
|
|
});
|