mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Don't display todo in history (#11516)
This commit is contained in:
committed by
GitHub
parent
71ecc401c3
commit
35afab31e1
@@ -7,67 +7,15 @@
|
||||
import { render } from 'ink-testing-library';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Box } from 'ink';
|
||||
import { TodoTray, TodoListDisplay } from './Todo.js';
|
||||
import type { TodoList, TodoStatus } from '@google/gemini-cli-core';
|
||||
import { TodoTray } from './Todo.js';
|
||||
import type { Todo } from '@google/gemini-cli-core';
|
||||
import type { UIState } from '../../contexts/UIStateContext.js';
|
||||
import { UIStateContext } from '../../contexts/UIStateContext.js';
|
||||
import type { HistoryItem } from '../../types.js';
|
||||
import { ToolCallStatus } from '../../types.js';
|
||||
|
||||
describe('<TodoListDisplay />', () => {
|
||||
it('renders an empty todo list correctly', () => {
|
||||
const todos: TodoList = { todos: [] };
|
||||
const { lastFrame } = render(<TodoListDisplay todos={todos} />);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a todo list with various statuses correctly', () => {
|
||||
const todos: TodoList = {
|
||||
todos: [
|
||||
{ description: 'Task 1', status: 'pending' as TodoStatus },
|
||||
{ description: 'Task 2', status: 'in_progress' as TodoStatus },
|
||||
{ description: 'Task 3', status: 'completed' as TodoStatus },
|
||||
{ description: 'Task 4', status: 'cancelled' as TodoStatus },
|
||||
],
|
||||
};
|
||||
const { lastFrame } = render(<TodoListDisplay todos={todos} />);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a todo list with long descriptions that wrap', () => {
|
||||
const todos: TodoList = {
|
||||
todos: [
|
||||
{
|
||||
description:
|
||||
'This is a very long description for a pending task that should wrap around multiple lines when the terminal width is constrained.',
|
||||
status: 'pending' as TodoStatus,
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Another completed task with an equally verbose description to test wrapping behavior.',
|
||||
status: 'completed' as TodoStatus,
|
||||
},
|
||||
],
|
||||
};
|
||||
const { lastFrame } = render(
|
||||
<Box width="30">
|
||||
<TodoListDisplay todos={todos} />
|
||||
</Box>,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a single todo item', () => {
|
||||
const todos: TodoList = {
|
||||
todos: [{ description: 'Single task', status: 'pending' as TodoStatus }],
|
||||
};
|
||||
const { lastFrame } = render(<TodoListDisplay todos={todos} />);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('<TodoTray />', () => {
|
||||
const mockHistoryItem = {
|
||||
const createTodoHistoryItem = (todos: Todo[]): HistoryItem =>
|
||||
({
|
||||
type: 'tool_group',
|
||||
id: '1',
|
||||
tools: [
|
||||
@@ -76,15 +24,18 @@ describe('<TodoTray />', () => {
|
||||
callId: 'tool-1',
|
||||
status: ToolCallStatus.Success,
|
||||
resultDisplay: {
|
||||
todos: [
|
||||
{ description: 'Pending Task', status: 'pending' },
|
||||
{ description: 'In Progress Task', status: 'in_progress' },
|
||||
{ description: 'Completed Task', status: 'completed' },
|
||||
],
|
||||
todos,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as HistoryItem;
|
||||
}) as unknown as HistoryItem;
|
||||
|
||||
describe('<TodoTray />', () => {
|
||||
const mockHistoryItem = createTodoHistoryItem([
|
||||
{ description: 'Pending Task', status: 'pending' },
|
||||
{ description: 'In Progress Task', status: 'in_progress' },
|
||||
{ description: 'Completed Task', status: 'completed' },
|
||||
]);
|
||||
|
||||
const renderWithUiState = (uiState: Partial<UIState>) =>
|
||||
render(
|
||||
@@ -99,24 +50,11 @@ describe('<TodoTray />', () => {
|
||||
});
|
||||
|
||||
it('renders null when todos exist but none are in progress and full view is off', () => {
|
||||
const historyWithNoInProgress = {
|
||||
type: 'tool_group',
|
||||
id: '1',
|
||||
tools: [
|
||||
{
|
||||
name: 'write_todos_list',
|
||||
callId: 'tool-1',
|
||||
status: ToolCallStatus.Success,
|
||||
resultDisplay: {
|
||||
todos: [
|
||||
{ description: 'Pending Task', status: 'pending' },
|
||||
{ description: 'In Progress Task', status: 'cancelled' },
|
||||
{ description: 'Completed Task', status: 'completed' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as HistoryItem;
|
||||
const historyWithNoInProgress = createTodoHistoryItem([
|
||||
{ description: 'Pending Task', status: 'pending' },
|
||||
{ description: 'In Progress Task', status: 'cancelled' },
|
||||
{ description: 'Completed Task', status: 'completed' },
|
||||
]);
|
||||
const { lastFrame } = renderWithUiState({
|
||||
history: [historyWithNoInProgress],
|
||||
showFullTodos: false,
|
||||
@@ -124,6 +62,70 @@ describe('<TodoTray />', () => {
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders an empty todo list when full view is on', () => {
|
||||
const emptyTodosHistoryItem = createTodoHistoryItem([]);
|
||||
const { lastFrame } = renderWithUiState({
|
||||
history: [emptyTodosHistoryItem],
|
||||
showFullTodos: true,
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a todo list with various statuses when full view is on', () => {
|
||||
const variousTodosHistoryItem = createTodoHistoryItem([
|
||||
{ description: 'Task 1', status: 'pending' },
|
||||
{ description: 'Task 2', status: 'in_progress' },
|
||||
{ description: 'Task 3', status: 'completed' },
|
||||
{ description: 'Task 4', status: 'cancelled' },
|
||||
]);
|
||||
const { lastFrame } = renderWithUiState({
|
||||
history: [variousTodosHistoryItem],
|
||||
showFullTodos: true,
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a todo list with long descriptions that wrap when full view is on', () => {
|
||||
const longDescriptionTodosHistoryItem = createTodoHistoryItem([
|
||||
{
|
||||
description:
|
||||
'This is a very long description for a pending task that should wrap around multiple lines when the terminal width is constrained.',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
description:
|
||||
'Another completed task with an equally verbose description to test wrapping behavior.',
|
||||
status: 'completed',
|
||||
},
|
||||
]);
|
||||
const { lastFrame } = render(
|
||||
<Box width="30">
|
||||
<UIStateContext.Provider
|
||||
value={
|
||||
{
|
||||
history: [longDescriptionTodosHistoryItem],
|
||||
showFullTodos: true,
|
||||
} as UIState
|
||||
}
|
||||
>
|
||||
<TodoTray />
|
||||
</UIStateContext.Provider>
|
||||
</Box>,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders a single todo item when full view is on', () => {
|
||||
const singleTodoHistoryItem = createTodoHistoryItem([
|
||||
{ description: 'Single task', status: 'pending' },
|
||||
]);
|
||||
const { lastFrame } = renderWithUiState({
|
||||
history: [singleTodoHistoryItem],
|
||||
showFullTodos: true,
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders only the in-progress task when full view is off', () => {
|
||||
const { lastFrame } = renderWithUiState({
|
||||
history: [mockHistoryItem],
|
||||
|
||||
@@ -16,17 +16,6 @@ import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
import { useMemo } from 'react';
|
||||
import type { HistoryItemToolGroup } from '../../types.js';
|
||||
|
||||
const TodoItemDisplay: React.FC<{ todo: Todo }> = ({ todo }) => (
|
||||
<Box flexDirection="row">
|
||||
<Box marginRight={1}>
|
||||
<TodoStatusDisplay status={todo.status} />
|
||||
</Box>
|
||||
<Box flexShrink={1}>
|
||||
<Text color={theme.text.primary}>{todo.description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const TodoStatusDisplay: React.FC<{ status: TodoStatus }> = ({ status }) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
@@ -41,6 +30,17 @@ const TodoStatusDisplay: React.FC<{ status: TodoStatus }> = ({ status }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const TodoItemDisplay: React.FC<{ todo: Todo }> = ({ todo }) => (
|
||||
<Box flexDirection="row">
|
||||
<Box marginRight={1}>
|
||||
<TodoStatusDisplay status={todo.status} />
|
||||
</Box>
|
||||
<Box flexShrink={1}>
|
||||
<Text color={theme.text.primary}>{todo.description}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const TodoTray: React.FC = () => {
|
||||
const uiState = useUIState();
|
||||
|
||||
@@ -120,11 +120,11 @@ export const TodoTray: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export interface TodoListDisplayProps {
|
||||
interface TodoListDisplayProps {
|
||||
todos: TodoList;
|
||||
}
|
||||
|
||||
export const TodoListDisplay: React.FC<TodoListDisplayProps> = ({ todos }) => (
|
||||
const TodoListDisplay: React.FC<TodoListDisplayProps> = ({ todos }) => (
|
||||
<Box flexDirection="column">
|
||||
{todos.todos.map((todo: Todo, index: number) => (
|
||||
<TodoItemDisplay todo={todo} key={index} />
|
||||
|
||||
@@ -13,7 +13,6 @@ import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
||||
import { AnsiOutputText } from '../AnsiOutput.js';
|
||||
import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js';
|
||||
import { MaxSizedBox } from '../shared/MaxSizedBox.js';
|
||||
import { TodoListDisplay } from './Todo.js';
|
||||
import { ShellInputPrompt } from '../ShellInputPrompt.js';
|
||||
import {
|
||||
SHELL_COMMAND_NAME,
|
||||
@@ -21,7 +20,7 @@ import {
|
||||
TOOL_STATUS,
|
||||
} from '../../constants.js';
|
||||
import { theme } from '../../semantic-colors.js';
|
||||
import type { AnsiOutput, Config, TodoList } from '@google/gemini-cli-core';
|
||||
import type { AnsiOutput, Config } from '@google/gemini-cli-core';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
|
||||
const STATIC_HEIGHT = 1;
|
||||
@@ -173,7 +172,8 @@ export const ToolMessage: React.FC<ToolMessageProps> = ({
|
||||
/>
|
||||
) : typeof resultDisplay === 'object' &&
|
||||
'todos' in resultDisplay ? (
|
||||
<TodoListDisplay todos={resultDisplay as TodoList} />
|
||||
// display nothing, as the TodoTray will handle rendering todos
|
||||
<></>
|
||||
) : (
|
||||
<AnsiOutputText
|
||||
data={resultDisplay as AnsiOutput}
|
||||
|
||||
@@ -1,28 +1,48 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<TodoListDisplay /> > renders a single todo item 1`] = `"☐ Single task"`;
|
||||
|
||||
exports[`<TodoListDisplay /> > renders a todo list with long descriptions that wrap 1`] = `
|
||||
"☐ This is a very long
|
||||
description for a pending
|
||||
task that should wrap around
|
||||
multiple lines when the
|
||||
terminal width is
|
||||
constrained.
|
||||
✓ Another completed task with
|
||||
an equally verbose
|
||||
description to test wrapping
|
||||
behavior."
|
||||
exports[`<TodoTray /> > renders a single todo item when full view is on 1`] = `
|
||||
"┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📝 Todo:(ctrl+t to collapse) │
|
||||
│ │
|
||||
│ ☐ Single task │"
|
||||
`;
|
||||
|
||||
exports[`<TodoListDisplay /> > renders a todo list with various statuses correctly 1`] = `
|
||||
"☐ Task 1
|
||||
» Task 2
|
||||
✓ Task 3
|
||||
✗ Task 4"
|
||||
exports[`<TodoTray /> > renders a todo list with long descriptions that wrap when full view is on 1`] = `
|
||||
"┌────────────────────────────┐
|
||||
│ 📝 Todo:(ctrl+t to │
|
||||
│ collapse) │
|
||||
│ │
|
||||
│ ☐ This is a very long │
|
||||
│ description for a │
|
||||
│ pending task that │
|
||||
│ should wrap around │
|
||||
│ multiple lines when │
|
||||
│ the terminal width │
|
||||
│ is constrained. │
|
||||
│ │
|
||||
│ ✓ Another completed │
|
||||
│ task with an │
|
||||
│ equally verbose │
|
||||
│ description to │
|
||||
│ test wrapping │
|
||||
│ behavior. │"
|
||||
`;
|
||||
|
||||
exports[`<TodoListDisplay /> > renders an empty todo list correctly 1`] = `""`;
|
||||
exports[`<TodoTray /> > renders a todo list with various statuses when full view is on 1`] = `
|
||||
"┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📝 Todo:(ctrl+t to collapse) │
|
||||
│ │
|
||||
│ ☐ Task 1 │
|
||||
│ » Task 2 │
|
||||
│ ✓ Task 3 │
|
||||
│ ✗ Task 4 │"
|
||||
`;
|
||||
|
||||
exports[`<TodoTray /> > renders an empty todo list when full view is on 1`] = `
|
||||
"┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 📝 Todo:(ctrl+t to collapse) │
|
||||
│ │"
|
||||
`;
|
||||
|
||||
exports[`<TodoTray /> > renders null when no todos are in the history 1`] = `""`;
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<TodoListDisplay /> > renders a single todo item 1`] = `"☐ Single task"`;
|
||||
|
||||
exports[`<TodoListDisplay /> > renders a todo list with long descriptions that wrap 1`] = `
|
||||
"☐ This is a very long description for a
|
||||
pending task that should wrap around
|
||||
multiple lines when the terminal width
|
||||
is constrained.
|
||||
✓ Another completed task with an equally
|
||||
verbose description to test wrapping
|
||||
behavior."
|
||||
`;
|
||||
|
||||
exports[`<TodoListDisplay /> > renders a todo list with various statuses correctly 1`] = `
|
||||
"☐ Task 1
|
||||
» Task 2
|
||||
✓ Task 3
|
||||
✗ Task 4"
|
||||
`;
|
||||
|
||||
exports[`<TodoListDisplay /> > renders an empty todo list correctly 1`] = `""`;
|
||||
Reference in New Issue
Block a user