mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
refactor(ui): extract QueuedMessageDisplay into separate component (#8374)
Co-authored-by: Abhi <43648792+abhipatel12@users.noreply.github.com>
This commit is contained in:
@@ -65,6 +65,21 @@ vi.mock('./ShowMoreLines.js', () => ({
|
|||||||
ShowMoreLines: () => <Text>ShowMoreLines</Text>,
|
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
|
// Mock contexts
|
||||||
vi.mock('../contexts/OverflowContext.js', () => ({
|
vi.mock('../contexts/OverflowContext.js', () => ({
|
||||||
OverflowProvider: ({ children }: { children: React.ReactNode }) => children,
|
OverflowProvider: ({ children }: { children: React.ReactNode }) => children,
|
||||||
@@ -289,36 +304,17 @@ describe('Composer', () => {
|
|||||||
expect(output).toContain('Third queued message');
|
expect(output).toContain('Third queued message');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows overflow indicator when more than 3 messages are queued', () => {
|
it('renders QueuedMessageDisplay with empty message queue', () => {
|
||||||
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', () => {
|
|
||||||
const uiState = createMockUIState({
|
const uiState = createMockUIState({
|
||||||
messageQueue: [],
|
messageQueue: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { lastFrame } = renderComposer(uiState);
|
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();
|
const output = lastFrame();
|
||||||
expect(output).not.toContain('more)');
|
expect(output).toContain('InputPrompt'); // Verify basic Composer rendering
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { DetailedMessagesDisplay } from './DetailedMessagesDisplay.js';
|
|||||||
import { InputPrompt, calculatePromptWidths } from './InputPrompt.js';
|
import { InputPrompt, calculatePromptWidths } from './InputPrompt.js';
|
||||||
import { Footer, type FooterProps } from './Footer.js';
|
import { Footer, type FooterProps } from './Footer.js';
|
||||||
import { ShowMoreLines } from './ShowMoreLines.js';
|
import { ShowMoreLines } from './ShowMoreLines.js';
|
||||||
|
import { QueuedMessageDisplay } from './QueuedMessageDisplay.js';
|
||||||
import { OverflowProvider } from '../contexts/OverflowContext.js';
|
import { OverflowProvider } from '../contexts/OverflowContext.js';
|
||||||
import { theme } from '../semantic-colors.js';
|
import { theme } from '../semantic-colors.js';
|
||||||
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
|
||||||
@@ -27,8 +28,6 @@ import { ApprovalMode } from '@google/gemini-cli-core';
|
|||||||
import { StreamingState } from '../types.js';
|
import { StreamingState } from '../types.js';
|
||||||
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
import { ConfigInitDisplay } from '../components/ConfigInitDisplay.js';
|
||||||
|
|
||||||
const MAX_DISPLAYED_QUEUED_MESSAGES = 3;
|
|
||||||
|
|
||||||
export const Composer = () => {
|
export const Composer = () => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
@@ -89,33 +88,7 @@ export const Composer = () => {
|
|||||||
|
|
||||||
{!uiState.isConfigInitialized && <ConfigInitDisplay />}
|
{!uiState.isConfigInitialized && <ConfigInitDisplay />}
|
||||||
|
|
||||||
{uiState.messageQueue.length > 0 && (
|
<QueuedMessageDisplay messageQueue={uiState.messageQueue} />
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
marginTop={1}
|
marginTop={1}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user