diff --git a/packages/cli/src/interactiveCli.tsx b/packages/cli/src/interactiveCli.tsx
index a27cdbbb78..a6337ef29c 100644
--- a/packages/cli/src/interactiveCli.tsx
+++ b/packages/cli/src/interactiveCli.tsx
@@ -101,18 +101,8 @@ export async function startInteractiveUI(
return (
-
-
+
+
diff --git a/packages/cli/src/test-utils/AppRig.tsx b/packages/cli/src/test-utils/AppRig.tsx
index 6043c7f8cc..39a896a3f8 100644
--- a/packages/cli/src/test-utils/AppRig.tsx
+++ b/packages/cli/src/test-utils/AppRig.tsx
@@ -204,6 +204,7 @@ export class AppRig {
enableEventDrivenScheduler: true,
extensionLoader: new MockExtensionManager(),
excludeTools: this.options.configOverrides?.excludeTools,
+ useAlternateBuffer: false,
...this.options.configOverrides,
};
this.config = makeFakeConfig(configParams);
@@ -275,6 +276,9 @@ export class AppRig {
enabled: false,
hasSeenNudge: true,
},
+ ui: {
+ useAlternateBuffer: false,
+ },
},
});
}
@@ -410,7 +414,6 @@ export class AppRig {
config: this.config!,
settings: this.settings!,
width: this.options.terminalWidth ?? 120,
- useAlternateBuffer: false,
uiState: {
terminalHeight: this.options.terminalHeight ?? 40,
},
diff --git a/packages/cli/src/test-utils/mockCommandContext.ts b/packages/cli/src/test-utils/mockCommandContext.ts
index 47e56e1a44..b153aaf85e 100644
--- a/packages/cli/src/test-utils/mockCommandContext.ts
+++ b/packages/cli/src/test-utils/mockCommandContext.ts
@@ -37,14 +37,14 @@ export const createMockCommandContext = (
},
services: {
config: null,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
settings: {
merged: defaultMergedSettings,
setValue: vi.fn(),
forScope: vi.fn().mockReturnValue({ settings: {} }),
} as unknown as LoadedSettings,
git: undefined as GitService | undefined,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
+
logger: {
log: vi.fn(),
logMessage: vi.fn(),
@@ -53,7 +53,7 @@ export const createMockCommandContext = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any, // Cast because Logger is a class.
},
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
+
ui: {
addItem: vi.fn(),
clear: vi.fn(),
@@ -72,7 +72,7 @@ export const createMockCommandContext = (
} as any,
session: {
sessionShellAllowlist: new Set(),
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
stats: {
sessionStartTime: new Date(),
lastPromptTokenCount: 0,
@@ -93,14 +93,12 @@ export const createMockCommandContext = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const merge = (target: any, source: any): any => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const output = { ...target };
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const sourceValue = source[key];
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+
const targetValue = output[key];
if (
@@ -108,11 +106,10 @@ export const createMockCommandContext = (
Object.prototype.toString.call(sourceValue) === '[object Object]' &&
Object.prototype.toString.call(targetValue) === '[object Object]'
) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
output[key] = merge(targetValue, sourceValue);
} else {
// If not, we do a direct assignment. This preserves Date objects and others.
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+
output[key] = sourceValue;
}
}
@@ -120,6 +117,5 @@ export const createMockCommandContext = (
return output;
};
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return merge(defaultMocks, overrides);
};
diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx
index 74bac044c4..ede4fd6a5c 100644
--- a/packages/cli/src/test-utils/render.tsx
+++ b/packages/cli/src/test-utils/render.tsx
@@ -18,7 +18,7 @@ import type React from 'react';
import { act, useState } from 'react';
import os from 'node:os';
import path from 'node:path';
-import { LoadedSettings } from '../config/settings.js';
+import type { LoadedSettings } from '../config/settings.js';
import { KeypressProvider } from '../ui/contexts/KeypressContext.js';
import { SettingsContext } from '../ui/contexts/SettingsContext.js';
import { ShellFocusContext } from '../ui/contexts/ShellFocusContext.js';
@@ -416,11 +416,10 @@ export const render = (
stdout.clear();
act(() => {
instance = inkRenderDirect(tree, {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
stdout: stdout as unknown as NodeJS.WriteStream,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
stderr: stderr as unknown as NodeJS.WriteStream,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
stdin: stdin as unknown as NodeJS.ReadStream,
debug: false,
exitOnCtrlC: false,
@@ -499,7 +498,6 @@ const getMockConfigInternal = (): Config => {
return mockConfigInternal;
};
-// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const configProxy = new Proxy({} as Config, {
get(_target, prop) {
if (prop === 'getTargetDir') {
@@ -526,21 +524,13 @@ const configProxy = new Proxy({} as Config, {
}
const internal = getMockConfigInternal();
if (prop in internal) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return internal[prop as keyof typeof internal];
}
throw new Error(`mockConfig does not have property ${String(prop)}`);
},
});
-export const mockSettings = new LoadedSettings(
- { path: '', settings: {}, originalSettings: {} },
- { path: '', settings: {}, originalSettings: {} },
- { path: '', settings: {}, originalSettings: {} },
- { path: '', settings: {}, originalSettings: {} },
- true,
- [],
-);
+export const mockSettings = createMockSettings();
// A minimal mock UIState to satisfy the context provider.
// Tests that need specific UIState values should provide their own.
@@ -657,9 +647,8 @@ export const renderWithProviders = (
uiState: providedUiState,
width,
mouseEventsEnabled = false,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
config = configProxy as unknown as Config,
- useAlternateBuffer = true,
uiActions,
persistentState,
appState = mockAppState,
@@ -670,7 +659,6 @@ export const renderWithProviders = (
width?: number;
mouseEventsEnabled?: boolean;
config?: Config;
- useAlternateBuffer?: boolean;
uiActions?: Partial;
persistentState?: {
get?: typeof persistentStateMock.get;
@@ -685,20 +673,17 @@ export const renderWithProviders = (
button?: 0 | 1 | 2,
) => Promise;
} => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const baseState: UIState = new Proxy(
{ ...baseMockUiState, ...providedUiState },
{
get(target, prop) {
if (prop in target) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return target[prop as keyof typeof target];
}
// For properties not in the base mock or provided state,
// we'll check the original proxy to see if it's a defined but
// unprovided property, and if not, throw.
if (prop in baseMockUiState) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return baseMockUiState[prop as keyof typeof baseMockUiState];
}
throw new Error(`mockUiState does not have property ${String(prop)}`);
@@ -716,31 +701,8 @@ export const renderWithProviders = (
persistentStateMock.mockClear();
const terminalWidth = width ?? baseState.terminalWidth;
- let finalSettings = settings;
- if (useAlternateBuffer !== undefined) {
- finalSettings = createMockSettings({
- ...settings.merged,
- ui: {
- ...settings.merged.ui,
- useAlternateBuffer,
- },
- });
- }
-
- // Wrap config in a Proxy so useAlternateBuffer hook (which reads from Config) gets the correct value,
- // without replacing the entire config object and its other values.
- let finalConfig = config;
- if (useAlternateBuffer !== undefined) {
- finalConfig = new Proxy(config, {
- get(target, prop, receiver) {
- if (prop === 'getUseAlternateBuffer') {
- return () => useAlternateBuffer;
- }
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return Reflect.get(target, prop, receiver);
- },
- });
- }
+ const finalSettings = settings;
+ const finalConfig = config;
const mainAreaWidth = terminalWidth;
@@ -768,7 +730,7 @@ export const renderWithProviders = (
capturedOverflowState = undefined;
capturedOverflowActions = undefined;
- const renderResult = render(
+ const wrapWithProviders = (comp: React.ReactElement) => (
@@ -803,7 +765,7 @@ export const renderWithProviders = (
flexGrow={0}
flexDirection="column"
>
- {component}
+ {comp}
@@ -821,12 +783,16 @@ export const renderWithProviders = (
- ,
- terminalWidth,
+
);
+ const renderResult = render(wrapWithProviders(component), terminalWidth);
+
return {
...renderResult,
+ rerender: (newComponent: React.ReactElement) => {
+ renderResult.rerender(wrapWithProviders(newComponent));
+ },
capturedOverflowState,
capturedOverflowActions,
simulateClick: (col: number, row: number, button?: 0 | 1 | 2) =>
@@ -847,9 +813,8 @@ export function renderHook(
waitUntilReady: () => Promise;
generateSvg: () => string;
} {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const result = { current: undefined as unknown as Result };
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
let currentProps = options?.initialProps as Props;
function TestComponent({
@@ -884,7 +849,6 @@ export function renderHook(
function rerender(props?: Props) {
if (arguments.length > 0) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
currentProps = props as Props;
}
act(() => {
@@ -911,7 +875,6 @@ export function renderHookWithProviders(
width?: number;
mouseEventsEnabled?: boolean;
config?: Config;
- useAlternateBuffer?: boolean;
} = {},
): {
result: { current: Result };
@@ -920,7 +883,6 @@ export function renderHookWithProviders(
waitUntilReady: () => Promise;
generateSvg: () => string;
} {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const result = { current: undefined as unknown as Result };
let setPropsFn: ((props: Props) => void) | undefined;
@@ -942,7 +904,7 @@ export function renderHookWithProviders(
act(() => {
renderResult = renderWithProviders(
- {/* eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion */}
+ {}
,
options,
@@ -952,7 +914,6 @@ export function renderHookWithProviders(
function rerender(newProps?: Props) {
act(() => {
if (arguments.length > 0 && setPropsFn) {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
setPropsFn(newProps as Props);
} else if (forceUpdateFn) {
forceUpdateFn();
diff --git a/packages/cli/src/test-utils/settings.ts b/packages/cli/src/test-utils/settings.ts
index dd498b6625..ab2420849d 100644
--- a/packages/cli/src/test-utils/settings.ts
+++ b/packages/cli/src/test-utils/settings.ts
@@ -46,23 +46,22 @@ export const createMockSettings = (
workspace,
isTrusted,
errors,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+
merged: mergedOverride,
...settingsOverrides
} = overrides;
const loaded = new LoadedSettings(
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(system as any) || { path: '', settings: {}, originalSettings: {} },
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
(systemDefaults as any) || { path: '', settings: {}, originalSettings: {} },
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
(user as any) || {
path: '',
settings: settingsOverrides,
originalSettings: settingsOverrides,
},
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
+
(workspace as any) || { path: '', settings: {}, originalSettings: {} },
isTrusted ?? true,
errors || [],
@@ -76,7 +75,6 @@ export const createMockSettings = (
// Assign any function overrides (e.g., vi.fn() for methods)
for (const key in overrides) {
if (typeof overrides[key] === 'function') {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment
(loaded as any)[key] = overrides[key];
}
}
diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx
index d96bfe3071..969e8b23aa 100644
--- a/packages/cli/src/ui/App.test.tsx
+++ b/packages/cli/src/ui/App.test.tsx
@@ -7,6 +7,7 @@
import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest';
import type React from 'react';
import { renderWithProviders } from '../test-utils/render.js';
+import { createMockSettings } from '../test-utils/settings.js';
import { Text, useIsScreenReaderEnabled, type DOMElement } from 'ink';
import { App } from './App.js';
import { type UIState } from './contexts/UIStateContext.js';
@@ -97,7 +98,10 @@ describe('App', () => {
,
{
uiState: mockUIState,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
);
await waitUntilReady();
@@ -118,7 +122,10 @@ describe('App', () => {
,
{
uiState: quittingUIState,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
);
await waitUntilReady();
@@ -139,7 +146,10 @@ describe('App', () => {
,
{
uiState: quittingUIState,
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -159,6 +169,10 @@ describe('App', () => {
,
{
uiState: dialogUIState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -185,6 +199,10 @@ describe('App', () => {
,
{
uiState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -201,6 +219,10 @@ describe('App', () => {
,
{
uiState: mockUIState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -219,6 +241,10 @@ describe('App', () => {
,
{
uiState: mockUIState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -265,7 +291,7 @@ describe('App', () => {
],
} as UIState;
- const configWithExperiment = makeFakeConfig();
+ const configWithExperiment = makeFakeConfig({ useAlternateBuffer: true });
vi.spyOn(configWithExperiment, 'isTrustedFolder').mockReturnValue(true);
vi.spyOn(configWithExperiment, 'getIdeMode').mockReturnValue(false);
@@ -274,6 +300,9 @@ describe('App', () => {
{
uiState: stateWithConfirmingTool,
config: configWithExperiment,
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -293,6 +322,10 @@ describe('App', () => {
,
{
uiState: mockUIState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -306,6 +339,10 @@ describe('App', () => {
,
{
uiState: mockUIState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
@@ -322,6 +359,10 @@ describe('App', () => {
,
{
uiState: dialogUIState,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx
index 13550d3f42..26ee1a87c1 100644
--- a/packages/cli/src/ui/AppContainer.test.tsx
+++ b/packages/cli/src/ui/AppContainer.test.tsx
@@ -95,7 +95,8 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
};
});
import ansiEscapes from 'ansi-escapes';
-import { mergeSettings, type LoadedSettings } from '../config/settings.js';
+import { type LoadedSettings } from '../config/settings.js';
+import { createMockSettings } from '../test-utils/settings.js';
import type { InitializationResult } from '../core/initializer.js';
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
import { StreamingState } from './types.js';
@@ -484,23 +485,20 @@ describe('AppContainer State Management', () => {
);
// Mock LoadedSettings
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- mockSettings = {
+ mockSettings = createMockSettings({
merged: {
- ...defaultMergedSettings,
hideBanner: false,
hideFooter: false,
hideTips: false,
showMemoryUsage: false,
theme: 'default',
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: false,
hideWindowTitle: false,
useAlternateBuffer: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock InitializationResult
mockInitResult = {
@@ -1008,16 +1006,14 @@ describe('AppContainer State Management', () => {
describe('Settings Integration', () => {
it('handles settings with all display options disabled', async () => {
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const settingsAllHidden = {
+ const settingsAllHidden = createMockSettings({
merged: {
- ...defaultMergedSettings,
hideBanner: true,
hideFooter: true,
hideTips: true,
showMemoryUsage: false,
},
- } as unknown as LoadedSettings;
+ });
let unmount: () => void;
await act(async () => {
@@ -1029,16 +1025,11 @@ describe('AppContainer State Management', () => {
});
it('handles settings with memory usage enabled', async () => {
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const settingsWithMemory = {
+ const settingsWithMemory = createMockSettings({
merged: {
- ...defaultMergedSettings,
- hideBanner: false,
- hideFooter: false,
- hideTips: false,
showMemoryUsage: true,
},
- } as unknown as LoadedSettings;
+ });
let unmount: () => void;
await act(async () => {
@@ -1078,9 +1069,7 @@ describe('AppContainer State Management', () => {
});
it('handles undefined settings gracefully', async () => {
- const undefinedSettings = {
- merged: mergeSettings({}, {}, {}, {}, true),
- } as LoadedSettings;
+ const undefinedSettings = createMockSettings();
let unmount: () => void;
await act(async () => {
@@ -1498,18 +1487,14 @@ describe('AppContainer State Management', () => {
it('should update terminal title with Working… when showStatusInTitle is false', () => {
// Arrange: Set up mock settings with showStatusInTitle disabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithShowStatusFalse = {
- ...mockSettings,
+ const mockSettingsWithShowStatusFalse = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: false,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state as Active
mockedUseGeminiStream.mockReturnValue({
@@ -1537,17 +1522,14 @@ describe('AppContainer State Management', () => {
it('should use legacy terminal title when dynamicWindowTitle is false', () => {
// Arrange: Set up mock settings with dynamicWindowTitle disabled
- const mockSettingsWithDynamicTitleFalse = {
- ...mockSettings,
+ const mockSettingsWithDynamicTitleFalse = createMockSettings({
merged: {
- ...mockSettings.merged,
ui: {
- ...mockSettings.merged.ui,
dynamicWindowTitle: false,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state
mockedUseGeminiStream.mockReturnValue({
@@ -1575,18 +1557,14 @@ describe('AppContainer State Management', () => {
it('should not update terminal title when hideWindowTitle is true', () => {
// Arrange: Set up mock settings with hideWindowTitle enabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithHideTitleTrue = {
- ...mockSettings,
+ const mockSettingsWithHideTitleTrue = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: true,
hideWindowTitle: true,
},
},
- } as unknown as LoadedSettings;
+ });
// Act: Render the container
const { unmount } = renderAppContainer({
@@ -1604,18 +1582,14 @@ describe('AppContainer State Management', () => {
it('should update terminal title with thought subject when in active state', () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state and thought
const thoughtSubject = 'Processing request';
@@ -1644,18 +1618,14 @@ describe('AppContainer State Management', () => {
it('should update terminal title with default text when in Idle state and no thought subject', () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state as Idle with no thought
mockedUseGeminiStream.mockReturnValue(DEFAULT_GEMINI_STREAM_MOCK);
@@ -1679,18 +1649,14 @@ describe('AppContainer State Management', () => {
it('should update terminal title when in WaitingForConfirmation state with thought subject', async () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state and thought
const thoughtSubject = 'Confirm tool execution';
@@ -1742,17 +1708,14 @@ describe('AppContainer State Management', () => {
vi.setSystemTime(startTime);
// Arrange: Set up mock settings with showStatusInTitle enabled
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...mockSettings.merged,
ui: {
- ...mockSettings.merged.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock an active shell pty but not focused
mockedUseGeminiStream.mockReturnValue({
@@ -1801,17 +1764,14 @@ describe('AppContainer State Management', () => {
vi.setSystemTime(startTime);
// Arrange: Set up mock settings with showStatusInTitle enabled
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...mockSettings.merged,
ui: {
- ...mockSettings.merged.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock an active shell pty with redirection active
mockedUseGeminiStream.mockReturnValue({
@@ -1871,17 +1831,14 @@ describe('AppContainer State Management', () => {
vi.setSystemTime(startTime);
// Arrange: Set up mock settings with showStatusInTitle enabled
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...mockSettings.merged,
ui: {
- ...mockSettings.merged.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock an active shell pty with NO output since operation started (silent)
mockedUseGeminiStream.mockReturnValue({
@@ -1921,17 +1878,14 @@ describe('AppContainer State Management', () => {
vi.setSystemTime(startTime);
// Arrange: Set up mock settings with showStatusInTitle enabled
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...mockSettings.merged,
ui: {
- ...mockSettings.merged.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock an active shell pty but not focused
let lastOutputTime = startTime + 1000;
@@ -2005,18 +1959,14 @@ describe('AppContainer State Management', () => {
it('should pad title to exactly 80 characters', () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state and thought with a short subject
const shortTitle = 'Short';
@@ -2046,18 +1996,14 @@ describe('AppContainer State Management', () => {
it('should use correct ANSI escape code format', () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const mockSettingsWithTitleEnabled = {
- ...mockSettings,
+ const mockSettingsWithTitleEnabled = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
showStatusInTitle: true,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock the streaming state and thought
const title = 'Test Title';
@@ -2085,17 +2031,14 @@ describe('AppContainer State Management', () => {
it('should use CLI_TITLE environment variable when set', () => {
// Arrange: Set up mock settings with showStatusInTitle disabled (so it shows suffix)
- const mockSettingsWithTitleDisabled = {
- ...mockSettings,
+ const mockSettingsWithTitleDisabled = createMockSettings({
merged: {
- ...mockSettings.merged,
ui: {
- ...mockSettings.merged.ui,
showStatusInTitle: false,
hideWindowTitle: false,
},
},
- } as unknown as LoadedSettings;
+ });
// Mock CLI_TITLE environment variable
vi.stubEnv('CLI_TITLE', 'Custom Gemini Title');
@@ -2664,17 +2607,13 @@ describe('AppContainer State Management', () => {
);
// Update settings for this test run
- const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
- const testSettings = {
- ...mockSettings,
+ const testSettings = createMockSettings({
merged: {
- ...defaultMergedSettings,
ui: {
- ...defaultMergedSettings.ui,
useAlternateBuffer: isAlternateMode,
},
},
- } as unknown as LoadedSettings;
+ });
function TestChild() {
useKeypress(childHandler || (() => {}), {
@@ -3384,13 +3323,11 @@ describe('AppContainer State Management', () => {
let unmount: () => void;
await act(async () => {
unmount = renderAppContainer({
- settings: {
- ...mockSettings,
+ settings: createMockSettings({
merged: {
- ...mockSettings.merged,
- ui: { ...mockSettings.merged.ui, useAlternateBuffer: false },
+ ui: { useAlternateBuffer: false },
},
- } as LoadedSettings,
+ }),
}).unmount;
});
@@ -3426,13 +3363,11 @@ describe('AppContainer State Management', () => {
let unmount: () => void;
await act(async () => {
unmount = renderAppContainer({
- settings: {
- ...mockSettings,
+ settings: createMockSettings({
merged: {
- ...mockSettings.merged,
- ui: { ...mockSettings.merged.ui, useAlternateBuffer: true },
+ ui: { useAlternateBuffer: true },
},
- } as LoadedSettings,
+ }),
}).unmount;
});
@@ -3701,16 +3636,13 @@ describe('AppContainer State Management', () => {
});
it('DOES set showIsExpandableHint when overflow occurs in Alternate Buffer Mode', async () => {
- const alternateSettings = mergeSettings({}, {}, {}, {}, true);
- const settingsWithAlternateBuffer = {
+ const settingsWithAlternateBuffer = createMockSettings({
merged: {
- ...alternateSettings,
ui: {
- ...alternateSettings.ui,
useAlternateBuffer: true,
},
},
- } as unknown as LoadedSettings;
+ });
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(true);
diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx
index b0a936a81b..b2402f9fe9 100644
--- a/packages/cli/src/ui/AppContainer.tsx
+++ b/packages/cli/src/ui/AppContainer.tsx
@@ -1677,11 +1677,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
const handleGlobalKeypress = useCallback(
(key: Key): boolean => {
- // Debug log keystrokes if enabled
- if (settings.merged.general.debugKeystrokeLogging) {
- debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
- }
-
if (shortcutsHelpVisible && isHelpDismissKey(key)) {
setShortcutsHelpVisible(false);
}
@@ -1860,7 +1855,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
activePtyId,
handleSuspend,
embeddedShellFocused,
- settings.merged.general.debugKeystrokeLogging,
refreshStatic,
setCopyModeEnabled,
tabFocusTimeoutRef,
diff --git a/packages/cli/src/ui/IdeIntegrationNudge.test.tsx b/packages/cli/src/ui/IdeIntegrationNudge.test.tsx
index 52d00550ea..1b30e0e0b2 100644
--- a/packages/cli/src/ui/IdeIntegrationNudge.test.tsx
+++ b/packages/cli/src/ui/IdeIntegrationNudge.test.tsx
@@ -5,10 +5,9 @@
*/
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
-import { render } from '../test-utils/render.js';
+import { renderWithProviders } from '../test-utils/render.js';
import { act } from 'react';
import { IdeIntegrationNudge } from './IdeIntegrationNudge.js';
-import { KeypressProvider } from './contexts/KeypressContext.js';
import { debugLogger } from '@google/gemini-cli-core';
// Mock debugLogger
@@ -54,10 +53,8 @@ describe('IdeIntegrationNudge', () => {
});
it('renders correctly with default options', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
-
-
- ,
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
);
await waitUntilReady();
const frame = lastFrame();
@@ -71,10 +68,8 @@ describe('IdeIntegrationNudge', () => {
it('handles "Yes" selection', async () => {
const onComplete = vi.fn();
- const { stdin, waitUntilReady, unmount } = render(
-
-
- ,
+ const { stdin, waitUntilReady, unmount } = renderWithProviders(
+ ,
);
await waitUntilReady();
@@ -94,10 +89,8 @@ describe('IdeIntegrationNudge', () => {
it('handles "No" selection', async () => {
const onComplete = vi.fn();
- const { stdin, waitUntilReady, unmount } = render(
-
-
- ,
+ const { stdin, waitUntilReady, unmount } = renderWithProviders(
+ ,
);
await waitUntilReady();
@@ -122,10 +115,8 @@ describe('IdeIntegrationNudge', () => {
it('handles "Dismiss" selection', async () => {
const onComplete = vi.fn();
- const { stdin, waitUntilReady, unmount } = render(
-
-
- ,
+ const { stdin, waitUntilReady, unmount } = renderWithProviders(
+ ,
);
await waitUntilReady();
@@ -155,10 +146,8 @@ describe('IdeIntegrationNudge', () => {
it('handles Escape key press', async () => {
const onComplete = vi.fn();
- const { stdin, waitUntilReady, unmount } = render(
-
-
- ,
+ const { stdin, waitUntilReady, unmount } = renderWithProviders(
+ ,
);
await waitUntilReady();
@@ -184,10 +173,8 @@ describe('IdeIntegrationNudge', () => {
vi.stubEnv('GEMINI_CLI_IDE_WORKSPACE_PATH', '/tmp');
const onComplete = vi.fn();
- const { lastFrame, stdin, waitUntilReady, unmount } = render(
-
-
- ,
+ const { lastFrame, stdin, waitUntilReady, unmount } = renderWithProviders(
+ ,
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/components/AgentConfigDialog.test.tsx b/packages/cli/src/ui/components/AgentConfigDialog.test.tsx
index 52cda094e0..2e5b6ecdb2 100644
--- a/packages/cli/src/ui/components/AgentConfigDialog.test.tsx
+++ b/packages/cli/src/ui/components/AgentConfigDialog.test.tsx
@@ -4,21 +4,14 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { render } from '../../test-utils/render.js';
+import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
import { AgentConfigDialog } from './AgentConfigDialog.js';
import { LoadedSettings, SettingScope } from '../../config/settings.js';
-import { KeypressProvider } from '../contexts/KeypressContext.js';
import type { AgentDefinition } from '@google/gemini-cli-core';
-vi.mock('../contexts/UIStateContext.js', () => ({
- useUIState: () => ({
- mainAreaWidth: 100,
- }),
-}));
-
enum TerminalKeys {
ENTER = '\u000D',
TAB = '\t',
@@ -122,17 +115,16 @@ describe('AgentConfigDialog', () => {
settings: LoadedSettings,
definition: AgentDefinition = createMockAgentDefinition(),
) => {
- const result = render(
-
-
- ,
+ const result = renderWithProviders(
+ ,
+ { settings, uiState: { mainAreaWidth: 100 } },
);
await result.waitUntilReady();
return result;
@@ -331,18 +323,17 @@ describe('AgentConfigDialog', () => {
const settings = createMockSettings();
// Agent config has about 6 base items + 2 per tool
// Render with very small height (20)
- const { lastFrame, unmount } = render(
-
-
- ,
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ { settings, uiState: { mainAreaWidth: 100 } },
);
await waitFor(() =>
expect(lastFrame()).toContain('Configure: Test Agent'),
diff --git a/packages/cli/src/ui/components/AskUserDialog.test.tsx b/packages/cli/src/ui/components/AskUserDialog.test.tsx
index 0469bec373..2f4f711e75 100644
--- a/packages/cli/src/ui/components/AskUserDialog.test.tsx
+++ b/packages/cli/src/ui/components/AskUserDialog.test.tsx
@@ -7,6 +7,8 @@
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import { act } from 'react';
import { renderWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
+import { makeFakeConfig } from '@google/gemini-cli-core';
import { waitFor } from '../../test-utils/async.js';
import { AskUserDialog } from './AskUserDialog.js';
import { QuestionType, type Question } from '@google/gemini-cli-core';
@@ -313,7 +315,12 @@ describe('AskUserDialog', () => {
width={80}
availableHeight={10} // Small height to force scrolling
/>,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(async () => {
@@ -1291,7 +1298,12 @@ describe('AskUserDialog', () => {
width={80}
/>
,
- { useAlternateBuffer: false },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
+ },
);
// With height 5 and alternate buffer disabled, it should show scroll arrows (▲)
@@ -1327,7 +1339,12 @@ describe('AskUserDialog', () => {
width={40} // Small width to force wrapping
/>
,
- { useAlternateBuffer: true },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
+ },
);
// Should NOT contain the truncation message
diff --git a/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx b/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx
index 6ebe22d982..d3b285c3a4 100644
--- a/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx
+++ b/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx
@@ -4,11 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { render } from '../../test-utils/render.js';
+import { renderWithProviders } from '../../test-utils/render.js';
import { EditorSettingsDialog } from './EditorSettingsDialog.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SettingScope, type LoadedSettings } from '../../config/settings.js';
-import { KeypressProvider } from '../contexts/KeypressContext.js';
import { act } from 'react';
import { waitFor } from '../../test-utils/async.js';
import { debugLogger } from '@google/gemini-cli-core';
@@ -52,8 +51,8 @@ describe('EditorSettingsDialog', () => {
vi.clearAllMocks();
});
- const renderWithProvider = (ui: React.ReactNode) =>
- render({ui});
+ const renderWithProvider = (ui: React.ReactElement) =>
+ renderWithProviders(ui);
it('renders correctly', async () => {
const { lastFrame, waitUntilReady } = renderWithProvider(
diff --git a/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx b/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx
index 33daca1e33..272ccbdc27 100644
--- a/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx
+++ b/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx
@@ -7,6 +7,7 @@
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import { act } from 'react';
import { renderWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
import { waitFor } from '../../test-utils/async.js';
import { ExitPlanModeDialog } from './ExitPlanModeDialog.js';
import { useKeypress } from '../hooks/useKeypress.js';
@@ -138,8 +139,9 @@ Implement a comprehensive authentication system with multiple providers.
vi.restoreAllMocks();
});
- const renderDialog = (options?: { useAlternateBuffer?: boolean }) =>
- renderWithProviders(
+ const renderDialog = (options?: { useAlternateBuffer?: boolean }) => {
+ const useAlternateBuffer = options?.useAlternateBuffer ?? true;
+ return renderWithProviders(
options?.useAlternateBuffer ?? true,
+ getUseAlternateBuffer: () => useAlternateBuffer,
} as unknown as import('@google/gemini-cli-core').Config,
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
},
);
+ };
describe.each([{ useAlternateBuffer: true }, { useAlternateBuffer: false }])(
'useAlternateBuffer: $useAlternateBuffer',
@@ -429,7 +435,6 @@ Implement a comprehensive authentication system with multiple providers.
/>
,
{
- useAlternateBuffer,
config: {
getTargetDir: () => mockTargetDir,
getIdeMode: () => false,
@@ -443,6 +448,11 @@ Implement a comprehensive authentication system with multiple providers.
}),
getUseAlternateBuffer: () => useAlternateBuffer ?? true,
} as unknown as import('@google/gemini-cli-core').Config,
+ settings: createMockSettings({
+ merged: {
+ ui: { useAlternateBuffer: useAlternateBuffer ?? true },
+ },
+ }),
},
);
diff --git a/packages/cli/src/ui/components/FolderTrustDialog.test.tsx b/packages/cli/src/ui/components/FolderTrustDialog.test.tsx
index e68417fc55..0ff0e9b0df 100644
--- a/packages/cli/src/ui/components/FolderTrustDialog.test.tsx
+++ b/packages/cli/src/ui/components/FolderTrustDialog.test.tsx
@@ -5,11 +5,12 @@
*/
import { renderWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
+import { makeFakeConfig, ExitCodes } from '@google/gemini-cli-core';
import { waitFor } from '../../test-utils/async.js';
import { act } from 'react';
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { FolderTrustDialog } from './FolderTrustDialog.js';
-import { ExitCodes } from '@google/gemini-cli-core';
import * as processUtils from '../../utils/processUtils.js';
vi.mock('../../utils/processUtils.js', () => ({
@@ -78,7 +79,10 @@ describe('FolderTrustDialog', () => {
/>,
{
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true, terminalHeight: 24 },
},
);
@@ -108,7 +112,10 @@ describe('FolderTrustDialog', () => {
/>,
{
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true, terminalHeight: 14 },
},
);
@@ -139,7 +146,10 @@ describe('FolderTrustDialog', () => {
/>,
{
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true, terminalHeight: 10 },
},
);
@@ -168,7 +178,10 @@ describe('FolderTrustDialog', () => {
/>,
{
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
// Initially constrained
uiState: { constrainHeight: true, terminalHeight: 24 },
},
@@ -194,7 +207,10 @@ describe('FolderTrustDialog', () => {
/>,
{
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: false, terminalHeight: 24 },
},
);
@@ -434,7 +450,10 @@ describe('FolderTrustDialog', () => {
/>,
{
width: 80,
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: { constrainHeight: false, terminalHeight: 15 },
},
);
diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
index f049ffe15e..d258a8089d 100644
--- a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
+++ b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
@@ -16,6 +16,7 @@ import {
import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
import { renderWithProviders } from '../../test-utils/render.js';
import { createMockSettings } from '../../test-utils/settings.js';
+import { makeFakeConfig } from '@google/gemini-cli-core';
// Mock child components
vi.mock('./messages/ToolGroupMessage.js', () => ({
@@ -84,7 +85,12 @@ describe('', () => {
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -352,7 +358,12 @@ describe('', () => {
terminalWidth={80}
availableTerminalHeight={10}
/>,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitUntilReady();
@@ -374,7 +385,12 @@ describe('', () => {
availableTerminalHeight={10}
availableTerminalHeightGemini={Number.MAX_SAFE_INTEGER}
/>,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitUntilReady();
@@ -395,7 +411,12 @@ describe('', () => {
terminalWidth={80}
availableTerminalHeight={10}
/>,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitUntilReady();
@@ -417,7 +438,12 @@ describe('', () => {
availableTerminalHeight={10}
availableTerminalHeightGemini={Number.MAX_SAFE_INTEGER}
/>,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx
index c092e600b9..003f24c66b 100644
--- a/packages/cli/src/ui/components/InputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.test.tsx
@@ -6,6 +6,7 @@
import { renderWithProviders } from '../../test-utils/render.js';
import { createMockSettings } from '../../test-utils/settings.js';
+import { makeFakeConfig } from '@google/gemini-cli-core';
import { waitFor } from '../../test-utils/async.js';
import { act, useState } from 'react';
import {
@@ -3512,7 +3513,10 @@ describe('InputPrompt', () => {
,
{
mouseEventsEnabled: true,
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiActions,
},
);
@@ -3603,7 +3607,10 @@ describe('InputPrompt', () => {
,
{
mouseEventsEnabled: true,
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiActions,
},
);
diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx
index e0880e624c..23218647f9 100644
--- a/packages/cli/src/ui/components/MainContent.test.tsx
+++ b/packages/cli/src/ui/components/MainContent.test.tsx
@@ -5,6 +5,8 @@
*/
import { renderWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
+import { makeFakeConfig, CoreToolCallStatus } from '@google/gemini-cli-core';
import { waitFor } from '../../test-utils/async.js';
import { MainContent } from './MainContent.js';
import { getToolGroupBorderAppearance } from '../utils/borderStyles.js';
@@ -18,7 +20,6 @@ import {
useUIState,
type UIState,
} from '../contexts/UIStateContext.js';
-import { CoreToolCallStatus } from '@google/gemini-cli-core';
import { type IndividualToolCallDisplay } from '../types.js';
// Mock dependencies
@@ -482,7 +483,10 @@ describe('MainContent', () => {
,
{
uiState: uiState as Partial,
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
@@ -509,7 +513,10 @@ describe('MainContent', () => {
,
{
uiState: uiState as unknown as Partial,
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
@@ -733,7 +740,10 @@ describe('MainContent', () => {
,
{
uiState: uiState as Partial,
- useAlternateBuffer: isAlternateBuffer,
+ config: makeFakeConfig({ useAlternateBuffer: isAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: isAlternateBuffer } },
+ }),
},
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/components/SettingsDialog.test.tsx b/packages/cli/src/ui/components/SettingsDialog.test.tsx
index 4a2fd6a854..bc9249877c 100644
--- a/packages/cli/src/ui/components/SettingsDialog.test.tsx
+++ b/packages/cli/src/ui/components/SettingsDialog.test.tsx
@@ -20,16 +20,14 @@
*
*/
-import { render } from '../../test-utils/render.js';
+import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { SettingsDialog } from './SettingsDialog.js';
import { SettingScope } from '../../config/settings.js';
import { createMockSettings } from '../../test-utils/settings.js';
-import { KeypressProvider } from '../contexts/KeypressContext.js';
import { act } from 'react';
import { TEST_ONLY } from '../../utils/settingsUtils.js';
-import { SettingsContext } from '../contexts/SettingsContext.js';
import {
getSettingsSchema,
type SettingDefinition,
@@ -37,12 +35,6 @@ import {
} from '../../config/settingsSchema.js';
import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js';
-vi.mock('../contexts/UIStateContext.js', () => ({
- useUIState: () => ({
- terminalWidth: 100, // Fixed width for consistent snapshots
- }),
-}));
-
enum TerminalKeys {
ENTER = '\u000D',
TAB = '\t',
@@ -96,7 +88,25 @@ const ENUM_SETTING: SettingDefinition = {
showInDialog: true,
};
+// Minimal general schema for KeypressProvider
+const MINIMAL_GENERAL_SCHEMA = {
+ general: {
+ showInDialog: false,
+ properties: {
+ debugKeystrokeLogging: {
+ type: 'boolean',
+ label: 'Debug Keystroke Logging',
+ category: 'General',
+ requiresRestart: false,
+ default: false,
+ showInDialog: false,
+ },
+ },
+ },
+};
+
const ENUM_FAKE_SCHEMA: SettingsSchemaType = {
+ ...MINIMAL_GENERAL_SCHEMA,
ui: {
showInDialog: false,
properties: {
@@ -108,6 +118,7 @@ const ENUM_FAKE_SCHEMA: SettingsSchemaType = {
} as unknown as SettingsSchemaType;
const ARRAY_FAKE_SCHEMA: SettingsSchemaType = {
+ ...MINIMAL_GENERAL_SCHEMA,
context: {
type: 'object',
label: 'Context',
@@ -164,6 +175,7 @@ const ARRAY_FAKE_SCHEMA: SettingsSchemaType = {
} as unknown as SettingsSchemaType;
const TOOLS_SHELL_FAKE_SCHEMA: SettingsSchemaType = {
+ ...MINIMAL_GENERAL_SCHEMA,
tools: {
type: 'object',
label: 'Tools',
@@ -224,16 +236,16 @@ const renderDialog = (
availableTerminalHeight?: number;
},
) =>
- render(
-
-
-
-
- ,
+ renderWithProviders(
+ ,
+ {
+ settings,
+ uiState: { terminalBackgroundColor: undefined },
+ },
);
describe('SettingsDialog', () => {
@@ -1344,17 +1356,14 @@ describe('SettingsDialog', () => {
describe('String Settings Editing', () => {
it('should allow editing and committing a string setting', async () => {
- let settings = createMockSettings({
+ const settings = createMockSettings({
'general.sessionCleanup.maxAge': 'initial',
});
const onSelect = vi.fn();
- const { stdin, unmount, rerender, waitUntilReady } = render(
-
-
-
-
- ,
+ const { stdin, unmount, waitUntilReady } = renderWithProviders(
+ ,
+ { settings },
);
await waitUntilReady();
@@ -1384,20 +1393,15 @@ describe('SettingsDialog', () => {
});
await waitUntilReady();
- settings = createMockSettings({
- user: {
- settings: { 'general.sessionCleanup.maxAge': 'new value' },
- originalSettings: { 'general.sessionCleanup.maxAge': 'new value' },
- path: '',
- },
+ // Simulate the settings file being updated on disk
+ await act(async () => {
+ settings.setValue(
+ SettingScope.User,
+ 'general.sessionCleanup.maxAge',
+ 'new value',
+ );
});
- rerender(
-
-
-
-
- ,
- );
+ await waitUntilReady();
// Press Escape to exit
await act(async () => {
diff --git a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
index 77d072b02e..05ec5d5591 100644
--- a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
+++ b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
@@ -9,6 +9,7 @@ import { Box } from 'ink';
import { ToolConfirmationQueue } from './ToolConfirmationQueue.js';
import { StreamingState } from '../types.js';
import { renderWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
import { waitFor } from '../../test-utils/async.js';
import { type Config, CoreToolCallStatus } from '@google/gemini-cli-core';
import type { ConfirmingToolState } from '../hooks/useConfirmingTool.js';
@@ -162,8 +163,13 @@ describe('ToolConfirmationQueue', () => {
/>
,
{
- config: mockConfig,
- useAlternateBuffer: true,
+ config: {
+ ...mockConfig,
+ getUseAlternateBuffer: () => true,
+ } as unknown as Config,
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: {
terminalWidth: 80,
terminalHeight: 20,
@@ -212,7 +218,9 @@ describe('ToolConfirmationQueue', () => {
/>,
{
config: mockConfig,
- useAlternateBuffer: false,
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: {
terminalWidth: 80,
terminalHeight: 40,
diff --git a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
index 9063606146..5e88151715 100644
--- a/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
+++ b/packages/cli/src/ui/components/messages/DiffRenderer.test.tsx
@@ -6,6 +6,8 @@
import { OverflowProvider } from '../../contexts/OverflowContext.js';
import { renderWithProviders } from '../../../test-utils/render.js';
+import { createMockSettings } from '../../../test-utils/settings.js';
+import { makeFakeConfig } from '@google/gemini-cli-core';
import { waitFor } from '../../../test-utils/async.js';
import { DiffRenderer } from './DiffRenderer.js';
import * as CodeColorizer from '../../utils/CodeColorizer.js';
@@ -42,7 +44,12 @@ index 0000000..e69de29
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() =>
expect(mockColorizeCode).toHaveBeenCalledWith({
@@ -74,7 +81,12 @@ index 0000000..e69de29
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() =>
expect(mockColorizeCode).toHaveBeenCalledWith({
@@ -102,7 +114,12 @@ index 0000000..e69de29
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() =>
expect(mockColorizeCode).toHaveBeenCalledWith({
@@ -135,7 +152,12 @@ index 0000001..0000002 100644
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
// colorizeCode is used internally by the line-by-line rendering, not for the whole block
await waitFor(() => expect(lastFrame()).toContain('new line'));
@@ -166,7 +188,12 @@ index 1234567..1234567 100644
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toBeDefined());
expect(lastFrame()).toMatchSnapshot();
@@ -178,7 +205,12 @@ index 1234567..1234567 100644
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toBeDefined());
expect(lastFrame()).toMatchSnapshot();
@@ -208,7 +240,12 @@ index 123..456 100644
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toContain('added line'));
expect(lastFrame()).toMatchSnapshot();
@@ -242,7 +279,12 @@ index abc..def 100644
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toContain('context line 15'));
expect(lastFrame()).toMatchSnapshot();
@@ -292,7 +334,12 @@ index 123..789 100644
availableTerminalHeight={height}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toContain('anotherNew'));
const output = lastFrame();
@@ -326,7 +373,12 @@ fileDiff Index: file.txt
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toContain('newVar'));
expect(lastFrame()).toMatchSnapshot();
@@ -353,7 +405,12 @@ fileDiff Index: Dockerfile
terminalWidth={80}
/>
,
- { useAlternateBuffer },
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
+ },
);
await waitFor(() => expect(lastFrame()).toContain('RUN npm run build'));
expect(lastFrame()).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
index b650ee4d9d..39fd44bcdf 100644
--- a/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
@@ -16,6 +16,8 @@ import {
CoreToolCallStatus,
} from '@google/gemini-cli-core';
import { renderWithProviders } from '../../../test-utils/render.js';
+import { createMockSettings } from '../../../test-utils/settings.js';
+import { makeFakeConfig } from '@google/gemini-cli-core';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SHELL_COMMAND_NAME, ACTIVE_SHELL_MAX_LINES } from '../../constants.js';
@@ -48,14 +50,6 @@ describe('', () => {
setEmbeddedShellFocused: mockSetEmbeddedShellFocused,
};
- const renderShell = (
- props: Partial = {},
- options: Parameters[1] = {},
- ) =>
- renderWithProviders(, {
- uiActions,
- ...options,
- });
beforeEach(() => {
vi.clearAllMocks();
});
@@ -65,9 +59,9 @@ describe('', () => {
['SHELL_COMMAND_NAME', SHELL_COMMAND_NAME],
['SHELL_TOOL_NAME', SHELL_TOOL_NAME],
])('clicks inside the shell area sets focus for %s', async (_, name) => {
- const { lastFrame, simulateClick, unmount } = renderShell(
- { name },
- { mouseEventsEnabled: true },
+ const { lastFrame, simulateClick, unmount } = renderWithProviders(
+ ,
+ { uiActions, mouseEventsEnabled: true },
);
await waitFor(() => {
@@ -152,7 +146,10 @@ describe('', () => {
ptyId: 1,
},
{
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: {
embeddedShellFocused: true,
activePtyId: 1,
@@ -166,7 +163,10 @@ describe('', () => {
ptyId: 1,
},
{
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: {
embeddedShellFocused: false,
activePtyId: 1,
@@ -174,9 +174,9 @@ describe('', () => {
},
],
])('%s', async (_, props, options) => {
- const { lastFrame, waitUntilReady, unmount } = renderShell(
- props,
- options,
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
+ { uiActions, ...options },
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -223,16 +223,21 @@ describe('', () => {
focused,
constrainHeight,
) => {
- const { lastFrame, waitUntilReady, unmount } = renderShell(
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
{
- resultDisplay: LONG_OUTPUT,
- renderOutputAsMarkdown: false,
- availableTerminalHeight,
- ptyId: 1,
- status: CoreToolCallStatus.Executing,
- },
- {
- useAlternateBuffer: true,
+ uiActions,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: {
activePtyId: focused ? 1 : 2,
embeddedShellFocused: focused,
@@ -250,14 +255,21 @@ describe('', () => {
);
it('fully expands in standard mode when availableTerminalHeight is undefined', async () => {
- const { lastFrame, unmount } = renderShell(
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
{
- resultDisplay: LONG_OUTPUT,
- renderOutputAsMarkdown: false,
- availableTerminalHeight: undefined,
- status: CoreToolCallStatus.Executing,
+ uiActions,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
- { useAlternateBuffer: false },
);
await waitFor(() => {
@@ -269,16 +281,21 @@ describe('', () => {
});
it('fully expands in alternate buffer mode when constrainHeight is false and isExpandable is true', async () => {
- const { lastFrame, waitUntilReady, unmount } = renderShell(
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
{
- resultDisplay: LONG_OUTPUT,
- renderOutputAsMarkdown: false,
- availableTerminalHeight: undefined,
- status: CoreToolCallStatus.Success,
- isExpandable: true,
- },
- {
- useAlternateBuffer: true,
+ uiActions,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: {
constrainHeight: false,
},
@@ -296,16 +313,21 @@ describe('', () => {
});
it('stays constrained in alternate buffer mode when isExpandable is false even if constrainHeight is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = renderShell(
+ const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
+ ,
{
- resultDisplay: LONG_OUTPUT,
- renderOutputAsMarkdown: false,
- availableTerminalHeight: undefined,
- status: CoreToolCallStatus.Success,
- isExpandable: false,
- },
- {
- useAlternateBuffer: true,
+ uiActions,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
uiState: {
constrainHeight: false,
},
diff --git a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx
index 197b78e356..5af99541b5 100644
--- a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx
+++ b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx
@@ -4,12 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { waitFor } from '../../../test-utils/async.js';
-import { render } from '../../../test-utils/render.js';
+import { renderWithProviders } from '../../../test-utils/render.js';
import { SubagentGroupDisplay } from './SubagentGroupDisplay.js';
import { Kind, CoreToolCallStatus } from '@google/gemini-cli-core';
import type { IndividualToolCallDisplay } from '../../types.js';
-import { KeypressProvider } from '../../contexts/KeypressContext.js';
-import { OverflowProvider } from '../../contexts/OverflowContext.js';
import { vi } from 'vitest';
import { Text } from 'ink';
@@ -69,36 +67,32 @@ describe('', () => {
const renderSubagentGroup = (
toolCallsToRender: IndividualToolCallDisplay[],
height?: number,
- ) => (
-
-
-
-
-
- );
+ ) =>
+ renderWithProviders(
+ ,
+ );
it('renders nothing if there are no agent tool calls', async () => {
- const { lastFrame } = render(renderSubagentGroup([], 40));
+ const { lastFrame } = renderSubagentGroup([], 40);
expect(lastFrame({ allowEmpty: true })).toBe('');
});
it('renders collapsed view by default with correct agent counts and states', async () => {
- const { lastFrame, waitUntilReady } = render(
- renderSubagentGroup(mockToolCalls, 40),
+ const { lastFrame, waitUntilReady } = renderSubagentGroup(
+ mockToolCalls,
+ 40,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('expands when availableTerminalHeight is undefined', async () => {
- const { lastFrame, rerender } = render(
- renderSubagentGroup(mockToolCalls, 40),
- );
+ const { lastFrame, rerender } = renderSubagentGroup(mockToolCalls, 40);
// Default collapsed view
await waitFor(() => {
@@ -106,13 +100,27 @@ describe('', () => {
});
// Expand view
- rerender(renderSubagentGroup(mockToolCalls, undefined));
+ rerender(
+ ,
+ );
await waitFor(() => {
expect(lastFrame()).toContain('(ctrl+o to collapse)');
});
// Collapse view
- rerender(renderSubagentGroup(mockToolCalls, 40));
+ rerender(
+ ,
+ );
await waitFor(() => {
expect(lastFrame()).toContain('(ctrl+o to expand)');
});
diff --git a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx
index e3869b6e1b..c6142b2bf8 100644
--- a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx
@@ -13,8 +13,10 @@ import {
type AnsiOutput,
CoreToolCallStatus,
Kind,
+ makeFakeConfig,
} from '@google/gemini-cli-core';
import { renderWithProviders } from '../../../test-utils/render.js';
+import { createMockSettings } from '../../../test-utils/settings.js';
import { tryParseJSON } from '../../../utils/jsonoutput.js';
vi.mock('../GeminiRespondingSpinner.js', () => ({
@@ -462,7 +464,10 @@ describe('', () => {
constrainHeight: true,
},
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
);
await waitUntilReady();
@@ -495,7 +500,10 @@ describe('', () => {
uiActions,
uiState: { streamingState: StreamingState.Idle },
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
);
await waitUntilReady();
@@ -523,7 +531,10 @@ describe('', () => {
uiActions,
uiState: { streamingState: StreamingState.Idle },
width: 80,
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx b/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx
index 2375be7f0e..1300710ebe 100644
--- a/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx
@@ -5,11 +5,12 @@
*/
import { describe, it, expect } from 'vitest';
-import { ToolMessage, type ToolMessageProps } from './ToolMessage.js';
+import { type ToolMessageProps, ToolMessage } from './ToolMessage.js';
import { StreamingState } from '../../types.js';
import { StreamingContext } from '../../contexts/StreamingContext.js';
import { renderWithProviders } from '../../../test-utils/render.js';
-import { CoreToolCallStatus } from '@google/gemini-cli-core';
+import { createMockSettings } from '../../../test-utils/settings.js';
+import { CoreToolCallStatus, makeFakeConfig } from '@google/gemini-cli-core';
describe(' - Raw Markdown Display Snapshots', () => {
const baseProps: ToolMessageProps = {
@@ -72,7 +73,10 @@ describe(' - Raw Markdown Display Snapshots', () => {
,
{
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
- useAlternateBuffer,
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer } },
+ }),
},
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/components/messages/ToolOverflowConsistencyChecks.test.tsx b/packages/cli/src/ui/components/messages/ToolOverflowConsistencyChecks.test.tsx
index 20b8d13459..8b2da8b95e 100644
--- a/packages/cli/src/ui/components/messages/ToolOverflowConsistencyChecks.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolOverflowConsistencyChecks.test.tsx
@@ -7,9 +7,10 @@
import { describe, it, expect } from 'vitest';
import { ToolGroupMessage } from './ToolGroupMessage.js';
import { renderWithProviders } from '../../../test-utils/render.js';
+import { createMockSettings } from '../../../test-utils/settings.js';
import { StreamingState, type IndividualToolCallDisplay } from '../../types.js';
import { waitFor } from '../../../test-utils/async.js';
-import { CoreToolCallStatus } from '@google/gemini-cli-core';
+import { CoreToolCallStatus, makeFakeConfig } from '@google/gemini-cli-core';
import { useOverflowState } from '../../contexts/OverflowContext.js';
describe('ToolOverflowConsistencyChecks: ToolGroupMessage and ToolResultDisplay synchronization', () => {
@@ -56,7 +57,10 @@ describe('ToolOverflowConsistencyChecks: ToolGroupMessage and ToolResultDisplay
streamingState: StreamingState.Idle,
constrainHeight: true,
},
- useAlternateBuffer: true,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
},
);
@@ -106,7 +110,10 @@ describe('ToolOverflowConsistencyChecks: ToolGroupMessage and ToolResultDisplay
streamingState: StreamingState.Idle,
constrainHeight: true,
},
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
},
);
diff --git a/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx b/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx
index 02f466e72f..538a647744 100644
--- a/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx
@@ -5,9 +5,10 @@
*/
import { renderWithProviders } from '../../../test-utils/render.js';
+import { createMockSettings } from '../../../test-utils/settings.js';
import { ToolResultDisplay } from './ToolResultDisplay.js';
import { describe, it, expect, vi } from 'vitest';
-import type { AnsiOutput } from '@google/gemini-cli-core';
+import { makeFakeConfig, type AnsiOutput } from '@google/gemini-cli-core';
describe('ToolResultDisplay', () => {
beforeEach(() => {
@@ -36,7 +37,12 @@ describe('ToolResultDisplay', () => {
terminalWidth={80}
maxLines={10}
/>,
- { useAlternateBuffer: true },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame();
@@ -52,7 +58,12 @@ describe('ToolResultDisplay', () => {
terminalWidth={80}
maxLines={10}
/>,
- { useAlternateBuffer: true },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame();
@@ -69,7 +80,12 @@ describe('ToolResultDisplay', () => {
terminalWidth={80}
hasFocus={true}
/>,
- { useAlternateBuffer: true },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
+ },
);
await waitUntilReady();
@@ -80,7 +96,12 @@ describe('ToolResultDisplay', () => {
it('renders string result as markdown by default', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
,
- { useAlternateBuffer: false },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame();
@@ -98,7 +119,10 @@ describe('ToolResultDisplay', () => {
renderOutputAsMarkdown={false}
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
@@ -118,7 +142,10 @@ describe('ToolResultDisplay', () => {
availableTerminalHeight={20}
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
@@ -140,7 +167,12 @@ describe('ToolResultDisplay', () => {
terminalWidth={80}
availableTerminalHeight={20}
/>,
- { useAlternateBuffer: false },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame();
@@ -170,7 +202,12 @@ describe('ToolResultDisplay', () => {
terminalWidth={80}
availableTerminalHeight={20}
/>,
- { useAlternateBuffer: false },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame();
@@ -189,7 +226,12 @@ describe('ToolResultDisplay', () => {
terminalWidth={80}
availableTerminalHeight={20}
/>,
- { useAlternateBuffer: false },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame({ allowEmpty: true });
@@ -208,7 +250,10 @@ describe('ToolResultDisplay', () => {
renderOutputAsMarkdown={true}
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
@@ -226,7 +271,12 @@ describe('ToolResultDisplay', () => {
availableTerminalHeight={20}
renderOutputAsMarkdown={true}
/>,
- { useAlternateBuffer: true },
+ {
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: true } },
+ }),
+ },
);
await waitUntilReady();
const output = lastFrame();
@@ -306,7 +356,10 @@ describe('ToolResultDisplay', () => {
maxLines={3}
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
@@ -342,7 +395,10 @@ describe('ToolResultDisplay', () => {
availableTerminalHeight={undefined}
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
diff --git a/packages/cli/src/ui/components/messages/ToolResultDisplayOverflow.test.tsx b/packages/cli/src/ui/components/messages/ToolResultDisplayOverflow.test.tsx
index b809e89748..3ee86cc06e 100644
--- a/packages/cli/src/ui/components/messages/ToolResultDisplayOverflow.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolResultDisplayOverflow.test.tsx
@@ -5,9 +5,10 @@
*/
import { renderWithProviders } from '../../../test-utils/render.js';
+import { createMockSettings } from '../../../test-utils/settings.js';
import { ToolResultDisplay } from './ToolResultDisplay.js';
import { describe, it, expect } from 'vitest';
-import { type AnsiOutput } from '@google/gemini-cli-core';
+import { makeFakeConfig, type AnsiOutput } from '@google/gemini-cli-core';
describe('ToolResultDisplay Overflow', () => {
it('shows the head of the content when overflowDirection is bottom (string)', async () => {
@@ -20,7 +21,10 @@ describe('ToolResultDisplay Overflow', () => {
overflowDirection="bottom"
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
@@ -46,7 +50,10 @@ describe('ToolResultDisplay Overflow', () => {
overflowDirection="top"
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
@@ -83,7 +90,10 @@ describe('ToolResultDisplay Overflow', () => {
overflowDirection="bottom"
/>,
{
- useAlternateBuffer: false,
+ config: makeFakeConfig({ useAlternateBuffer: false }),
+ settings: createMockSettings({
+ merged: { ui: { useAlternateBuffer: false } },
+ }),
uiState: { constrainHeight: true },
},
);
diff --git a/packages/cli/src/ui/components/shared/BaseSettingsDialog.test.tsx b/packages/cli/src/ui/components/shared/BaseSettingsDialog.test.tsx
index 1ac701eff1..ebabe87133 100644
--- a/packages/cli/src/ui/components/shared/BaseSettingsDialog.test.tsx
+++ b/packages/cli/src/ui/components/shared/BaseSettingsDialog.test.tsx
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { render } from '../../../test-utils/render.js';
+import { renderWithProviders } from '../../../test-utils/render.js';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
@@ -14,15 +14,8 @@ import {
type BaseSettingsDialogProps,
type SettingsDialogItem,
} from './BaseSettingsDialog.js';
-import { KeypressProvider } from '../../contexts/KeypressContext.js';
import { SettingScope } from '../../../config/settings.js';
-vi.mock('../../contexts/UIStateContext.js', () => ({
- useUIState: () => ({
- mainAreaWidth: 100,
- }),
-}));
-
enum TerminalKeys {
ENTER = '\u000D',
TAB = '\t',
@@ -115,10 +108,8 @@ describe('BaseSettingsDialog', () => {
...props,
};
- const result = render(
-
-
- ,
+ const result = renderWithProviders(
+ ,
);
await result.waitUntilReady();
return result;
@@ -331,22 +322,18 @@ describe('BaseSettingsDialog', () => {
const filteredItems = [items[0], items[2], items[4]];
await act(async () => {
rerender(
-
-
- ,
+ ,
);
});
- await waitUntilReady();
-
// Verify the dialog hasn't crashed and the items are displayed
await waitFor(() => {
const frame = lastFrame();
@@ -391,22 +378,18 @@ describe('BaseSettingsDialog', () => {
const filteredItems = [items[0], items[1]];
await act(async () => {
rerender(
-
-
- ,
+ ,
);
});
- await waitUntilReady();
-
await waitFor(() => {
const frame = lastFrame();
expect(frame).toContain('Boolean Setting');
diff --git a/packages/cli/src/ui/components/shared/ScrollableList.test.tsx b/packages/cli/src/ui/components/shared/ScrollableList.test.tsx
index 1dd72b89a2..2a1182a5f3 100644
--- a/packages/cli/src/ui/components/shared/ScrollableList.test.tsx
+++ b/packages/cli/src/ui/components/shared/ScrollableList.test.tsx
@@ -5,21 +5,12 @@
*/
import { useState, useEffect, useRef, act } from 'react';
-import { render } from '../../../test-utils/render.js';
+import { renderWithProviders } from '../../../test-utils/render.js';
import { Box, Text } from 'ink';
import { ScrollableList, type ScrollableListRef } from './ScrollableList.js';
-import { ScrollProvider } from '../../contexts/ScrollProvider.js';
-import { KeypressProvider } from '../../contexts/KeypressContext.js';
-import { MouseProvider } from '../../contexts/MouseContext.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { waitFor } from '../../../test-utils/async.js';
-vi.mock('../../contexts/UIStateContext.js', () => ({
- useUIState: vi.fn(() => ({
- copyModeEnabled: false,
- })),
-}));
-
// Mock useStdout to provide a fixed size for testing
vi.mock('ink', async (importOriginal) => {
const actual = await importOriginal();
@@ -85,51 +76,45 @@ const TestComponent = ({
}, [onRef]);
return (
-
-
-
-
-
- (
-
+
+
+ (
+
+
+ {item.title}
- {item.title}
-
-
- }
- >
- {item.title}
-
- {getLorem(index)}
+ borderStyle="single"
+ borderTop={true}
+ borderBottom={false}
+ borderLeft={false}
+ borderRight={false}
+ borderColor="gray"
+ />
- )}
- estimatedItemHeight={() => 14}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- initialScrollIndex={Number.MAX_SAFE_INTEGER}
- />
+ }
+ >
+ {item.title}
+
+ {getLorem(index)}
- Count: {items.length}
-
-
-
-
+ )}
+ estimatedItemHeight={() => 14}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ initialScrollIndex={Number.MAX_SAFE_INTEGER}
+ />
+
+ Count: {items.length}
+
);
};
describe('ScrollableList Demo Behavior', () => {
@@ -147,10 +132,10 @@ describe('ScrollableList Demo Behavior', () => {
let lastFrame: (options?: { allowEmpty?: boolean }) => string | undefined;
let waitUntilReady: () => Promise;
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render(
+ result = renderWithProviders(
{
addItem = add;
@@ -230,45 +215,39 @@ describe('ScrollableList Demo Behavior', () => {
}, []);
return (
-
-
-
-
- (
-
- {index === 0 ? (
- [STICKY] {item.title}}
- >
- [Normal] {item.title}
-
- ) : (
- [Normal] {item.title}
- )}
- Content for {item.title}
- More content for {item.title}
-
- )}
- estimatedItemHeight={() => 3}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- />
+
+ (
+
+ {index === 0 ? (
+ [STICKY] {item.title}}
+ >
+ [Normal] {item.title}
+
+ ) : (
+ [Normal] {item.title}
+ )}
+ Content for {item.title}
+ More content for {item.title}
-
-
-
+ )}
+ estimatedItemHeight={() => 3}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ />
+
);
};
let lastFrame: () => string | undefined;
let waitUntilReady: () => Promise;
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render();
+ result = renderWithProviders();
lastFrame = result.lastFrame;
waitUntilReady = result.waitUntilReady;
});
@@ -334,27 +313,21 @@ describe('ScrollableList Demo Behavior', () => {
title: `Item ${i}`,
}));
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render(
-
-
-
-
- {
- listRef = ref;
- }}
- data={items}
- renderItem={({ item }) => {item.title}}
- estimatedItemHeight={() => 1}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- />
-
-
-
- ,
+ result = renderWithProviders(
+
+ {
+ listRef = ref;
+ }}
+ data={items}
+ renderItem={({ item }) => {item.title}}
+ estimatedItemHeight={() => 1}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ />
+ ,
);
lastFrame = result.lastFrame;
stdin = result.stdin;
@@ -444,25 +417,19 @@ describe('ScrollableList Demo Behavior', () => {
let lastFrame: (options?: { allowEmpty?: boolean }) => string | undefined;
let waitUntilReady: () => Promise;
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render(
-
-
-
-
- {item.title}}
- estimatedItemHeight={() => 1}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- width={50}
- />
-
-
-
- ,
+ result = renderWithProviders(
+
+ {item.title}}
+ estimatedItemHeight={() => 1}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ width={50}
+ />
+ ,
);
lastFrame = result.lastFrame;
waitUntilReady = result.waitUntilReady;
@@ -497,31 +464,25 @@ describe('ScrollableList Demo Behavior', () => {
}, []);
return (
-
-
-
-
- {
- listRef = ref;
- }}
- data={items}
- renderItem={({ item }) => {item.title}}
- estimatedItemHeight={() => 1}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- initialScrollIndex={Number.MAX_SAFE_INTEGER}
- />
-
-
-
-
+
+ {
+ listRef = ref;
+ }}
+ data={items}
+ renderItem={({ item }) => {item.title}}
+ estimatedItemHeight={() => 1}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ initialScrollIndex={Number.MAX_SAFE_INTEGER}
+ />
+
);
};
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render();
+ result = renderWithProviders();
});
await result!.waitUntilReady();
@@ -622,33 +583,27 @@ describe('ScrollableList Demo Behavior', () => {
);
return (
-
-
-
-
- {
- listRef = ref;
- }}
- data={items}
- renderItem={({ item, index }) => (
-
- )}
- estimatedItemHeight={() => 1}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- initialScrollIndex={Number.MAX_SAFE_INTEGER}
- />
-
-
-
-
+
+ {
+ listRef = ref;
+ }}
+ data={items}
+ renderItem={({ item, index }) => (
+
+ )}
+ estimatedItemHeight={() => 1}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ initialScrollIndex={Number.MAX_SAFE_INTEGER}
+ />
+
);
};
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render();
+ result = renderWithProviders();
});
await result!.waitUntilReady();
@@ -696,35 +651,29 @@ describe('ScrollableList Demo Behavior', () => {
}, []);
return (
-
-
-
-
- {
- listRef = ref;
- }}
- data={items}
- renderItem={({ item }) => (
-
- {item.title}
-
- )}
- estimatedItemHeight={() => 2}
- keyExtractor={(item) => item.id}
- hasFocus={true}
- initialScrollIndex={Number.MAX_SAFE_INTEGER}
- />
+
+ {
+ listRef = ref;
+ }}
+ data={items}
+ renderItem={({ item }) => (
+
+ {item.title}
-
-
-
+ )}
+ estimatedItemHeight={() => 2}
+ keyExtractor={(item) => item.id}
+ hasFocus={true}
+ initialScrollIndex={Number.MAX_SAFE_INTEGER}
+ />
+
);
};
- let result: ReturnType;
+ let result: ReturnType;
await act(async () => {
- result = render();
+ result = renderWithProviders();
});
await result!.waitUntilReady();
diff --git a/packages/cli/src/ui/components/shared/SearchableList.test.tsx b/packages/cli/src/ui/components/shared/SearchableList.test.tsx
index e156c12695..127a5feef8 100644
--- a/packages/cli/src/ui/components/shared/SearchableList.test.tsx
+++ b/packages/cli/src/ui/components/shared/SearchableList.test.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
-import { render } from '../../../test-utils/render.js';
+import { renderWithProviders } from '../../../test-utils/render.js';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
@@ -14,7 +14,6 @@ import {
type SearchListState,
type GenericListItem,
} from './SearchableList.js';
-import { KeypressProvider } from '../../contexts/KeypressContext.js';
import { useTextBuffer } from './text-buffer.js';
const useMockSearch = (props: {
@@ -52,12 +51,6 @@ const useMockSearch = (props: {
};
};
-vi.mock('../../contexts/UIStateContext.js', () => ({
- useUIState: () => ({
- mainAreaWidth: 100,
- }),
-}));
-
const mockItems: GenericListItem[] = [
{
key: 'item-1',
@@ -98,11 +91,7 @@ describe('SearchableList', () => {
...props,
};
- return render(
-
-
- ,
- );
+ return renderWithProviders();
};
it('should render all items initially', async () => {
diff --git a/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx b/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx
index d7e4fb8ae4..d8df7012cc 100644
--- a/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx
+++ b/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx
@@ -5,11 +5,10 @@
*/
import React from 'react';
-import { render } from '../../../test-utils/render.js';
+import { renderWithProviders } from '../../../test-utils/render.js';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ExtensionDetails } from './ExtensionDetails.js';
-import { KeypressProvider } from '../../contexts/KeypressContext.js';
import { type RegistryExtension } from '../../../config/extensionRegistryClient.js';
const mockExtension: RegistryExtension = {
@@ -43,15 +42,13 @@ describe('ExtensionDetails', () => {
});
const renderDetails = (isInstalled = false) =>
- render(
-
-
- ,
+ renderWithProviders(
+ ,
);
it('should render extension details correctly', async () => {
diff --git a/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx b/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx
index b13b202b90..55e307ecfe 100644
--- a/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx
+++ b/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx
@@ -5,7 +5,7 @@
*/
import React from 'react';
-import { render } from '../../../test-utils/render.js';
+import { renderWithProviders } from '../../../test-utils/render.js';
import { waitFor } from '../../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ExtensionRegistryView } from './ExtensionRegistryView.js';
@@ -14,9 +14,7 @@ import { useExtensionRegistry } from '../../hooks/useExtensionRegistry.js';
import { useExtensionUpdates } from '../../hooks/useExtensionUpdates.js';
import { useRegistrySearch } from '../../hooks/useRegistrySearch.js';
import { type RegistryExtension } from '../../../config/extensionRegistryClient.js';
-import { useUIState } from '../../contexts/UIStateContext.js';
-import { useConfig } from '../../contexts/ConfigContext.js';
-import { KeypressProvider } from '../../contexts/KeypressContext.js';
+import { type UIState } from '../../contexts/UIStateContext.js';
import {
type SearchListState,
type GenericListItem,
@@ -28,8 +26,6 @@ vi.mock('../../hooks/useExtensionRegistry.js');
vi.mock('../../hooks/useExtensionUpdates.js');
vi.mock('../../hooks/useRegistrySearch.js');
vi.mock('../../../config/extension-manager.js');
-vi.mock('../../contexts/UIStateContext.js');
-vi.mock('../../contexts/ConfigContext.js');
const mockExtensions: RegistryExtension[] = [
{
@@ -123,34 +119,27 @@ describe('ExtensionRegistryView', () => {
maxLabelWidth: 10,
}) as unknown as SearchListState,
);
-
- vi.mocked(useUIState).mockReturnValue({
- mainAreaWidth: 100,
- terminalHeight: 40,
- staticExtraHeight: 5,
- } as unknown as ReturnType);
-
- vi.mocked(useConfig).mockReturnValue({
- getEnableExtensionReloading: vi.fn().mockReturnValue(false),
- getExtensionRegistryURI: vi
- .fn()
- .mockReturnValue('https://geminicli.com/extensions.json'),
- } as unknown as ReturnType);
});
const renderView = () =>
- render(
-
-
- ,
+ renderWithProviders(
+ ,
+ {
+ uiState: {
+ staticExtraHeight: 5,
+ terminalHeight: 40,
+ } as Partial,
+ },
);
it('should render extensions', async () => {
- const { lastFrame } = renderView();
+ const { lastFrame, waitUntilReady } = renderView();
+ await waitUntilReady();
+
await waitFor(() => {
expect(lastFrame()).toContain('Test Extension 1');
expect(lastFrame()).toContain('Test Extension 2');
diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx
index 31e43af575..8eb9c7c94f 100644
--- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx
+++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx
@@ -5,13 +5,12 @@
*/
import { debugLogger } from '@google/gemini-cli-core';
-import type React from 'react';
import { act } from 'react';
-import { renderHook } from '../../test-utils/render.js';
+import { renderHookWithProviders } from '../../test-utils/render.js';
+import { createMockSettings } from '../../test-utils/settings.js';
import { waitFor } from '../../test-utils/async.js';
import { vi, afterAll, beforeAll, type Mock } from 'vitest';
import {
- KeypressProvider,
useKeypressContext,
ESC_TIMEOUT,
FAST_RETURN_TIMEOUT,
@@ -52,11 +51,8 @@ class MockStdin extends EventEmitter {
// Helper function to setup keypress test with standard configuration
const setupKeypressTest = () => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
return { result, keyHandler };
@@ -66,10 +62,6 @@ describe('KeypressContext', () => {
let stdin: MockStdin;
const mockSetRawMode = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
-
beforeAll(() => vi.useFakeTimers());
afterAll(() => vi.useRealTimers());
@@ -269,10 +261,7 @@ describe('KeypressContext', () => {
it('should handle double Escape', async () => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
act(() => {
@@ -306,10 +295,7 @@ describe('KeypressContext', () => {
it('should handle lone Escape key (keycode 27) with timeout when kitty protocol is enabled', async () => {
// Use real timers for this test to avoid issues with stream/buffer timing
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
// Send just ESC
@@ -432,7 +418,7 @@ describe('KeypressContext', () => {
])('should $name', async ({ pastedText, writeSequence }) => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -452,7 +438,7 @@ describe('KeypressContext', () => {
it('should parse valid OSC 52 response', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -473,7 +459,7 @@ describe('KeypressContext', () => {
it('should handle split OSC 52 response', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -499,7 +485,7 @@ describe('KeypressContext', () => {
it('should handle OSC 52 response terminated by ESC \\', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -520,7 +506,7 @@ describe('KeypressContext', () => {
it('should ignore unknown OSC sequences', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -537,7 +523,7 @@ describe('KeypressContext', () => {
it('should ignore invalid OSC 52 format', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -569,13 +555,11 @@ describe('KeypressContext', () => {
it('should not log keystrokes when debugKeystrokeLogging is false', async () => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext(), {
+ settings: createMockSettings({
+ general: { debugKeystrokeLogging: false },
+ }),
+ });
act(() => result.current.subscribe(keyHandler));
@@ -593,13 +577,11 @@ describe('KeypressContext', () => {
it('should log kitty buffer accumulation when debugKeystrokeLogging is true', async () => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext(), {
+ settings: createMockSettings({
+ general: { debugKeystrokeLogging: true },
+ }),
+ });
act(() => result.current.subscribe(keyHandler));
@@ -614,13 +596,11 @@ describe('KeypressContext', () => {
it('should show char codes when debugKeystrokeLogging is true even without debug mode', async () => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
-
- {children}
-
- );
-
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext(), {
+ settings: createMockSettings({
+ general: { debugKeystrokeLogging: true },
+ }),
+ });
act(() => result.current.subscribe(keyHandler));
@@ -765,7 +745,7 @@ describe('KeypressContext', () => {
'should recognize sequence "$sequence" as $expected.name',
({ sequence, expected }) => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
act(() => stdin.write(sequence));
@@ -1000,12 +980,7 @@ describe('KeypressContext', () => {
'should handle Alt+$key in $terminal',
({ chunk, expected }: { chunk: string; expected: Partial }) => {
const keyHandler = vi.fn();
- const testWrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
- const { result } = renderHook(() => useKeypressContext(), {
- wrapper: testWrapper,
- });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
act(() => stdin.write(chunk));
@@ -1042,7 +1017,7 @@ describe('KeypressContext', () => {
it('should timeout and flush incomplete kitty sequences after 50ms', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1077,7 +1052,7 @@ describe('KeypressContext', () => {
it('should immediately flush non-kitty CSI sequences', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1099,7 +1074,7 @@ describe('KeypressContext', () => {
it('should parse valid kitty sequences immediately when complete', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1117,7 +1092,7 @@ describe('KeypressContext', () => {
it('should handle batched kitty sequences correctly', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1144,7 +1119,7 @@ describe('KeypressContext', () => {
it('should handle mixed valid and invalid sequences', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1172,7 +1147,7 @@ describe('KeypressContext', () => {
'should handle sequences arriving character by character with %s ms delay',
async (delay) => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1196,7 +1171,7 @@ describe('KeypressContext', () => {
it('should reset timeout when new input arrives', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1231,7 +1206,7 @@ describe('KeypressContext', () => {
describe('SGR Mouse Handling', () => {
it('should ignore SGR mouse sequences', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1249,7 +1224,7 @@ describe('KeypressContext', () => {
it('should handle mixed SGR mouse and key sequences', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1275,7 +1250,7 @@ describe('KeypressContext', () => {
it('should ignore X11 mouse sequences', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1291,7 +1266,7 @@ describe('KeypressContext', () => {
it('should not flush slow SGR mouse sequences as garbage', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1311,7 +1286,7 @@ describe('KeypressContext', () => {
it('should ignore specific SGR mouse sequence sandwiched between keystrokes', async () => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
@@ -1342,12 +1317,7 @@ describe('KeypressContext', () => {
{ name: 'another mouse', sequence: '\u001b[<0;29;19m' },
])('should ignore $name sequence', async ({ sequence }) => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
- const { result } = renderHook(() => useKeypressContext(), {
- wrapper,
- });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
for (const char of sequence) {
@@ -1372,10 +1342,7 @@ describe('KeypressContext', () => {
it('should handle F12', async () => {
const keyHandler = vi.fn();
- const wrapper = ({ children }: { children: React.ReactNode }) => (
- {children}
- );
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
act(() => {
@@ -1404,7 +1371,7 @@ describe('KeypressContext', () => {
'A你B好C', // Mixed characters
])('should correctly handle string "%s"', async (inputString) => {
const keyHandler = vi.fn();
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
+ const { result } = renderHookWithProviders(() => useKeypressContext());
act(() => result.current.subscribe(keyHandler));
act(() => stdin.write(inputString));
diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx
index cdd6da7feb..3189172792 100644
--- a/packages/cli/src/ui/contexts/KeypressContext.tsx
+++ b/packages/cli/src/ui/contexts/KeypressContext.tsx
@@ -13,6 +13,7 @@ import {
useCallback,
useContext,
useEffect,
+ useMemo,
useRef,
} from 'react';
@@ -21,6 +22,7 @@ import { parseMouseEvent } from '../utils/mouse.js';
import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
import { appEvents, AppEvent } from '../../utils/events.js';
import { terminalCapabilityManager } from '../utils/terminalCapabilityManager.js';
+import { useSettingsStore } from './SettingsContext.js';
export const BACKSLASH_ENTER_TIMEOUT = 5;
export const ESC_TIMEOUT = 50;
@@ -766,12 +768,13 @@ export function useKeypressContext() {
export function KeypressProvider({
children,
config,
- debugKeystrokeLogging,
}: {
children: React.ReactNode;
config?: Config;
- debugKeystrokeLogging?: boolean;
}) {
+ const { settings } = useSettingsStore();
+ const debugKeystrokeLogging = settings.merged.general.debugKeystrokeLogging;
+
const { stdin, setRawMode } = useStdin();
const subscribersToPriority = useRef