2025-11-19 15:49:39 -08:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright 2025 Google LLC
|
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2026-02-06 10:36:14 -08:00
|
|
|
import React from 'react';
|
2025-11-19 15:49:39 -08:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|