mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 10:34:35 -07:00
139ef0d5bd
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
180 lines
5.2 KiB
TypeScript
180 lines
5.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { cleanup, renderWithProviders } from '../test-utils/render.js';
|
|
import { createMockSettings } from '../test-utils/settings.js';
|
|
import { App } from './App.js';
|
|
import {
|
|
CoreToolCallStatus,
|
|
ApprovalMode,
|
|
makeFakeConfig,
|
|
} from '@google/gemini-cli-core';
|
|
import { type UIState } from './contexts/UIStateContext.js';
|
|
import type { SerializableConfirmationDetails } from '@google/gemini-cli-core';
|
|
import { act } from 'react';
|
|
import { StreamingState } from './types.js';
|
|
|
|
vi.mock('ink', async (importOriginal) => {
|
|
const original = await importOriginal<typeof import('ink')>();
|
|
return {
|
|
...original,
|
|
useIsScreenReaderEnabled: vi.fn(() => false),
|
|
};
|
|
});
|
|
|
|
vi.mock('./components/GeminiSpinner.js', () => ({
|
|
GeminiSpinner: () => null,
|
|
}));
|
|
|
|
vi.mock('./components/CliSpinner.js', () => ({
|
|
CliSpinner: () => null,
|
|
}));
|
|
|
|
// Mock hooks to align with codebase style, even if App uses UIState directly
|
|
vi.mock('./hooks/useGeminiStream.js');
|
|
vi.mock('./hooks/useHistoryManager.js');
|
|
vi.mock('./hooks/useQuotaAndFallback.js');
|
|
vi.mock('./hooks/useThemeCommand.js');
|
|
vi.mock('./auth/useAuth.js');
|
|
vi.mock('./hooks/useEditorSettings.js');
|
|
vi.mock('./hooks/useSettingsCommand.js');
|
|
vi.mock('./hooks/useModelCommand.js');
|
|
vi.mock('./hooks/slashCommandProcessor.js');
|
|
vi.mock('./hooks/useConsoleMessages.js');
|
|
vi.mock('./hooks/useTerminalSize.js', () => ({
|
|
useTerminalSize: vi.fn(() => ({ columns: 100, rows: 30 })),
|
|
}));
|
|
|
|
describe('Full Terminal Tool Confirmation Snapshot', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('renders tool confirmation box in the frame of the entire terminal', async () => {
|
|
// Generate a large diff to warrant truncation
|
|
let largeDiff =
|
|
'--- a/packages/cli/src/ui/components/InputPrompt.tsx\n+++ b/packages/cli/src/ui/components/InputPrompt.tsx\n@@ -1,100 +1,105 @@\n';
|
|
for (let i = 1; i <= 60; i++) {
|
|
largeDiff += ` const line${i} = true;\n`;
|
|
}
|
|
largeDiff += '- return kittyProtocolSupporte...;\n';
|
|
largeDiff += '+ return kittyProtocolSupporte...;\n';
|
|
largeDiff += ' buffer: TextBuffer;\n';
|
|
largeDiff += ' onSubmit: (value: string) => void;';
|
|
|
|
const confirmationDetails: SerializableConfirmationDetails = {
|
|
type: 'edit',
|
|
title: 'Edit packages/.../InputPrompt.tsx',
|
|
fileName: 'InputPrompt.tsx',
|
|
filePath: 'packages/.../InputPrompt.tsx',
|
|
fileDiff: largeDiff,
|
|
originalContent: 'old',
|
|
newContent: 'new',
|
|
isModifying: false,
|
|
};
|
|
|
|
const toolCalls = [
|
|
{
|
|
callId: 'call-1-modify-selected',
|
|
name: 'Edit',
|
|
description:
|
|
'packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProtocolSupporte...',
|
|
status: CoreToolCallStatus.AwaitingApproval,
|
|
resultDisplay: '',
|
|
confirmationDetails,
|
|
},
|
|
];
|
|
|
|
const mockUIState = {
|
|
history: [
|
|
{
|
|
id: 1,
|
|
type: 'user',
|
|
text: 'Can you edit InputPrompt.tsx for me?',
|
|
},
|
|
],
|
|
mainAreaWidth: 99,
|
|
availableTerminalHeight: 36,
|
|
streamingState: StreamingState.WaitingForConfirmation,
|
|
constrainHeight: true,
|
|
isConfigInitialized: true,
|
|
cleanUiDetailsVisible: true,
|
|
quota: {
|
|
userTier: 'PRO',
|
|
stats: {
|
|
limits: {},
|
|
usage: {},
|
|
},
|
|
proQuotaRequest: null,
|
|
validationRequest: null,
|
|
},
|
|
pendingHistoryItems: [
|
|
{
|
|
id: 2,
|
|
type: 'tool_group',
|
|
tools: toolCalls,
|
|
},
|
|
],
|
|
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
|
sessionStats: {
|
|
lastPromptTokenCount: 175400,
|
|
contextPercentage: 3,
|
|
},
|
|
buffer: { text: '' },
|
|
messageQueue: [],
|
|
activeHooks: [],
|
|
contextFileNames: [],
|
|
rootUiRef: { current: null },
|
|
} as unknown as UIState;
|
|
|
|
const mockConfig = makeFakeConfig();
|
|
mockConfig.getUseAlternateBuffer = () => true;
|
|
mockConfig.isTrustedFolder = () => true;
|
|
mockConfig.getDisableAlwaysAllow = () => false;
|
|
mockConfig.getIdeMode = () => false;
|
|
mockConfig.getTargetDir = () => '/directory';
|
|
|
|
const { waitUntilReady, lastFrame, generateSvg, unmount } =
|
|
await renderWithProviders(<App />, {
|
|
uiState: mockUIState,
|
|
config: mockConfig,
|
|
settings: createMockSettings({
|
|
merged: {
|
|
ui: {
|
|
useAlternateBuffer: true,
|
|
theme: 'default',
|
|
showUserIdentity: false,
|
|
showShortcutsHint: false,
|
|
footer: {
|
|
hideContextPercentage: false,
|
|
hideTokens: false,
|
|
hideModel: false,
|
|
},
|
|
},
|
|
security: {
|
|
enablePermanentToolApproval: true,
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
|
|
await waitUntilReady();
|
|
|
|
// Give it a moment to render
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
});
|
|
|
|
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
|
unmount();
|
|
});
|
|
});
|