feat(ux) Expandable (ctrl-O) and scrollable approvals in alternate buffer mode. (#17640)

This commit is contained in:
Jacob Richman
2026-01-27 16:06:24 -08:00
committed by GitHub
parent ff6547857e
commit d165b6d4e7
34 changed files with 1177 additions and 496 deletions

View File

@@ -8,7 +8,6 @@ import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest';
import type React from 'react';
import { renderWithProviders } from '../test-utils/render.js';
import { Text, useIsScreenReaderEnabled, type DOMElement } from 'ink';
import { type Config } from '@google/gemini-cli-core';
import { App } from './App.js';
import { type UIState } from './contexts/UIStateContext.js';
import { StreamingState, ToolCallStatus } from './types.js';
@@ -22,10 +21,6 @@ vi.mock('ink', async (importOriginal) => {
};
});
vi.mock('./components/MainContent.js', () => ({
MainContent: () => <Text>MainContent</Text>,
}));
vi.mock('./components/DialogManager.js', () => ({
DialogManager: () => <Text>DialogManager</Text>,
}));
@@ -34,9 +29,16 @@ vi.mock('./components/Composer.js', () => ({
Composer: () => <Text>Composer</Text>,
}));
vi.mock('./components/Notifications.js', () => ({
Notifications: () => <Text>Notifications</Text>,
}));
vi.mock('./components/Notifications.js', async () => {
const { Text, Box } = await import('ink');
return {
Notifications: () => (
<Box>
<Text>Notifications</Text>
</Box>
),
};
});
vi.mock('./components/QuittingDisplay.js', () => ({
QuittingDisplay: () => <Text>Quitting...</Text>,
@@ -46,9 +48,16 @@ vi.mock('./components/HistoryItemDisplay.js', () => ({
HistoryItemDisplay: () => <Text>HistoryItemDisplay</Text>,
}));
vi.mock('./components/Footer.js', () => ({
Footer: () => <Text>Footer</Text>,
}));
vi.mock('./components/Footer.js', async () => {
const { Text, Box } = await import('ink');
return {
Footer: () => (
<Box>
<Text>Footer</Text>
</Box>
),
};
});
describe('App', () => {
beforeEach(() => {
@@ -87,7 +96,7 @@ describe('App', () => {
useAlternateBuffer: false,
});
expect(lastFrame()).toContain('MainContent');
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('Composer');
});
@@ -133,7 +142,7 @@ describe('App', () => {
uiState: dialogUIState,
});
expect(lastFrame()).toContain('MainContent');
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('DialogManager');
});
@@ -165,10 +174,10 @@ describe('App', () => {
uiState: mockUIState,
});
expect(lastFrame()).toContain(`Notifications
Footer
MainContent
Composer`);
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('Footer');
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Composer');
});
it('should render DefaultAppLayout when screen reader is not enabled', () => {
@@ -178,9 +187,9 @@ Composer`);
uiState: mockUIState,
});
expect(lastFrame()).toContain(`MainContent
Notifications
Composer`);
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('Composer');
});
it('should render ToolConfirmationQueue along with Composer when tool is confirming and experiment is on', () => {
@@ -209,19 +218,20 @@ Composer`);
pendingGeminiHistoryItems: [{ type: 'tool_group', tools: toolCalls }],
} as UIState;
const configWithExperiment = {
...makeFakeConfig(),
isEventDrivenSchedulerEnabled: () => true,
isTrustedFolder: () => true,
getIdeMode: () => false,
} as unknown as Config;
const configWithExperiment = makeFakeConfig();
vi.spyOn(
configWithExperiment,
'isEventDrivenSchedulerEnabled',
).mockReturnValue(true);
vi.spyOn(configWithExperiment, 'isTrustedFolder').mockReturnValue(true);
vi.spyOn(configWithExperiment, 'getIdeMode').mockReturnValue(false);
const { lastFrame } = renderWithProviders(<App />, {
uiState: stateWithConfirmingTool,
config: configWithExperiment,
});
expect(lastFrame()).toContain('MainContent');
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('Action Required'); // From ToolConfirmationQueue
expect(lastFrame()).toContain('1 of 1');