feat(ui): align AskUser color scheme with UX spec (#18943)

This commit is contained in:
Jerop Kipruto
2026-02-12 16:10:25 -05:00
committed by GitHub
parent 991b2c6002
commit e8e681c670
3 changed files with 147 additions and 4 deletions

View File

@@ -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);
});
});

View File

@@ -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 && (

View File

@@ -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 │