feat: include approved Code sidebar actions in combined trial source

This commit is contained in:
鲨鱼辣椒 committed 2026-10-13 21:51:27 +08:00
1 parent 16b10c44f1
commit 18e4be66da
12 files changed
+596 -61

No files matched your search

+104 -7
View File
@@ -523,7 +523,15 @@ describe('CodingChatPanel first Conversation', () => {
run: { status: 'running', runId: 'run-1' } });
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 { useState } = await import('react');
const CodingChatPanel = () => {
const [archived, setArchived] = useState(false);
return <>
<button onClick={() => setArchived(true)}>打开归档列表</button>
<CodingProjectConversations projectId={project.id} archived={archived} onArchivedChange={setArchived} />
<CoreChatPanel />
</>;
};
const { codingConversationStore } = await import('@/stores/coding-conversations');
render(<CodingChatPanel />);
const menu = await screen.findByRole('button', { name: '对话操作:新对话' });
@@ -533,7 +541,8 @@ describe('CodingChatPanel first Conversation', () => {
await screen.findByRole('heading', { name: '今天,你想创造什么?' });
expect(projectApi.create).not.toHaveBeenCalled();
expect(conversationApi.abort).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: '查看归档对话' }));
fireEvent.click(screen.getByRole('button', { name: '打开归档列表' }));
expect(screen.getByText('已归档对话')).toBeVisible();
fireEvent.click(within(screen.getByTestId('project-conversations')).getByRole('button', { name: '新对话', exact: true }));
await screen.findByText('此对话已归档,任务仍在运行。');
expect(screen.queryByRole('button', { name: '发送', exact: true })).not.toBeInTheDocument();
@@ -541,6 +550,92 @@ describe('CodingChatPanel first Conversation', () => {
await waitFor(() => expect(screen.queryByRole('button', { name: '恢复对话' })).not.toBeInTheDocument());
expect(codingConversationStore.getState().selectedConversationId).toBe(conversation.id);
expect(projectApi.patch).toHaveBeenLastCalledWith(conversation.id, { archived: false });
expect(screen.queryByText('已归档对话')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: '返回最近对话' })).not.toBeInTheDocument();
expect(within(screen.getByTestId('project-conversations')).getByRole('button', { name: '新对话', exact: true })).toHaveAttribute('aria-current', 'page');
expect(conversationApi.abort).not.toHaveBeenCalled();
});
it('returns to recent conversations when restoring an unselected archived conversation from its menu', async () => {
const archivedConversation = {
...conversation,
id: 'archived-conversation',
title: '归档中的对话',
archivedAt: '2026-09-20T00:00:00Z',
};
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });
projectApi.conversations.mockResolvedValue([conversation, archivedConversation]);
projectApi.patch.mockResolvedValue({ ...archivedConversation, archivedAt: null });
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: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
const { codingConversationStore } = await import('@/stores/coding-conversations');
const { useState } = await import('react');
const CodingChatPanel = () => {
const [archived, setArchived] = useState(true);
return <>
<CodingProjectConversations projectId={project.id} archived={archived} onArchivedChange={setArchived} />
<CoreChatPanel />
</>;
};
render(<CodingChatPanel />);
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId).toBe(conversation.id));
const list = within(screen.getByTestId('project-conversations'));
expect(list.getByText('已归档对话')).toBeVisible();
expect(list.queryByRole('button', { name: conversation.title, exact: true })).not.toBeInTheDocument();
fireEvent.keyDown(list.getByRole('button', { name: `对话操作:${archivedConversation.title}` }), { key: 'Enter' });
fireEvent.click(await screen.findByRole('menuitem', { name: '恢复到最近对话' }));
await waitFor(() => expect(list.queryByText('已归档对话')).not.toBeInTheDocument());
expect(projectApi.patch).toHaveBeenCalledWith(archivedConversation.id, { archived: false });
expect(list.getByRole('button', { name: archivedConversation.title, exact: true })).toBeVisible();
expect(list.getByRole('button', { name: conversation.title, exact: true })).toHaveAttribute('aria-current', 'page');
expect(codingConversationStore.getState().selectedConversationId).toBe(conversation.id);
expect(conversationApi.snapshot).not.toHaveBeenCalledWith(archivedConversation.id);
expect(projectApi.create).not.toHaveBeenCalled();
});
it('keeps the new-conversation label visible in compact archive view and returns to recent conversations after creation', async () => {
const archivedConversation = { ...conversation, archivedAt: '2026-09-20T00:00:00Z' };
const createdConversation = { ...conversation, id: 'created-conversation', title: '新建的对话' };
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });
projectApi.conversations.mockResolvedValue([archivedConversation]);
projectApi.create.mockResolvedValue(createdConversation);
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, createdConversation));
const { CodingChatPanel: CoreChatPanel } = await import('@/pages/Chat/CodingChatPanel');
const { CodingProjectConversations } = await import('@/pages/Chat/CodingProjectConversations');
const { codingConversationStore } = await import('@/stores/coding-conversations');
const { useState } = await import('react');
const CodingChatPanel = () => {
const [archived, setArchived] = useState(true);
return <>
<CodingProjectConversations projectId={project.id} compact archived={archived} onArchivedChange={setArchived} />
<CoreChatPanel />
</>;
};
render(<CodingChatPanel />);
const list = within(await screen.findByTestId('project-conversations'));
const create = list.getByRole('button', { name: '新建对话', exact: true });
expect(create).toHaveTextContent('新对话');
expect(list.getByText('已归档对话')).toBeVisible();
expect(projectApi.create).not.toHaveBeenCalled();
fireEvent.click(create);
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId).toBe(createdConversation.id));
expect(projectApi.create).toHaveBeenCalledOnce();
expect(list.queryByText('已归档对话')).not.toBeInTheDocument();
expect(list.queryByRole('button', { name: '返回最近对话' })).not.toBeInTheDocument();
expect(list.queryByRole('button', { name: archivedConversation.title, exact: true })).not.toBeInTheDocument();
expect(list.getByRole('button', { name: createdConversation.title, exact: true })).toHaveAttribute('aria-current', 'page');
expect(conversationApi.submit).not.toHaveBeenCalled();
});
it('renames an unselected conversation from its menu without preparing that conversation', async () => {
@@ -959,7 +1054,7 @@ describe('CodingChatPanel first Conversation', () => {
expect(screen.queryByText('metadata rejected')).not.toBeInTheDocument();
});
it('keeps project settings only in the Conversation list and removes legacy Header actions', async () => {
it('removes project toolbar actions and legacy Header actions while keeping conversation creation', async () => {
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config: configForAgents([agent, reviewer]) });
projectApi.conversations.mockResolvedValue([conversation, reviewerConversation]);
@@ -986,10 +1081,12 @@ describe('CodingChatPanel first Conversation', () => {
expect(within(header).queryByRole('button', { name: /标为(已读|未读)/ })).not.toBeInTheDocument();
expect(within(header).queryByRole('button', { name: '归档' })).not.toBeInTheDocument();
expect(within(header).queryByRole('button', { name: '伙伴设置' })).not.toBeInTheDocument();
expect(screen.getAllByRole('button', { name: '项目设置' })).toHaveLength(1);
fireEvent.click(screen.getByRole('button', { name: '项目设置' }));
expect(navigate).toHaveBeenCalledWith('/project-config');
expect(screen.queryByRole('button', { name: '项目设置' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: '查看归档对话' })).not.toBeInTheDocument();
const list = within(screen.getByTestId('project-conversations'));
expect(list.getByRole('button', { name: '新建对话', exact: true })).toHaveTextContent('新对话');
expect(list.getByRole('button', { name: `对话操作:${conversation.title}` })).toBeInTheDocument();
expect(onOpenProjectSettings).not.toHaveBeenCalled();
});
it('remounts pending interaction state when switching Conversations', async () => {