Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions packages/cli/src/ui/components/agent-view/AgentComposer.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<AgentComposer agentId="agent-1" />);

expect(setAgentInputBufferText).toHaveBeenCalledWith('draft');
setAgentInputBufferText.mockClear();

unmount();

expect(setAgentInputBufferText).not.toHaveBeenCalled();
});
});
3 changes: 1 addition & 2 deletions packages/cli/src/ui/components/agent-view/AgentComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,9 @@ export const AgentComposer: React.FC<AgentComposerProps> = ({ 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.),
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/contexts/AgentViewContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface AgentViewState {
agents: ReadonlyMap<string, RegisteredAgent>;
/** 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;
Expand Down
Loading