feat(coding): nest Conversations under Agents

This commit is contained in:
2026-08-25 16:34:11 +08:00
parent c4ca16fa19
commit 45d933732a
5 changed files with 238 additions and 64 deletions

View File

@@ -1,4 +1,4 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
@@ -267,6 +267,40 @@ describe('CodingChatPanel first Conversation', () => {
expect(codingConversationStore.getState().selectedConversationId).toBe(reviewerConversation.id);
});
it('renders Conversations as children of only the selected Agent', 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');
conversationApi.snapshot.mockImplementation(async (conversationId: string) => (
createLocalConversationSnapshot(
project.id,
conversationId === reviewerConversation.id ? reviewerConversation : 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();
});
it('does not let a pending fork steal selection or Header busy state after a Conversation switch', async () => {
const forkFlight = deferred<CodingConversationMetadata>();
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });