fix(coding): scope metadata updates by project
This commit is contained in:
@@ -296,6 +296,91 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
expect(codingConversationStore.getState().selectedConversationId).toBe(reviewerConversation.id);
|
||||
});
|
||||
|
||||
it('does not write a pending fork into the Renderer store after switching projects', async () => {
|
||||
const forkFlight = deferred<CodingConversationMetadata>();
|
||||
const secondProject = { ...project, id: 'project-2', name: 'Second project' };
|
||||
const secondAgent = { ...agent, id: 'agent-project-2', name: 'Second builder' };
|
||||
const secondConversation = {
|
||||
...conversation,
|
||||
id: 'conversation-project-2',
|
||||
agentId: secondAgent.id,
|
||||
title: 'Second project conversation',
|
||||
};
|
||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||
projectApi.config.mockResolvedValue({ project, config });
|
||||
projectApi.conversations.mockResolvedValue([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 { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingWorkspaceStore } = await import('@/stores/coding-workspace');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
createLocalConversationSnapshot(
|
||||
conversationId === secondConversation.id ? secondProject.id : project.id,
|
||||
conversationId === secondConversation.id ? secondConversation : conversation,
|
||||
)
|
||||
));
|
||||
render(<CodingChatPanel />);
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(conversation.id));
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '创建分支' }));
|
||||
await waitFor(() => expect(conversationApi.fork).toHaveBeenCalledWith(conversation.id, undefined));
|
||||
act(() => {
|
||||
codingWorkspaceStore.setState({
|
||||
activeProjectId: secondProject.id,
|
||||
activeProject: secondProject,
|
||||
config: configForAgents([secondAgent]),
|
||||
conversations: [secondConversation],
|
||||
selectedAgentId: secondAgent.id,
|
||||
});
|
||||
codingConversationStore.setState({ selectedConversationId: secondConversation.id });
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
forkFlight.resolve({ ...conversation, id: 'conversation-forked', title: 'Old project fork' });
|
||||
await forkFlight.promise;
|
||||
});
|
||||
expect(codingWorkspaceStore.getState().conversations).toEqual([secondConversation]);
|
||||
expect(codingConversationStore.getState().selectedConversationId).toBe(secondConversation.id);
|
||||
});
|
||||
|
||||
it('only shows a metadata error on its originating Conversation', 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 { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
const { codingWorkspaceStore } = await import('@/stores/coding-workspace');
|
||||
const { codingConversationStore } = await import('@/stores/coding-conversations');
|
||||
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
|
||||
createLocalConversationSnapshot(
|
||||
project.id,
|
||||
conversationId === reviewerConversation.id ? reviewerConversation : conversation,
|
||||
)
|
||||
));
|
||||
render(<CodingChatPanel />);
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(conversation.id));
|
||||
act(() => {
|
||||
codingWorkspaceStore.setState({
|
||||
conversationErrorsByProjectId: {
|
||||
[project.id]: { [conversation.id]: 'metadata rejected' },
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(await screen.findByText('metadata rejected')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Reviewer/ }));
|
||||
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
|
||||
.toBe(reviewerConversation.id));
|
||||
expect(screen.queryByText('metadata rejected')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not clear a newly selected Conversation when an old archive request finishes', async () => {
|
||||
const archiveFlight = deferred<CodingConversationMetadata>();
|
||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||
|
||||
Reference in New Issue
Block a user