Files
gemini-cli/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx

135 lines
3.7 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import {
ShellToolMessage,
type ShellToolMessageProps,
} from './ShellToolMessage.js';
import { StreamingState, ToolCallStatus } from '../../types.js';
import { Text } from 'ink';
import type { Config } from '@google/gemini-cli-core';
import { renderWithProviders } from '../../../test-utils/render.js';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SHELL_TOOL_NAME } from '@google/gemini-cli-core';
import { SHELL_COMMAND_NAME } from '../../constants.js';
import { StreamingContext } from '../../contexts/StreamingContext.js';
vi.mock('../TerminalOutput.js', () => ({
TerminalOutput: function MockTerminalOutput({
cursor,
}: {
cursor: { x: number; y: number } | null;
}) {
return (
<Text>
MockCursor:({cursor?.x},{cursor?.y})
</Text>
);
},
}));
// Mock child components or utilities if they are complex or have side effects
vi.mock('../GeminiRespondingSpinner.js', () => ({
GeminiRespondingSpinner: ({
nonRespondingDisplay,
}: {
nonRespondingDisplay?: string;
}) => {
const streamingState = React.useContext(StreamingContext)!;
if (streamingState === StreamingState.Responding) {
return <Text>MockRespondingSpinner</Text>;
}
return nonRespondingDisplay ? <Text>{nonRespondingDisplay}</Text> : null;
},
}));
vi.mock('../../utils/MarkdownDisplay.js', () => ({
MarkdownDisplay: function MockMarkdownDisplay({ text }: { text: string }) {
return <Text>MockMarkdown:{text}</Text>;
},
}));
describe('<ShellToolMessage />', () => {
const baseProps: ShellToolMessageProps = {
callId: 'tool-123',
name: SHELL_COMMAND_NAME,
description: 'A shell command',
resultDisplay: 'Test result',
status: ToolCallStatus.Executing,
terminalWidth: 80,
confirmationDetails: undefined,
emphasis: 'medium',
isFirst: true,
borderColor: 'green',
borderDimColor: false,
config: {
getEnableInteractiveShell: () => true,
} as unknown as Config,
};
const mockSetEmbeddedShellFocused = vi.fn();
const uiActions = {
setEmbeddedShellFocused: mockSetEmbeddedShellFocused,
};
beforeEach(() => {
vi.clearAllMocks();
});
describe('interactive shell focus', () => {
const shellProps: ShellToolMessageProps = {
...baseProps,
};
it('clicks inside the shell area sets focus to true', async () => {
const { stdin, lastFrame, simulateClick } = renderWithProviders(
<ShellToolMessage {...shellProps} />,
{
mouseEventsEnabled: true,
uiActions,
},
);
await waitFor(() => {
expect(lastFrame()).toContain('A shell command'); // Wait for render
});
await simulateClick(stdin, 2, 2); // Click at column 2, row 2 (1-based)
await waitFor(() => {
expect(mockSetEmbeddedShellFocused).toHaveBeenCalledWith(true);
});
});
it('handles focus for SHELL_TOOL_NAME (core shell tool)', async () => {
const coreShellProps: ShellToolMessageProps = {
...shellProps,
name: SHELL_TOOL_NAME,
};
const { stdin, lastFrame, simulateClick } = renderWithProviders(
<ShellToolMessage {...coreShellProps} />,
{
mouseEventsEnabled: true,
uiActions,
},
);
await waitFor(() => {
expect(lastFrame()).toContain('A shell command');
});
await simulateClick(stdin, 2, 2);
await waitFor(() => {
expect(mockSetEmbeddedShellFocused).toHaveBeenCalledWith(true);
});
});
});
});