Fix /chat list not write terminal escape codes directly (#10415)

This commit is contained in:
Adam Weidman
2025-10-03 00:52:16 +02:00
committed by GitHub
parent fcdfa8609a
commit 16d4701822
7 changed files with 171 additions and 99 deletions
@@ -29,6 +29,7 @@ import { ExtensionsList } from './views/ExtensionsList.js';
import { getMCPServerStatus } from '@google/gemini-cli-core';
import { ToolsList } from './views/ToolsList.js';
import { McpStatus } from './views/McpStatus.js';
import { ChatList } from './views/ChatList.js';
interface HistoryItemDisplayProps {
item: HistoryItem;
@@ -140,6 +141,9 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
{itemForDisplay.type === 'mcp_status' && (
<McpStatus {...itemForDisplay} serverStatus={getMCPServerStatus} />
)}
{itemForDisplay.type === 'chat_list' && (
<ChatList chats={itemForDisplay.chats} />
)}
</Box>
);
};
@@ -0,0 +1,46 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { render } from 'ink-testing-library';
import { describe, it, expect } from 'vitest';
import { ChatList } from './ChatList.js';
import type { ChatDetail } from '../../types.js';
const mockChats: ChatDetail[] = [
{
name: 'chat-1',
mtime: '2025-10-02T10:00:00.000Z',
},
{
name: 'another-chat',
mtime: '2025-10-01T12:30:00.000Z',
},
];
describe('<ChatList />', () => {
it('renders correctly with a list of chats', () => {
const { lastFrame } = render(<ChatList chats={mockChats} />);
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly with no chats', () => {
const { lastFrame } = render(<ChatList chats={[]} />);
expect(lastFrame()).toContain('No saved conversation checkpoints found.');
expect(lastFrame()).toMatchSnapshot();
});
it('handles invalid date formats gracefully', () => {
const mockChatsWithInvalidDate: ChatDetail[] = [
{
name: 'bad-date-chat',
mtime: 'an-invalid-date-string',
},
];
const { lastFrame } = render(<ChatList chats={mockChatsWithInvalidDate} />);
expect(lastFrame()).toContain('(Invalid Date)');
expect(lastFrame()).toMatchSnapshot();
});
});
@@ -0,0 +1,46 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import type { ChatDetail } from '../../types.js';
interface ChatListProps {
chats: readonly ChatDetail[];
}
export const ChatList: React.FC<ChatListProps> = ({ chats }) => {
if (chats.length === 0) {
return <Text>No saved conversation checkpoints found.</Text>;
}
return (
<Box flexDirection="column">
<Text>List of saved conversations:</Text>
<Box height={1} />
{chats.map((chat) => {
const isoString = chat.mtime;
const match = isoString.match(
/(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/,
);
const formattedDate = match
? `${match[1]} ${match[2]}`
: 'Invalid Date';
return (
<Box key={chat.name} flexDirection="row">
<Text>
{' '}- <Text color={theme.text.accent}>{chat.name}</Text>{' '}
<Text color={theme.text.secondary}>({formattedDate})</Text>
</Text>
</Box>
);
})}
<Box height={1} />
<Text color={theme.text.secondary}>Note: Newest last, oldest first</Text>
</Box>
);
};
@@ -0,0 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<ChatList /> > handles invalid date formats gracefully 1`] = `
"List of saved conversations:
- bad-date-chat (Invalid Date)
Note: Newest last, oldest first"
`;
exports[`<ChatList /> > renders correctly with a list of chats 1`] = `
"List of saved conversations:
- chat-1 (2025-10-02 10:00:00)
- another-chat (2025-10-01 12:30:00)
Note: Newest last, oldest first"
`;
exports[`<ChatList /> > renders correctly with no chats 1`] = `"No saved conversation checkpoints found."`;