diff --git a/packages/cli/src/ui/components/agent-view/AgentComposer.test.tsx b/packages/cli/src/ui/components/agent-view/AgentComposer.test.tsx new file mode 100644 index 00000000000..5b746e0559c --- /dev/null +++ b/packages/cli/src/ui/components/agent-view/AgentComposer.test.tsx @@ -0,0 +1,102 @@ +/** + * @license + * Copyright 2026 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from 'ink-testing-library'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { AgentStatus } from '@qwen-code/qwen-code-core'; +import { + useAgentViewActions, + useAgentViewState, +} from '../../contexts/AgentViewContext.js'; +import { useConfig } from '../../contexts/ConfigContext.js'; +import { useAgentStreamingState } from '../../hooks/useAgentStreamingState.js'; +import { useKeypress } from '../../hooks/useKeypress.js'; +import { usePreferredEditor } from '../../hooks/usePreferredEditor.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; +import { StreamingState } from '../../types.js'; +import { useTextBuffer } from '../shared/text-buffer.js'; +import { AgentComposer } from './AgentComposer.js'; + +vi.mock('../../contexts/AgentViewContext.js'); +vi.mock('../../contexts/ConfigContext.js'); +vi.mock('../../hooks/useAgentStreamingState.js'); +vi.mock('../../hooks/useKeypress.js'); +vi.mock('../../hooks/usePreferredEditor.js'); +vi.mock('../../hooks/useTerminalSize.js'); +vi.mock('../shared/text-buffer.js'); +vi.mock('../BaseTextInput.js', () => ({ BaseTextInput: () => null })); +vi.mock('../LoadingIndicator.js', () => ({ LoadingIndicator: () => null })); +vi.mock('../QueuedMessageDisplay.js', () => ({ + QueuedMessageDisplay: () => null, +})); +vi.mock('./AgentFooter.js', () => ({ AgentFooter: () => null })); + +describe('AgentComposer', () => { + const setAgentInputBufferText = vi.fn(); + const setAgentTabBarFocused = vi.fn(); + const setAgentApprovalMode = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + + vi.mocked(useAgentViewState).mockReturnValue({ + activeView: 'agent-1', + agents: new Map([ + [ + 'agent-1', + { + modelId: 'qwen', + color: 'cyan', + interactiveAgent: { + cancelCurrentRound: vi.fn(), + enqueueMessage: vi.fn(), + getError: vi.fn(), + getLastRoundError: vi.fn(), + }, + }, + ], + ]), + agentShellFocused: false, + agentInputBufferText: '', + agentTabBarFocused: false, + agentApprovalModes: new Map(), + } as never); + vi.mocked(useAgentViewActions).mockReturnValue({ + setAgentInputBufferText, + setAgentTabBarFocused, + setAgentApprovalMode, + } as never); + vi.mocked(useConfig).mockReturnValue({ + getContentGeneratorConfig: () => undefined, + } as never); + vi.mocked(usePreferredEditor).mockReturnValue(undefined); + vi.mocked(useTerminalSize).mockReturnValue({ columns: 80, rows: 24 }); + vi.mocked(useKeypress).mockImplementation(() => {}); + vi.mocked(useAgentStreamingState).mockReturnValue({ + status: AgentStatus.IDLE, + streamingState: StreamingState.Idle, + isInputActive: true, + elapsedTime: 0, + lastPromptTokenCount: 0, + } as never); + vi.mocked(useTextBuffer).mockReturnValue({ + text: 'draft', + allVisualLines: ['draft'], + visualCursor: [0, 5], + } as never); + }); + + it('does not reset the parent input-buffer state during unmount', () => { + const { unmount } = render(); + + expect(setAgentInputBufferText).toHaveBeenCalledWith('draft'); + setAgentInputBufferText.mockClear(); + + unmount(); + + expect(setAgentInputBufferText).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/cli/src/ui/components/agent-view/AgentComposer.tsx b/packages/cli/src/ui/components/agent-view/AgentComposer.tsx index a2f6ac0536c..ed0557a03c0 100644 --- a/packages/cli/src/ui/components/agent-view/AgentComposer.tsx +++ b/packages/cli/src/ui/components/agent-view/AgentComposer.tsx @@ -133,10 +133,9 @@ export const AgentComposer: React.FC = ({ agentId }) => { preferredEditor, }); - // Sync agent buffer text to context so AgentTabBar can guard tab switching + // Sync the active agent buffer text to context. useEffect(() => { setAgentInputBufferText(buffer.text); - return () => setAgentInputBufferText(''); }, [buffer.text, setAgentInputBufferText]); // When agent input is not active (agent running, completed, etc.), diff --git a/packages/cli/src/ui/contexts/AgentViewContext.tsx b/packages/cli/src/ui/contexts/AgentViewContext.tsx index f414db5d892..49437b283a7 100644 --- a/packages/cli/src/ui/contexts/AgentViewContext.tsx +++ b/packages/cli/src/ui/contexts/AgentViewContext.tsx @@ -48,7 +48,7 @@ export interface AgentViewState { agents: ReadonlyMap; /** Whether any agent tab's embedded shell currently has input focus. */ agentShellFocused: boolean; - /** Current text in the active agent tab's input buffer (empty when on main). */ + /** Last synced text from the active agent tab's input buffer. */ agentInputBufferText: string; /** Whether the tab bar has keyboard focus (vs the agent input). */ agentTabBarFocused: boolean;