fix: toggle the teacher dock from the conversation header
This commit is contained in:
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { AppError } from '@/lib/error-model';
|
||||
import type { ConversationSnapshot } from '@/types/coding-conversation';
|
||||
import type { TeacherDefinition, TeacherTopic } from '../../shared/coding-teacher';
|
||||
import type {
|
||||
CodingConversationMetadata,
|
||||
CodingProjectAgent,
|
||||
@@ -320,6 +321,93 @@ describe('CodingChatPanel first Conversation', () => {
|
||||
expect(conversationApi.submit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('toggles the teacher dock from the header without changing tabs, drafts, history, or sending prompts', async () => {
|
||||
localStorage.clear();
|
||||
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);
|
||||
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
|
||||
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
|
||||
const definition: TeacherDefinition = {
|
||||
schema_version: 1, config_id: 'teacher-collapse', teacher_id: 'teacher', name: '设计老师',
|
||||
description: '', avatar_id: 'avatar-01', welcome_message: '聊聊吧', suggested_questions: [],
|
||||
system_prompt: '', skills: [], model: { model_id: 'model', reasoning_choice: { mode: 'default' } },
|
||||
limits: { max_input_tokens: 8000, max_output_tokens: 1000 },
|
||||
};
|
||||
const topic: TeacherTopic = {
|
||||
schemaVersion: 1, revision: 1, id: 'teacher-history', accountId: 'signed-out',
|
||||
projectId: project.id, sourceConversationId: conversation.id, definition, version: 1,
|
||||
createdAt: '2026-09-29T00:00:00.000Z', updatedAt: '2026-09-29T00:00:00.000Z',
|
||||
requests: [{
|
||||
id: 'teacher-reply-1', text: '怎样让规则更清楚?', references: [],
|
||||
createdAt: '2026-09-29T00:00:00.000Z', sourceCursor: { seq: 0, workerGeneration: 0 },
|
||||
sourceCapturedAt: '2026-09-29T00:00:00.000Z', includedSourceMessageIds: [], omittedMessages: 0,
|
||||
status: 'completed', response: '先用一句话说明获胜条件。',
|
||||
projectId: project.id, sourceConversationId: conversation.id, teacherVersion: 1,
|
||||
}],
|
||||
conversation: { agentId: 'teacher-collapse', segmentTurns: 1, discussions: {}, seenThrough: 'teacher-reply-1' },
|
||||
};
|
||||
const { teacherApi } = await import('@/lib/coding-teacher');
|
||||
vi.spyOn(teacherApi, 'config').mockResolvedValue({ enabled: true, published_version: 1, revision: 1, definition });
|
||||
vi.spyOn(teacherApi, 'catalog').mockResolvedValue({ items: [{ teacher_id: 'teacher-collapse', version: 1, definition, is_default: true }] });
|
||||
vi.spyOn(teacherApi, 'conversation').mockResolvedValue({ topic, before: null });
|
||||
vi.spyOn(teacherApi, 'events').mockResolvedValue(new FakeEventSource() as unknown as EventSource);
|
||||
const consultationSend = vi.spyOn(teacherApi, 'sendConversation');
|
||||
const legacySend = vi.spyOn(teacherApi, 'send');
|
||||
const consultationCreate = vi.spyOn(teacherApi, 'create');
|
||||
const browserApi = await import('@/lib/agent-browser');
|
||||
const closedBrowser = {
|
||||
browserId: null, projectId: project.id, projectPath: null, state: 'closed' as const,
|
||||
generation: 0, url: '', title: '', visible: false, bounds: null,
|
||||
canGoBack: false, canGoForward: false, eventCursor: 0,
|
||||
};
|
||||
vi.spyOn(browserApi, 'getAgentBrowserState').mockResolvedValue(closedBrowser);
|
||||
vi.spyOn(browserApi, 'closeAgentBrowser').mockResolvedValue(closedBrowser);
|
||||
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
|
||||
render(<CodingChatPanel />);
|
||||
const composer = within(await screen.findByTestId('coding-message-composer')).getByRole('textbox');
|
||||
fireEvent.change(composer, { target: { value: '操作对话还没写完' } });
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '智能体', exact: true })).toHaveTextContent('设计老师'));
|
||||
const chatTab = screen.getByRole('tab', { name: '操作对话' });
|
||||
const workTab = screen.getByRole('tab', { name: '作品', exact: true });
|
||||
const toggle = screen.getByRole('button', { name: '展开老师对话栏' });
|
||||
expect(toggle).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(toggle).toHaveAttribute('aria-controls', 'coding-consultation-dock');
|
||||
|
||||
fireEvent.click(toggle);
|
||||
expect(screen.getByRole('button', { name: '收起老师对话栏' })).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(document.getElementById('coding-consultation-dock')).toBeVisible();
|
||||
expect(chatTab).toHaveAttribute('aria-selected', 'true');
|
||||
expect(composer).toBeVisible();
|
||||
expect(await screen.findByText('先用一句话说明获胜条件。')).toBeVisible();
|
||||
const teacherInput = screen.getByRole('textbox', { name: '向智能体提问' });
|
||||
await waitFor(() => expect(teacherInput).toBeEnabled());
|
||||
fireEvent.change(teacherInput, { target: { value: '老师对话也还没写完' } });
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '收起老师对话栏' }));
|
||||
expect(document.getElementById('coding-consultation-dock')).toBeNull();
|
||||
expect(screen.getByRole('button', { name: '展开老师对话栏' })).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(chatTab).toHaveAttribute('aria-selected', 'true');
|
||||
expect(composer).toHaveValue('操作对话还没写完');
|
||||
|
||||
fireEvent.click(workTab);
|
||||
fireEvent.click(screen.getByRole('button', { name: '展开老师对话栏' }));
|
||||
expect(workTab).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.getByRole('tabpanel', { name: '作品', exact: true })).toBeVisible();
|
||||
expect(screen.getByRole('textbox', { name: '向智能体提问' })).toHaveValue('老师对话也还没写完');
|
||||
expect(await screen.findByText('先用一句话说明获胜条件。')).toBeVisible();
|
||||
fireEvent.click(screen.getByRole('button', { name: '收起老师对话栏' }));
|
||||
expect(workTab).toHaveAttribute('aria-selected', 'true');
|
||||
expect(screen.queryByRole('textbox', { name: '向智能体提问' })).not.toBeInTheDocument();
|
||||
fireEvent.click(chatTab);
|
||||
expect(composer).toHaveValue('操作对话还没写完');
|
||||
expect(consultationCreate).not.toHaveBeenCalled();
|
||||
expect(consultationSend).not.toHaveBeenCalled();
|
||||
expect(legacySend).not.toHaveBeenCalled();
|
||||
expect(conversationApi.submit).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('archives the last conversation without creating another, then restores the same running conversation', async () => {
|
||||
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
|
||||
projectApi.config.mockResolvedValue({ project, config });
|
||||
|
||||
Reference in New Issue
Block a user