mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
feat(ui): align AskUser color scheme with UX spec (#18943)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { Box } from 'ink';
|
||||
import { ToolConfirmationQueue } from './ToolConfirmationQueue.js';
|
||||
import { ToolCallStatus, StreamingState } from '../types.js';
|
||||
@@ -12,6 +12,31 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor } from '../../test-utils/async.js';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import type { ConfirmingToolState } from '../hooks/useConfirmingTool.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
vi.mock('./StickyHeader.js', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('./StickyHeader.js')>();
|
||||
return {
|
||||
...actual,
|
||||
StickyHeader: vi.fn((props) => actual.StickyHeader(props)),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
validatePlanPath: vi.fn().mockResolvedValue(undefined),
|
||||
validatePlanContent: vi.fn().mockResolvedValue(undefined),
|
||||
processSingleFileContent: vi.fn().mockResolvedValue({
|
||||
llmContent: 'Plan content goes here',
|
||||
error: undefined,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const { StickyHeader } = await import('./StickyHeader.js');
|
||||
|
||||
describe('ToolConfirmationQueue', () => {
|
||||
const mockConfig = {
|
||||
@@ -19,8 +44,19 @@ describe('ToolConfirmationQueue', () => {
|
||||
getIdeMode: () => false,
|
||||
getModel: () => 'gemini-pro',
|
||||
getDebugMode: () => false,
|
||||
getTargetDir: () => '/mock/target/dir',
|
||||
getFileSystemService: () => ({
|
||||
readFile: vi.fn().mockResolvedValue('Plan content'),
|
||||
}),
|
||||
storage: {
|
||||
getProjectTempPlansDir: () => '/mock/temp/plans',
|
||||
},
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders the confirming tool with progress indicator', () => {
|
||||
const confirmingTool = {
|
||||
tool: {
|
||||
@@ -60,6 +96,9 @@ describe('ToolConfirmationQueue', () => {
|
||||
expect(output).toContain('list files'); // Tool description
|
||||
expect(output).toContain("Allow execution of: 'ls'?");
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
const stickyHeaderProps = vi.mocked(StickyHeader).mock.calls[0][0];
|
||||
expect(stickyHeaderProps.borderColor).toBe(theme.status.warning);
|
||||
});
|
||||
|
||||
it('returns null if tool has no confirmation details', () => {
|
||||
@@ -229,4 +268,80 @@ describe('ToolConfirmationQueue', () => {
|
||||
expect(output).not.toContain('Press ctrl-o to show more lines');
|
||||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders AskUser tool confirmation with Success color', () => {
|
||||
const confirmingTool = {
|
||||
tool: {
|
||||
callId: 'call-1',
|
||||
name: 'ask_user',
|
||||
description: 'ask user',
|
||||
status: ToolCallStatus.Confirming,
|
||||
confirmationDetails: {
|
||||
type: 'ask_user' as const,
|
||||
questions: [],
|
||||
onConfirm: vi.fn(),
|
||||
},
|
||||
},
|
||||
index: 1,
|
||||
total: 1,
|
||||
};
|
||||
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ToolConfirmationQueue
|
||||
confirmingTool={confirmingTool as unknown as ConfirmingToolState}
|
||||
/>,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState: {
|
||||
terminalWidth: 80,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
const stickyHeaderProps = vi.mocked(StickyHeader).mock.calls[0][0];
|
||||
expect(stickyHeaderProps.borderColor).toBe(theme.status.success);
|
||||
});
|
||||
|
||||
it('renders ExitPlanMode tool confirmation with Success color', async () => {
|
||||
const confirmingTool = {
|
||||
tool: {
|
||||
callId: 'call-1',
|
||||
name: 'exit_plan_mode',
|
||||
description: 'exit plan mode',
|
||||
status: ToolCallStatus.Confirming,
|
||||
confirmationDetails: {
|
||||
type: 'exit_plan_mode' as const,
|
||||
planPath: '/path/to/plan',
|
||||
onConfirm: vi.fn(),
|
||||
},
|
||||
},
|
||||
index: 1,
|
||||
total: 1,
|
||||
};
|
||||
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ToolConfirmationQueue
|
||||
confirmingTool={confirmingTool as unknown as ConfirmingToolState}
|
||||
/>,
|
||||
{
|
||||
config: mockConfig,
|
||||
uiState: {
|
||||
terminalWidth: 80,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(lastFrame()).toContain('Plan content goes here');
|
||||
});
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
const stickyHeaderProps = vi.mocked(StickyHeader).mock.calls[0][0];
|
||||
expect(stickyHeaderProps.borderColor).toBe(theme.status.success);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,10 +70,11 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
? Math.max(maxHeight - 6, 4)
|
||||
: undefined;
|
||||
|
||||
const borderColor = theme.status.warning;
|
||||
const hideToolIdentity =
|
||||
const isRoutine =
|
||||
tool.confirmationDetails?.type === 'ask_user' ||
|
||||
tool.confirmationDetails?.type === 'exit_plan_mode';
|
||||
const borderColor = isRoutine ? theme.status.success : theme.status.warning;
|
||||
const hideToolIdentity = isRoutine;
|
||||
|
||||
return (
|
||||
<OverflowProvider>
|
||||
@@ -90,7 +91,7 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
marginBottom={hideToolIdentity ? 0 : 1}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Text color={theme.status.warning} bold>
|
||||
<Text color={borderColor} bold>
|
||||
{getConfirmationHeader(tool.confirmationDetails)}
|
||||
</Text>
|
||||
{total > 1 && (
|
||||
|
||||
@@ -40,6 +40,33 @@ exports[`ToolConfirmationQueue > does not render expansion hint when constrainHe
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > renders AskUser tool confirmation with Success color 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Answer Questions │
|
||||
│ │
|
||||
│ Review your answers: │
|
||||
│ │
|
||||
│ │
|
||||
│ Enter to submit · Tab/Shift+Tab to edit answers · Esc to cancel │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > renders ExitPlanMode tool confirmation with Success color 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Ready to start implementation? │
|
||||
│ │
|
||||
│ Plan content goes here │
|
||||
│ │
|
||||
│ ● 1. Yes, automatically accept edits │
|
||||
│ Approves plan and allows tools to run automatically │
|
||||
│ 2. Yes, manually accept edits │
|
||||
│ Approves plan but requires confirmation for each tool │
|
||||
│ 3. Type your feedback... │
|
||||
│ │
|
||||
│ Enter to select · ↑/↓ to navigate · Esc to cancel │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > renders expansion hint when content is long and constrained 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Action Required │
|
||||
|
||||
Reference in New Issue
Block a user