mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 07:01:09 -07:00
fix(cli): reset constrainHeight on ESC in tool confirmation (#21743)
This commit is contained in:
@@ -4,16 +4,18 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
|
||||
import type {
|
||||
SerializableConfirmationDetails,
|
||||
ToolCallConfirmationDetails,
|
||||
Config,
|
||||
import {
|
||||
type SerializableConfirmationDetails,
|
||||
type ToolCallConfirmationDetails,
|
||||
type Config,
|
||||
ToolConfirmationOutcome,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../../test-utils/settings.js';
|
||||
import { useToolActions } from '../../contexts/ToolActionsContext.js';
|
||||
import { act } from 'react';
|
||||
|
||||
vi.mock('../../contexts/ToolActionsContext.js', async (importOriginal) => {
|
||||
const actual =
|
||||
@@ -644,4 +646,125 @@ describe('ToolConfirmationMessage', () => {
|
||||
expect(output).not.toContain('Invocation Arguments:');
|
||||
unmount();
|
||||
});
|
||||
|
||||
describe('ESCAPE key behavior', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should call confirm(Cancel) and reset constrainHeight when ESC is pressed', async () => {
|
||||
const mockConfirm = vi.fn().mockResolvedValue(undefined);
|
||||
const mockSetConstrainHeight = vi.fn();
|
||||
const mockRefreshStatic = vi.fn();
|
||||
|
||||
vi.mocked(useToolActions).mockReturnValue({
|
||||
confirm: mockConfirm,
|
||||
cancel: vi.fn(),
|
||||
isDiffingEnabled: false,
|
||||
});
|
||||
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'info',
|
||||
title: 'Confirm Web Fetch',
|
||||
prompt: 'https://example.com',
|
||||
urls: ['https://example.com'],
|
||||
};
|
||||
|
||||
const { stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
confirmationDetails={confirmationDetails}
|
||||
config={mockConfig}
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
/>,
|
||||
{
|
||||
uiState: { constrainHeight: false },
|
||||
uiActions: {
|
||||
setConstrainHeight: mockSetConstrainHeight,
|
||||
refreshStatic: mockRefreshStatic,
|
||||
},
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
// Simulate ESC key
|
||||
stdin.write('\x1b');
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(mockConfirm).toHaveBeenCalledWith(
|
||||
'test-call-id',
|
||||
ToolConfirmationOutcome.Cancel,
|
||||
undefined,
|
||||
);
|
||||
expect(mockSetConstrainHeight).toHaveBeenCalledWith(true);
|
||||
expect(mockRefreshStatic).toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should NOT reset constrainHeight if it is already true when ESC is pressed', async () => {
|
||||
const mockConfirm = vi.fn().mockResolvedValue(undefined);
|
||||
const mockSetConstrainHeight = vi.fn();
|
||||
const mockRefreshStatic = vi.fn();
|
||||
|
||||
vi.mocked(useToolActions).mockReturnValue({
|
||||
confirm: mockConfirm,
|
||||
cancel: vi.fn(),
|
||||
isDiffingEnabled: false,
|
||||
});
|
||||
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'info',
|
||||
title: 'Confirm Web Fetch',
|
||||
prompt: 'https://example.com',
|
||||
urls: ['https://example.com'],
|
||||
};
|
||||
|
||||
const { stdin, waitUntilReady, unmount } = renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
confirmationDetails={confirmationDetails}
|
||||
config={mockConfig}
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
/>,
|
||||
{
|
||||
uiState: { constrainHeight: true },
|
||||
uiActions: {
|
||||
setConstrainHeight: mockSetConstrainHeight,
|
||||
refreshStatic: mockRefreshStatic,
|
||||
},
|
||||
},
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
// Simulate ESC key
|
||||
stdin.write('\x1b');
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
});
|
||||
|
||||
expect(mockConfirm).toHaveBeenCalledWith(
|
||||
'test-call-id',
|
||||
ToolConfirmationOutcome.Cancel,
|
||||
undefined,
|
||||
);
|
||||
expect(mockSetConstrainHeight).not.toHaveBeenCalled();
|
||||
expect(mockRefreshStatic).not.toHaveBeenCalled();
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { useMemo, useCallback, useState } from 'react';
|
||||
import { useEffect, useMemo, useCallback, useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { DiffRenderer } from './DiffRenderer.js';
|
||||
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import type { RadioSelectItem } from '../shared/RadioButtonSelect.js';
|
||||
import { useToolActions } from '../../contexts/ToolActionsContext.js';
|
||||
import { RadioButtonSelect } from '../shared/RadioButtonSelect.js';
|
||||
import { useUIActions } from '../../contexts/UIActionsContext.js';
|
||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||
import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js';
|
||||
import {
|
||||
sanitizeForDisplay,
|
||||
@@ -70,6 +72,8 @@ export const ToolConfirmationMessage: React.FC<
|
||||
}) => {
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { confirm, isDiffingEnabled } = useToolActions();
|
||||
const uiActions = useUIActions();
|
||||
const { constrainHeight } = useUIState();
|
||||
const [mcpDetailsExpansionState, setMcpDetailsExpansionState] = useState<{
|
||||
callId: string;
|
||||
expanded: boolean;
|
||||
@@ -77,6 +81,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
callId,
|
||||
expanded: false,
|
||||
});
|
||||
const [isCancelling, setIsCancelling] = useState(false);
|
||||
const isMcpToolDetailsExpanded =
|
||||
mcpDetailsExpansionState.callId === callId
|
||||
? mcpDetailsExpansionState.expanded
|
||||
@@ -179,7 +184,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
return true;
|
||||
}
|
||||
if (keyMatchers[Command.ESCAPE](key)) {
|
||||
handleConfirm(ToolConfirmationOutcome.Cancel);
|
||||
setIsCancelling(true);
|
||||
return true;
|
||||
}
|
||||
if (keyMatchers[Command.QUIT](key)) {
|
||||
@@ -192,6 +197,16 @@ export const ToolConfirmationMessage: React.FC<
|
||||
{ isActive: isFocused, priority: true },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isCancelling) {
|
||||
if (!constrainHeight) {
|
||||
uiActions.setConstrainHeight(true);
|
||||
uiActions.refreshStatic();
|
||||
}
|
||||
handleConfirm(ToolConfirmationOutcome.Cancel);
|
||||
}
|
||||
}, [isCancelling, constrainHeight, uiActions, handleConfirm]);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(item: ToolConfirmationOutcome) => handleConfirm(item),
|
||||
[handleConfirm],
|
||||
|
||||
Reference in New Issue
Block a user