feat: organize project conversations and add coding teacher side chat
This commit is contained in:
@@ -43,7 +43,8 @@ function withForkSource(
|
||||
};
|
||||
}
|
||||
|
||||
const runtimeSnapshot = deferred<ConversationSnapshot>();
|
||||
const navigate = vi.hoisted(() => vi.fn());
|
||||
vi.mock('react-router-dom', async (importOriginal) => ({ ...await importOriginal<typeof import('react-router-dom')>(), useNavigate: () => navigate }));
|
||||
|
||||
class FakeEventSource {
|
||||
onopen: ((event: Event) => void) | null = null;
|
||||
@@ -52,7 +53,6 @@ class FakeEventSource {
|
||||
addEventListener = vi.fn();
|
||||
}
|
||||
|
||||
const source = new FakeEventSource();
|
||||
const project: CodingProjectSummary = {
|
||||
id: 'project-1',
|
||||
name: 'Local project',
|
||||
@@ -216,18 +216,20 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
conversationApi.snapshot.mockResolvedValue({ ...createLocalConversationSnapshot(project.id, conversation),
|
||||
run: { status: 'running', runId: 'run-1' } });
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
render(<CodingChatPanel />);
|
||||
const menu = await screen.findByRole('button', { name: '对话操作:新对话' });
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId).toBe(conversation.id));
|
||||
fireEvent.keyDown(menu, { key: 'Enter' });
|
||||
fireEvent.click(await screen.findByRole('menuitem', { name: '归档(任务继续运行)' }));
|
||||
await screen.findByText('没有最近会话,可新建对话或查看归档');
|
||||
await screen.findByTestId('coding-conversation-pending');
|
||||
expect(projectApi.create).not.toHaveBeenCalled();
|
||||
expect(conversationApi.abort).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '已归档(1)' }));
|
||||
fireEvent.click(within(screen.getByTestId('coding-conversation-sidebar')).getByRole('button', { name: '新对话', exact: true }));
|
||||
fireEvent.click(screen.getByRole('button', { name: '查看归档对话' }));
|
||||
fireEvent.click(within(screen.getByTestId('project-conversations')).getByRole('button', { name: '新对话', exact: true }));
|
||||
await screen.findByText('此对话已归档,任务仍在运行。');
|
||||
expect(screen.queryByRole('button', { name: '发送', exact: true })).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '恢复对话' }));
|
||||
@@ -245,7 +247,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
render(<CodingChatPanel />);
|
||||
fireEvent.keyDown(await screen.findByRole('button', { name: '对话操作:旧标题' }), { key: 'Enter' });
|
||||
fireEvent.click(await screen.findByRole('menuitem', { name: '重命名' }));
|
||||
@@ -267,13 +271,15 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
config: { ...configForAgents([]), initialized: false },
|
||||
});
|
||||
projectApi.conversations.mockResolvedValue([]);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
|
||||
render(<CodingChatPanel onOpenProjectSettings={openProjectSettings} />);
|
||||
|
||||
expect(await screen.findByTestId('coding-chat-empty-agent')).toHaveTextContent('项目已准备好');
|
||||
expect(projectApi.create).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建项目智能体' }));
|
||||
fireEvent.click(within(screen.getByTestId('coding-chat-empty-agent')).getByRole('button', { name: '项目设置' }));
|
||||
expect(openProjectSettings).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
@@ -282,7 +288,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
const onCreateProject = vi.fn();
|
||||
const onOpenProject = vi.fn();
|
||||
const onOpenProjectSettings = vi.fn();
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
|
||||
render(
|
||||
<CodingChatPanel
|
||||
@@ -311,44 +319,22 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
expect(onOpenProjectSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('makes the first-Conversation textarea editable while runtime metadata is held', async () => {
|
||||
it('keeps an empty project editable without creating a conversation until first send', async () => {
|
||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||
projectApi.config.mockResolvedValue({ project, config });
|
||||
projectApi.conversations.mockResolvedValue([]);
|
||||
projectApi.create.mockResolvedValue(conversation);
|
||||
conversationApi.snapshot.mockReturnValue(runtimeSnapshot.promise);
|
||||
conversationApi.events.mockResolvedValue(source as unknown as EventSource);
|
||||
conversationApi.submit.mockRejectedValue(new Error('Provider is intentionally not used'));
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { createLocalConversationSnapshot } = await import(
|
||||
'@/pages/Chat/coding-chat-snapshot'
|
||||
);
|
||||
const view = render(<CodingChatPanel />);
|
||||
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
render(<CodingChatPanel />);
|
||||
const textbox = await screen.findByRole('textbox');
|
||||
expect(screen.getByRole('heading', { name: '你想让麦洛和你一起构建什么?' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('img', { name: '麦洛 M 标识' })).toBeInTheDocument();
|
||||
expect(textbox).toBeEnabled();
|
||||
expect(projectApi.create).toHaveBeenCalledOnce();
|
||||
expect(conversationApi.snapshot).toHaveBeenCalledWith(conversation.id);
|
||||
|
||||
fireEvent.change(textbox, { target: { value: 'First prompt' } });
|
||||
const sendButton = screen.getByRole('button', { name: '发送' });
|
||||
await waitFor(() => expect(sendButton).toBeEnabled());
|
||||
expect(textbox).toHaveValue('First prompt');
|
||||
expect(conversationApi.submit).not.toHaveBeenCalled();
|
||||
|
||||
await act(async () => {
|
||||
runtimeSnapshot.resolve({
|
||||
...createLocalConversationSnapshot(project.id, conversation),
|
||||
worker: { status: 'ready', generation: 1 },
|
||||
cursor: { workerGeneration: 1, seq: 0 },
|
||||
});
|
||||
await runtimeSnapshot.promise;
|
||||
});
|
||||
view.unmount();
|
||||
expect(source.close).toHaveBeenCalled();
|
||||
expect(projectApi.create).not.toHaveBeenCalled();
|
||||
expect(conversationApi.snapshot).not.toHaveBeenCalled();
|
||||
fireEvent.change(textbox, {target:{value:'First prompt'}});
|
||||
fireEvent.click(screen.getByRole('button',{name:'发送'}));
|
||||
await waitFor(()=>expect(projectApi.create).toHaveBeenCalledOnce());
|
||||
});
|
||||
|
||||
it('rehydrates a preselected stale Conversation when the programming view mounts', async () => {
|
||||
@@ -357,7 +343,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
projectApi.conversations.mockResolvedValue([conversation]);
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
const { codingWorkspaceStore } = await import('@/stores/coding-workspace');
|
||||
@@ -427,7 +415,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
.mockResolvedValueOnce(firstSource as unknown as EventSource)
|
||||
.mockResolvedValueOnce(resumedSource as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
const localSnapshot = createLocalConversationSnapshot(project.id, conversation);
|
||||
@@ -461,7 +451,7 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
await waitFor(() => expect(
|
||||
codingConversationStore.getState().entriesByConversationId[conversation.id]?.reducer.snapshot?.run.status,
|
||||
).toBe('running'));
|
||||
expect(screen.getByText('处理中')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('对话正在运行')).toBeInTheDocument();
|
||||
const snapshotCallsBeforeSleep = conversationApi.snapshot.mock.calls.length;
|
||||
conversationApi.snapshot.mockResolvedValue(terminalSnapshot);
|
||||
|
||||
@@ -495,50 +485,15 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('does not let a slow first-Conversation completion steal selection after an Agent switch', async () => {
|
||||
const slowCreate = deferred<CodingConversationMetadata>();
|
||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||
projectApi.config.mockResolvedValue({ project, config: configForAgents([agent, reviewer]) });
|
||||
projectApi.conversations.mockResolvedValue([reviewerConversation]);
|
||||
projectApi.create.mockReturnValue(slowCreate.promise);
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.submit.mockRejectedValue(new Error('Provider is intentionally not used'));
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
createLocalConversationSnapshot(
|
||||
project.id,
|
||||
conversationId === reviewerConversation.id ? reviewerConversation : conversation,
|
||||
)
|
||||
));
|
||||
render(<CodingChatPanel />);
|
||||
|
||||
await screen.findByRole('textbox');
|
||||
await waitFor(() => expect(projectApi.create).toHaveBeenCalledWith({
|
||||
projectId: project.id,
|
||||
agentId: agent.id,
|
||||
title: '新对话',
|
||||
}));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Reviewer/ }));
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(reviewerConversation.id));
|
||||
|
||||
await act(async () => {
|
||||
slowCreate.resolve(conversation);
|
||||
await slowCreate.promise;
|
||||
});
|
||||
expect(codingConversationStore.getState().selectedConversationId).toBe(reviewerConversation.id);
|
||||
});
|
||||
|
||||
it('renders Conversations as children of only the selected Agent', async () => {
|
||||
it('lists all project conversations without Agent navigation', async () => {
|
||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||
projectApi.config.mockResolvedValue({ project, config: configForAgents([agent, reviewer]) });
|
||||
projectApi.conversations.mockResolvedValue([conversation, reviewerConversation]);
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
createLocalConversationSnapshot(
|
||||
@@ -548,22 +503,12 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
));
|
||||
render(<CodingChatPanel />);
|
||||
|
||||
const builderButton = await screen.findByRole('button', { name: agent.name });
|
||||
const reviewerButton = screen.getByRole('button', { name: reviewer.name });
|
||||
expect(builderButton).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(reviewerButton).toHaveAttribute('aria-expanded', 'false');
|
||||
const builderConversations = screen.getByRole('group', { name: `${agent.name} 的对话` });
|
||||
expect(within(builderConversations).getByRole('button', { name: conversation.title })).toBeVisible();
|
||||
expect(screen.queryByText(reviewerConversation.title)).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(reviewerButton);
|
||||
|
||||
await waitFor(() => expect(reviewerButton).toHaveAttribute('aria-expanded', 'true'));
|
||||
expect(builderButton).toHaveAttribute('aria-expanded', 'false');
|
||||
const reviewerConversations = screen.getByRole('group', { name: `${reviewer.name} 的对话` });
|
||||
expect(within(reviewerConversations)
|
||||
.getByRole('button', { name: reviewerConversation.title })).toBeVisible();
|
||||
expect(screen.queryByText(conversation.title)).not.toBeInTheDocument();
|
||||
const list = await screen.findByRole('group', {name:'项目会话'});
|
||||
expect(within(list).getByRole('button',{name:conversation.title})).toBeVisible();
|
||||
expect(within(list).getByRole('button',{name:reviewerConversation.title})).toBeVisible();
|
||||
expect(screen.queryByRole('button',{name:agent.name})).not.toBeInTheDocument();
|
||||
fireEvent.click(within(list).getByRole('button',{name:reviewerConversation.title}));
|
||||
await waitFor(()=>expect(within(list).getByRole('button',{name:reviewerConversation.title})).toHaveAttribute('aria-current','page'));
|
||||
});
|
||||
|
||||
it('does not let a pending message fork steal selection after a Conversation switch', async () => {
|
||||
@@ -574,7 +519,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
conversationApi.fork.mockReturnValue(forkFlight.promise);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
@@ -590,7 +537,7 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '从这里创建新对话分支' }));
|
||||
await waitFor(() => expect(conversationApi.fork)
|
||||
.toHaveBeenCalledWith(conversation.id, `entry-${conversation.id}`));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Reviewer/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: reviewerConversation.title }));
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(reviewerConversation.id));
|
||||
expect(screen.getByRole('button', { name: '从这里创建新对话分支' })).toBeEnabled();
|
||||
@@ -622,7 +569,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
conversationApi.fork.mockReturnValue(forkFlight.promise);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingWorkspaceStore } = await import('@/stores/coding-workspace');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
@@ -664,7 +613,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
projectApi.conversations.mockResolvedValue([conversation, reviewerConversation]);
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingWorkspaceStore } = await import('@/stores/coding-workspace');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
@@ -686,7 +637,7 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
});
|
||||
expect(await screen.findByText('metadata rejected')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Reviewer/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: reviewerConversation.title }));
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(reviewerConversation.id));
|
||||
expect(screen.queryByText('metadata rejected')).not.toBeInTheDocument();
|
||||
@@ -698,7 +649,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
projectApi.conversations.mockResolvedValue([conversation, reviewerConversation]);
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
@@ -720,7 +673,7 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
expect(screen.getAllByRole('button', { name: '项目设置' })).toHaveLength(1);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '项目设置' }));
|
||||
expect(onOpenProjectSettings).toHaveBeenCalledOnce();
|
||||
expect(navigate).toHaveBeenCalledWith('/project-config');
|
||||
});
|
||||
|
||||
it('remounts pending interaction state when switching Conversations', async () => {
|
||||
@@ -731,7 +684,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
conversationApi.respond.mockReturnValue(respondFlight.promise);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => {
|
||||
@@ -757,7 +712,7 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
interactionId: `interaction-${conversation.id}`,
|
||||
confirmed: true,
|
||||
}));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Reviewer/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: reviewerConversation.title }));
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(reviewerConversation.id));
|
||||
expect(await screen.findByText(`Confirm ${reviewerConversation.id}`)).toBeInTheDocument();
|
||||
@@ -778,7 +733,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.submit.mockReturnValueOnce(pendingSubmit.promise);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
@@ -802,7 +759,7 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '发送' }));
|
||||
await waitFor(() => expect(conversationApi.submit).toHaveBeenCalledOnce());
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Reviewer/ }));
|
||||
fireEvent.click(screen.getByRole('button', { name: reviewerConversation.title }));
|
||||
await waitFor(() => expect(screen.getByRole('textbox')).toHaveValue(''));
|
||||
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'B prompt' } });
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '发送' })).toBeEnabled());
|
||||
@@ -835,7 +792,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
runId: 'run-retry',
|
||||
mode: input.mode,
|
||||
}));
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
@@ -871,7 +830,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
undefined,
|
||||
{ backendCode: 'CODING_REQUEST_UNCERTAIN' },
|
||||
));
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
@@ -949,7 +910,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
conversationApi.recover.mockResolvedValue(undefined);
|
||||
conversationApi.abort.mockResolvedValue(undefined);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const runningSnapshot: ConversationSnapshot = {
|
||||
...createLocalConversationSnapshot(project.id, conversation),
|
||||
@@ -1018,7 +981,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
byteLength: file.size,
|
||||
}));
|
||||
});
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
@@ -1083,7 +1048,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
? Promise.reject(new Error('图片附件无效。'))
|
||||
: upload.promise
|
||||
));
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
@@ -1131,7 +1098,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
mime: file.type,
|
||||
byteLength: file.size,
|
||||
}));
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
@@ -1191,7 +1160,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
byteLength: file.size,
|
||||
};
|
||||
});
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
@@ -1240,7 +1211,9 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
runId: 'run-1',
|
||||
mode: input.mode,
|
||||
}));
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
|
||||
const CodingChatPanel = (props: import('@/pages/Chat/CodingChatPanel').CodingChatPanelProps) => <><CodingProjectConversations projectId={project.id} /><CoreChatPanel {...props} /></>;
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
const runningSnapshot: ConversationSnapshot = {
|
||||
|
||||
Reference in New Issue
Block a user