refactor(ui): extract QueuedMessageDisplay into separate component (#8374)

Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com>
This commit is contained in:
Akhil Appana
2025-09-18 11:54:09 -07:00
committed by GitHub
parent 2c754d71e3
commit 92c99d7873
4 changed files with 144 additions and 52 deletions

View File

@@ -65,6 +65,21 @@ vi.mock('./ShowMoreLines.js', () => ({
ShowMoreLines: () => <Text>ShowMoreLines</Text>,
}));
vi.mock('./QueuedMessageDisplay.js', () => ({
QueuedMessageDisplay: ({ messageQueue }: { messageQueue: string[] }) => {
if (messageQueue.length === 0) {
return null;
}
return (
<>
{messageQueue.map((message, index) => (
<Text key={index}>{message}</Text>
))}
</>
);
},
}));
// 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
});
});

View File

@@ -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 && <ConfigInitDisplay />}
{uiState.messageQueue.length > 0 && (
<Box flexDirection="column" marginTop={1}>
{uiState.messageQueue
.slice(0, MAX_DISPLAYED_QUEUED_MESSAGES)
.map((message, index) => {
const preview = message.replace(/\s+/g, ' ');
return (
<Box key={index} paddingLeft={2} width="100%">
<Text dimColor wrap="truncate">
{preview}
</Text>
</Box>
);
})}
{uiState.messageQueue.length > MAX_DISPLAYED_QUEUED_MESSAGES && (
<Box paddingLeft={2}>
<Text dimColor>
... (+
{uiState.messageQueue.length -
MAX_DISPLAYED_QUEUED_MESSAGES}{' '}
more)
</Text>
</Box>
)}
</Box>
)}
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
<Box
marginTop={1}

View File

@@ -0,0 +1,76 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect } from 'vitest';
import { render } from 'ink-testing-library';
import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
describe('QueuedMessageDisplay', () => {
it('renders nothing when message queue is empty', () => {
const { lastFrame } = render(<QueuedMessageDisplay messageQueue={[]} />);
expect(lastFrame()).toBe('');
});
it('displays single queued message', () => {
const { lastFrame } = render(
<QueuedMessageDisplay messageQueue={['First message']} />,
);
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(
<QueuedMessageDisplay messageQueue={messageQueue} />,
);
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(
<QueuedMessageDisplay messageQueue={messageQueue} />,
);
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(
<QueuedMessageDisplay messageQueue={messageQueue} />,
);
const output = lastFrame();
expect(output).toContain('Message with multiple whitespace');
});
});

View File

@@ -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 (
<Box flexDirection="column" marginTop={1}>
{messageQueue
.slice(0, MAX_DISPLAYED_QUEUED_MESSAGES)
.map((message, index) => {
const preview = message.replace(/\s+/g, ' ');
return (
<Box key={index} paddingLeft={2} width="100%">
<Text dimColor wrap="truncate">
{preview}
</Text>
</Box>
);
})}
{messageQueue.length > MAX_DISPLAYED_QUEUED_MESSAGES && (
<Box paddingLeft={2}>
<Text dimColor>
... (+
{messageQueue.length - MAX_DISPLAYED_QUEUED_MESSAGES} more)
</Text>
</Box>
)}
</Box>
);
};