diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx
index c9b7dd0a52..6d1f5372c5 100644
--- a/packages/cli/src/ui/components/Composer.test.tsx
+++ b/packages/cli/src/ui/components/Composer.test.tsx
@@ -65,6 +65,21 @@ vi.mock('./ShowMoreLines.js', () => ({
ShowMoreLines: () => ShowMoreLines,
}));
+vi.mock('./QueuedMessageDisplay.js', () => ({
+ QueuedMessageDisplay: ({ messageQueue }: { messageQueue: string[] }) => {
+ if (messageQueue.length === 0) {
+ return null;
+ }
+ return (
+ <>
+ {messageQueue.map((message, index) => (
+ {message}
+ ))}
+ >
+ );
+ },
+}));
+
// Mock contexts
vi.mock('../contexts/OverflowContext.js', () => ({
OverflowProvider: ({ children }: { children: React.ReactNode }) => children,
@@ -289,36 +304,17 @@ describe('Composer', () => {
expect(output).toContain('Third queued message');
});
- it('shows overflow indicator when more than 3 messages are queued', () => {
- const uiState = createMockUIState({
- messageQueue: [
- 'Message 1',
- 'Message 2',
- 'Message 3',
- 'Message 4',
- 'Message 5',
- ],
- });
-
- const { lastFrame } = renderComposer(uiState);
-
- const output = lastFrame();
- expect(output).toContain('Message 1');
- expect(output).toContain('Message 2');
- expect(output).toContain('Message 3');
- expect(output).toContain('... (+2 more)');
- });
-
- it('does not display message queue section when empty', () => {
+ it('renders QueuedMessageDisplay with empty message queue', () => {
const uiState = createMockUIState({
messageQueue: [],
});
const { lastFrame } = renderComposer(uiState);
- // Should not contain queued message indicators
+ // The component should render but return null for empty queue
+ // This test verifies that the component receives the correct prop
const output = lastFrame();
- expect(output).not.toContain('more)');
+ expect(output).toContain('InputPrompt'); // Verify basic Composer rendering
});
});
diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx
index db7255afb0..37c095c6d1 100644
--- a/packages/cli/src/ui/components/Composer.tsx
+++ b/packages/cli/src/ui/components/Composer.tsx
@@ -14,6 +14,7 @@ import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
import { InputPrompt, calculatePromptWidths } from './InputPrompt.js';
import { Footer, type FooterProps } from './Footer.js';
import { ShowMoreLines } from './ShowMoreLines.js';
+import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
import { OverflowProvider } from '../contexts/OverflowContext.js';
import { theme } from '../semantic-colors.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
@@ -27,8 +28,6 @@ import { ApprovalMode } from '@google/gemini-cli-core';
import { StreamingState } from '../types.js';
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
-const MAX_DISPLAYED_QUEUED_MESSAGES = 3;
-
export const Composer = () => {
const config = useConfig();
const settings = useSettings();
@@ -89,33 +88,7 @@ export const Composer = () => {
{!uiState.isConfigInitialized && }
- {uiState.messageQueue.length > 0 && (
-
- {uiState.messageQueue
- .slice(0, MAX_DISPLAYED_QUEUED_MESSAGES)
- .map((message, index) => {
- const preview = message.replace(/\s+/g, ' ');
-
- return (
-
-
- {preview}
-
-
- );
- })}
- {uiState.messageQueue.length > MAX_DISPLAYED_QUEUED_MESSAGES && (
-
-
- ... (+
- {uiState.messageQueue.length -
- MAX_DISPLAYED_QUEUED_MESSAGES}{' '}
- more)
-
-
- )}
-
- )}
+
{
+ it('renders nothing when message queue is empty', () => {
+ const { lastFrame } = render();
+
+ expect(lastFrame()).toBe('');
+ });
+
+ it('displays single queued message', () => {
+ const { lastFrame } = render(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('First message');
+ });
+
+ it('displays multiple queued messages', () => {
+ const messageQueue = [
+ 'First queued message',
+ 'Second queued message',
+ 'Third queued message',
+ ];
+
+ const { lastFrame } = render(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('First queued message');
+ expect(output).toContain('Second queued message');
+ expect(output).toContain('Third queued message');
+ });
+
+ it('shows overflow indicator when more than 3 messages are queued', () => {
+ const messageQueue = [
+ 'Message 1',
+ 'Message 2',
+ 'Message 3',
+ 'Message 4',
+ 'Message 5',
+ ];
+
+ const { lastFrame } = render(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Message 1');
+ expect(output).toContain('Message 2');
+ expect(output).toContain('Message 3');
+ expect(output).toContain('... (+2 more)');
+ expect(output).not.toContain('Message 4');
+ expect(output).not.toContain('Message 5');
+ });
+
+ it('normalizes whitespace in messages', () => {
+ const messageQueue = ['Message with\tmultiple\n whitespace'];
+
+ const { lastFrame } = render(
+ ,
+ );
+
+ const output = lastFrame();
+ expect(output).toContain('Message with multiple whitespace');
+ });
+});
diff --git a/packages/cli/src/ui/components/QueuedMessageDisplay.tsx b/packages/cli/src/ui/components/QueuedMessageDisplay.tsx
new file mode 100644
index 0000000000..a42e9feab1
--- /dev/null
+++ b/packages/cli/src/ui/components/QueuedMessageDisplay.tsx
@@ -0,0 +1,47 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { Box, Text } from 'ink';
+
+const MAX_DISPLAYED_QUEUED_MESSAGES = 3;
+
+export interface QueuedMessageDisplayProps {
+ messageQueue: string[];
+}
+
+export const QueuedMessageDisplay = ({
+ messageQueue,
+}: QueuedMessageDisplayProps) => {
+ if (messageQueue.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {messageQueue
+ .slice(0, MAX_DISPLAYED_QUEUED_MESSAGES)
+ .map((message, index) => {
+ const preview = message.replace(/\s+/g, ' ');
+
+ return (
+
+
+ {preview}
+
+
+ );
+ })}
+ {messageQueue.length > MAX_DISPLAYED_QUEUED_MESSAGES && (
+
+
+ ... (+
+ {messageQueue.length - MAX_DISPLAYED_QUEUED_MESSAGES} more)
+
+
+ )}
+
+ );
+};