mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-12 20:37:08 -07:00
Make merged settings non-nullable and fix all lints related to that. (#16647)
This commit is contained in:
@@ -136,7 +136,7 @@ describe('App', () => {
|
||||
pendingHistoryItems: [{ type: 'user', text: 'pending item' }],
|
||||
} as UIState;
|
||||
|
||||
mockLoadedSettings.merged.ui = { useAlternateBuffer: true };
|
||||
mockLoadedSettings.merged.ui.useAlternateBuffer = true;
|
||||
|
||||
const { lastFrame } = renderWithProviders(<App />, quittingUIState);
|
||||
|
||||
@@ -144,7 +144,7 @@ describe('App', () => {
|
||||
expect(lastFrame()).toContain('Quitting...');
|
||||
|
||||
// Reset settings
|
||||
mockLoadedSettings.merged.ui = { useAlternateBuffer: false };
|
||||
mockLoadedSettings.merged.ui.useAlternateBuffer = false;
|
||||
});
|
||||
|
||||
it('should render dialog manager when dialogs are visible', () => {
|
||||
|
||||
@@ -82,7 +82,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
import type { LoadedSettings } from '../config/settings.js';
|
||||
import { type LoadedSettings, mergeSettings } from '../config/settings.js';
|
||||
import type { InitializationResult } from '../core/initializer.js';
|
||||
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
|
||||
import { UIStateContext, type UIState } from './contexts/UIStateContext.js';
|
||||
@@ -380,14 +380,17 @@ describe('AppContainer State Management', () => {
|
||||
);
|
||||
|
||||
// Mock LoadedSettings
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
mockSettings = {
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
hideBanner: false,
|
||||
hideFooter: false,
|
||||
hideTips: false,
|
||||
showMemoryUsage: false,
|
||||
theme: 'default',
|
||||
ui: {
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: false,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -507,8 +510,10 @@ describe('AppContainer State Management', () => {
|
||||
|
||||
describe('Settings Integration', () => {
|
||||
it('handles settings with all display options disabled', async () => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
const settingsAllHidden = {
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
hideBanner: true,
|
||||
hideFooter: true,
|
||||
hideTips: true,
|
||||
@@ -526,8 +531,10 @@ describe('AppContainer State Management', () => {
|
||||
});
|
||||
|
||||
it('handles settings with memory usage enabled', async () => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
const settingsWithMemory = {
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
hideBanner: false,
|
||||
hideFooter: false,
|
||||
hideTips: false,
|
||||
@@ -574,7 +581,7 @@ describe('AppContainer State Management', () => {
|
||||
|
||||
it('handles undefined settings gracefully', async () => {
|
||||
const undefinedSettings = {
|
||||
merged: {},
|
||||
merged: mergeSettings({}, {}, {}, {}, true),
|
||||
} as LoadedSettings;
|
||||
|
||||
let unmount: () => void;
|
||||
@@ -991,12 +998,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: false,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -1073,12 +1081,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: true,
|
||||
hideWindowTitle: true,
|
||||
},
|
||||
@@ -1101,12 +1110,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: true,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -1143,12 +1153,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: true,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -1184,12 +1195,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: true,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -1392,12 +1404,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: true,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -1435,12 +1448,13 @@ 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,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
showStatusInTitle: true,
|
||||
hideWindowTitle: false,
|
||||
},
|
||||
@@ -1802,12 +1816,13 @@ describe('AppContainer State Management', () => {
|
||||
|
||||
const setupCopyModeTest = async (isAlternateMode = false) => {
|
||||
// Update settings for this test run
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
const testSettings = {
|
||||
...mockSettings,
|
||||
merged: {
|
||||
...mockSettings.merged,
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...mockSettings.merged.ui,
|
||||
...defaultMergedSettings.ui,
|
||||
useAlternateBuffer: isAlternateMode,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -392,8 +392,8 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
}, []);
|
||||
|
||||
const getPreferredEditor = useCallback(
|
||||
() => settings.merged.general?.preferredEditor as EditorType,
|
||||
[settings.merged.general?.preferredEditor],
|
||||
() => settings.merged.general.preferredEditor as EditorType,
|
||||
[settings.merged.general.preferredEditor],
|
||||
);
|
||||
|
||||
const buffer = useTextBuffer({
|
||||
@@ -443,7 +443,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!(settings.merged.ui?.hideBanner || config.getScreenReader()) &&
|
||||
!(settings.merged.ui.hideBanner || config.getScreenReader()) &&
|
||||
bannerVisible &&
|
||||
bannerText
|
||||
) {
|
||||
@@ -603,17 +603,17 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
// Check for enforced auth type mismatch
|
||||
useEffect(() => {
|
||||
if (
|
||||
settings.merged.security?.auth?.enforcedType &&
|
||||
settings.merged.security?.auth.selectedType &&
|
||||
settings.merged.security?.auth.enforcedType !==
|
||||
settings.merged.security?.auth.selectedType
|
||||
settings.merged.security.auth.enforcedType &&
|
||||
settings.merged.security.auth.selectedType &&
|
||||
settings.merged.security.auth.enforcedType !==
|
||||
settings.merged.security.auth.selectedType
|
||||
) {
|
||||
onAuthError(
|
||||
`Authentication is enforced to be ${settings.merged.security?.auth.enforcedType}, but you are currently using ${settings.merged.security?.auth.selectedType}.`,
|
||||
`Authentication is enforced to be ${settings.merged.security.auth.enforcedType}, but you are currently using ${settings.merged.security.auth.selectedType}.`,
|
||||
);
|
||||
} else if (
|
||||
settings.merged.security?.auth?.selectedType &&
|
||||
!settings.merged.security?.auth?.useExternal
|
||||
settings.merged.security.auth.selectedType &&
|
||||
!settings.merged.security.auth.useExternal
|
||||
) {
|
||||
// We skip validation for Gemini API key here because it might be stored
|
||||
// in the keychain, which we can't check synchronously.
|
||||
@@ -630,9 +630,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}
|
||||
}
|
||||
}, [
|
||||
settings.merged.security?.auth?.selectedType,
|
||||
settings.merged.security?.auth?.enforcedType,
|
||||
settings.merged.security?.auth?.useExternal,
|
||||
settings.merged.security.auth.selectedType,
|
||||
settings.merged.security.auth.enforcedType,
|
||||
settings.merged.security.auth.useExternal,
|
||||
onAuthError,
|
||||
]);
|
||||
|
||||
@@ -951,8 +951,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
Math.floor(availableTerminalHeight - SHELL_HEIGHT_PADDING),
|
||||
1,
|
||||
),
|
||||
pager: settings.merged.tools?.shell?.pager,
|
||||
showColor: settings.merged.tools?.shell?.showColor,
|
||||
pager: settings.merged.tools.shell.pager,
|
||||
showColor: settings.merged.tools.shell.showColor,
|
||||
sanitizationConfig: config.sanitizationConfig,
|
||||
});
|
||||
|
||||
@@ -960,13 +960,13 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
|
||||
// Context file names computation
|
||||
const contextFileNames = useMemo(() => {
|
||||
const fromSettings = settings.merged.context?.fileName;
|
||||
const fromSettings = settings.merged.context.fileName;
|
||||
return fromSettings
|
||||
? Array.isArray(fromSettings)
|
||||
? fromSettings
|
||||
: [fromSettings]
|
||||
: getAllGeminiMdFilenames();
|
||||
}, [settings.merged.context?.fileName]);
|
||||
}, [settings.merged.context.fileName]);
|
||||
// Initial prompt handling
|
||||
const initialPrompt = useMemo(() => config.getQuestion(), [config]);
|
||||
const initialPromptSubmitted = useRef(false);
|
||||
@@ -1040,7 +1040,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
const shouldShowIdePrompt = Boolean(
|
||||
currentIDE &&
|
||||
!config.getIdeMode() &&
|
||||
!settings.merged.ide?.hasSeenNudge &&
|
||||
!settings.merged.ide.hasSeenNudge &&
|
||||
!idePromptAnswered,
|
||||
);
|
||||
|
||||
@@ -1221,7 +1221,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
|
||||
const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(
|
||||
streamingState,
|
||||
settings.merged.ui?.customWittyPhrases,
|
||||
settings.merged.ui.customWittyPhrases,
|
||||
!!activePtyId && !embeddedShellFocused,
|
||||
lastOutputTime,
|
||||
retryStatus,
|
||||
@@ -1237,7 +1237,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}
|
||||
|
||||
// Debug log keystrokes if enabled
|
||||
if (settings.merged.general?.debugKeystrokeLogging) {
|
||||
if (settings.merged.general.debugKeystrokeLogging) {
|
||||
debugLogger.log('[DEBUG] Keystroke:', JSON.stringify(key));
|
||||
}
|
||||
|
||||
@@ -1337,7 +1337,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
cancelOngoingRequest,
|
||||
activePtyId,
|
||||
embeddedShellFocused,
|
||||
settings.merged.general?.debugKeystrokeLogging,
|
||||
settings.merged.general.debugKeystrokeLogging,
|
||||
refreshStatic,
|
||||
setCopyModeEnabled,
|
||||
copyModeEnabled,
|
||||
@@ -1351,7 +1351,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
// Update terminal title with Gemini CLI status and thoughts
|
||||
useEffect(() => {
|
||||
// Respect hideWindowTitle settings
|
||||
if (settings.merged.ui?.hideWindowTitle) return;
|
||||
if (settings.merged.ui.hideWindowTitle) return;
|
||||
|
||||
const paddedTitle = computeTerminalTitle({
|
||||
streamingState,
|
||||
@@ -1361,8 +1361,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
!!confirmationRequest ||
|
||||
showShellActionRequired,
|
||||
folderName: basename(config.getTargetDir()),
|
||||
showThoughts: !!settings.merged.ui?.showStatusInTitle,
|
||||
useDynamicTitle: settings.merged.ui?.dynamicWindowTitle ?? true,
|
||||
showThoughts: !!settings.merged.ui.showStatusInTitle,
|
||||
useDynamicTitle: settings.merged.ui.dynamicWindowTitle,
|
||||
});
|
||||
|
||||
// Only update the title if it's different from the last value we set
|
||||
@@ -1377,9 +1377,9 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
shellConfirmationRequest,
|
||||
confirmationRequest,
|
||||
showShellActionRequired,
|
||||
settings.merged.ui?.showStatusInTitle,
|
||||
settings.merged.ui?.dynamicWindowTitle,
|
||||
settings.merged.ui?.hideWindowTitle,
|
||||
settings.merged.ui.showStatusInTitle,
|
||||
settings.merged.ui.dynamicWindowTitle,
|
||||
settings.merged.ui.hideWindowTitle,
|
||||
config,
|
||||
stdout,
|
||||
]);
|
||||
|
||||
@@ -152,7 +152,7 @@ describe('AuthDialog', () => {
|
||||
});
|
||||
|
||||
it('filters auth types when enforcedType is set', () => {
|
||||
props.settings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI;
|
||||
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||
renderWithProviders(<AuthDialog {...props} />);
|
||||
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
|
||||
expect(items).toHaveLength(1);
|
||||
@@ -160,7 +160,7 @@ describe('AuthDialog', () => {
|
||||
});
|
||||
|
||||
it('sets initial index to 0 when enforcedType is set', () => {
|
||||
props.settings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI;
|
||||
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||
renderWithProviders(<AuthDialog {...props} />);
|
||||
const { initialIndex } = mockedRadioButtonSelect.mock.calls[0][0];
|
||||
expect(initialIndex).toBe(0);
|
||||
@@ -170,7 +170,7 @@ describe('AuthDialog', () => {
|
||||
it.each([
|
||||
{
|
||||
setup: () => {
|
||||
props.settings.merged.security!.auth!.selectedType =
|
||||
props.settings.merged.security.auth.selectedType =
|
||||
AuthType.USE_VERTEX_AI;
|
||||
},
|
||||
expected: AuthType.USE_VERTEX_AI,
|
||||
@@ -290,7 +290,7 @@ describe('AuthDialog', () => {
|
||||
mockedValidateAuthMethod.mockReturnValue(null);
|
||||
process.env['GEMINI_API_KEY'] = 'test-key-from-env';
|
||||
// Simulate that the user has already authenticated once
|
||||
props.settings.merged.security!.auth!.selectedType =
|
||||
props.settings.merged.security.auth.selectedType =
|
||||
AuthType.LOGIN_WITH_GOOGLE;
|
||||
|
||||
renderWithProviders(<AuthDialog {...props} />);
|
||||
@@ -349,7 +349,7 @@ describe('AuthDialog', () => {
|
||||
{
|
||||
desc: 'calls onAuthError on escape if no auth method is set',
|
||||
setup: () => {
|
||||
props.settings.merged.security!.auth!.selectedType = undefined;
|
||||
props.settings.merged.security.auth.selectedType = undefined;
|
||||
},
|
||||
expectations: (p: typeof props) => {
|
||||
expect(p.onAuthError).toHaveBeenCalledWith(
|
||||
@@ -360,7 +360,7 @@ describe('AuthDialog', () => {
|
||||
{
|
||||
desc: 'calls setAuthState(Unauthenticated) on escape if auth method is set',
|
||||
setup: () => {
|
||||
props.settings.merged.security!.auth!.selectedType =
|
||||
props.settings.merged.security.auth.selectedType =
|
||||
AuthType.USE_GEMINI;
|
||||
},
|
||||
expectations: (p: typeof props) => {
|
||||
@@ -392,7 +392,7 @@ describe('AuthDialog', () => {
|
||||
});
|
||||
|
||||
it('renders correctly with enforced auth type', () => {
|
||||
props.settings.merged.security!.auth!.enforcedType = AuthType.USE_GEMINI;
|
||||
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
|
||||
const { lastFrame } = renderWithProviders(<AuthDialog {...props} />);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -78,9 +78,9 @@ export function AuthDialog({
|
||||
},
|
||||
];
|
||||
|
||||
if (settings.merged.security?.auth?.enforcedType) {
|
||||
if (settings.merged.security.auth.enforcedType) {
|
||||
items = items.filter(
|
||||
(item) => item.value === settings.merged.security?.auth?.enforcedType,
|
||||
(item) => item.value === settings.merged.security.auth.enforcedType,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function AuthDialog({
|
||||
}
|
||||
|
||||
let initialAuthIndex = items.findIndex((item) => {
|
||||
if (settings.merged.security?.auth?.selectedType) {
|
||||
if (settings.merged.security.auth.selectedType) {
|
||||
return item.value === settings.merged.security.auth.selectedType;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export function AuthDialog({
|
||||
|
||||
return item.value === AuthType.LOGIN_WITH_GOOGLE;
|
||||
});
|
||||
if (settings.merged.security?.auth?.enforcedType) {
|
||||
if (settings.merged.security.auth.enforcedType) {
|
||||
initialAuthIndex = 0;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export function AuthDialog({
|
||||
if (authError) {
|
||||
return;
|
||||
}
|
||||
if (settings.merged.security?.auth?.selectedType === undefined) {
|
||||
if (settings.merged.security.auth.selectedType === undefined) {
|
||||
// Prevent exiting if no auth method is set
|
||||
onAuthError(
|
||||
'You must select an auth method to proceed. Press Ctrl+C twice to exit.',
|
||||
|
||||
@@ -20,11 +20,11 @@ export function validateAuthMethodWithSettings(
|
||||
authType: AuthType,
|
||||
settings: LoadedSettings,
|
||||
): string | null {
|
||||
const enforcedType = settings.merged.security?.auth?.enforcedType;
|
||||
const enforcedType = settings.merged.security.auth.enforcedType;
|
||||
if (enforcedType && enforcedType !== authType) {
|
||||
return `Authentication is enforced to be ${enforcedType}, but you are currently using ${authType}.`;
|
||||
}
|
||||
if (settings.merged.security?.auth?.useExternal) {
|
||||
if (settings.merged.security.auth.useExternal) {
|
||||
return null;
|
||||
}
|
||||
// If using Gemini API key, we don't validate it here as we might need to prompt for it.
|
||||
@@ -80,7 +80,7 @@ export const useAuthCommand = (settings: LoadedSettings, config: Config) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const authType = settings.merged.security?.auth?.selectedType;
|
||||
const authType = settings.merged.security.auth.selectedType;
|
||||
if (!authType) {
|
||||
if (process.env['GEMINI_API_KEY']) {
|
||||
onAuthError(
|
||||
|
||||
@@ -33,7 +33,7 @@ export const aboutCommand: SlashCommand = {
|
||||
const modelVersion = context.services.config?.getModel() || 'Unknown';
|
||||
const cliVersion = await getVersion();
|
||||
const selectedAuthType =
|
||||
context.services.settings.merged.security?.auth?.selectedType || '';
|
||||
context.services.settings.merged.security.auth.selectedType || '';
|
||||
const gcpProject = process.env['GOOGLE_CLOUD_PROJECT'] || '';
|
||||
const ideClient = await getIdeClientName(context);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { agentsCommand } from './agentsCommand.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import type { Config, AgentOverride } from '@google/gemini-cli-core';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import { enableAgent, disableAgent } from '../../utils/agentSettings.js';
|
||||
@@ -148,12 +148,9 @@ describe('agentsCommand', () => {
|
||||
reload: reloadSpy,
|
||||
});
|
||||
// Add agent to disabled overrides so validation passes
|
||||
(
|
||||
mockContext.services.settings.merged.agents!.overrides as Record<
|
||||
string,
|
||||
AgentOverride
|
||||
>
|
||||
)['test-agent'] = { disabled: true };
|
||||
mockContext.services.settings.merged.agents.overrides['test-agent'] = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
vi.mocked(enableAgent).mockReturnValue({
|
||||
status: 'success',
|
||||
@@ -266,12 +263,9 @@ describe('agentsCommand', () => {
|
||||
|
||||
it('should show info message if agent is already disabled', async () => {
|
||||
mockConfig.getAgentRegistry().getAllAgentNames.mockReturnValue([]);
|
||||
(
|
||||
mockContext.services.settings.merged.agents!.overrides as Record<
|
||||
string,
|
||||
AgentOverride
|
||||
>
|
||||
)['test-agent'] = { disabled: true };
|
||||
mockContext.services.settings.merged.agents.overrides['test-agent'] = {
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
const disableCommand = agentsCommand.subCommands?.find(
|
||||
(cmd) => cmd.name === 'disable',
|
||||
|
||||
@@ -12,7 +12,6 @@ import type {
|
||||
import { CommandKind } from './types.js';
|
||||
import { MessageType, type HistoryItemAgentsList } from '../types.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import type { AgentOverride } from '@google/gemini-cli-core';
|
||||
import { disableAgent, enableAgent } from '../../utils/agentSettings.js';
|
||||
import { renderAgentActionFeedback } from '../../utils/agentUtils.js';
|
||||
|
||||
@@ -84,10 +83,7 @@ async function enableAction(
|
||||
}
|
||||
|
||||
const allAgents = agentRegistry.getAllAgentNames();
|
||||
const overrides = (settings.merged.agents?.overrides ?? {}) as Record<
|
||||
string,
|
||||
AgentOverride
|
||||
>;
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.keys(overrides).filter(
|
||||
(name) => overrides[name]?.disabled === true,
|
||||
);
|
||||
@@ -157,10 +153,7 @@ async function disableAction(
|
||||
}
|
||||
|
||||
const allAgents = agentRegistry.getAllAgentNames();
|
||||
const overrides = (settings.merged.agents?.overrides ?? {}) as Record<
|
||||
string,
|
||||
AgentOverride
|
||||
>;
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.keys(overrides).filter(
|
||||
(name) => overrides[name]?.disabled === true,
|
||||
);
|
||||
@@ -211,10 +204,7 @@ function completeAgentsToEnable(context: CommandContext, partialArg: string) {
|
||||
const { config, settings } = context.services;
|
||||
if (!config) return [];
|
||||
|
||||
const overrides = (settings.merged.agents?.overrides ?? {}) as Record<
|
||||
string,
|
||||
AgentOverride
|
||||
>;
|
||||
const overrides = settings.merged.agents.overrides;
|
||||
const disabledAgents = Object.entries(overrides)
|
||||
.filter(([_, override]) => override?.disabled === true)
|
||||
.map(([name]) => name);
|
||||
|
||||
@@ -271,9 +271,10 @@ describe('hooksCommand', () => {
|
||||
|
||||
it('should enable a hook and update settings', async () => {
|
||||
// Update the context's settings with disabled hooks
|
||||
mockContext.services.settings.merged.hooks = {
|
||||
disabled: ['test-hook', 'other-hook'],
|
||||
};
|
||||
mockContext.services.settings.merged.hooks.disabled = [
|
||||
'test-hook',
|
||||
'other-hook',
|
||||
];
|
||||
|
||||
const enableCmd = hooksCommand.subCommands!.find(
|
||||
(cmd) => cmd.name === 'enable',
|
||||
@@ -401,9 +402,7 @@ describe('hooksCommand', () => {
|
||||
});
|
||||
|
||||
it('should disable a hook and update settings', async () => {
|
||||
mockContext.services.settings.merged.hooks = {
|
||||
disabled: [],
|
||||
};
|
||||
mockContext.services.settings.merged.hooks.disabled = [];
|
||||
|
||||
const disableCmd = hooksCommand.subCommands!.find(
|
||||
(cmd) => cmd.name === 'disable',
|
||||
@@ -432,9 +431,7 @@ describe('hooksCommand', () => {
|
||||
|
||||
it('should return info when hook is already disabled', async () => {
|
||||
// Update the context's settings with the hook already disabled
|
||||
mockContext.services.settings.merged.hooks = {
|
||||
disabled: ['test-hook'],
|
||||
};
|
||||
mockContext.services.settings.merged.hooks.disabled = ['test-hook'];
|
||||
|
||||
const disableCmd = hooksCommand.subCommands!.find(
|
||||
(cmd) => cmd.name === 'disable',
|
||||
@@ -455,9 +452,7 @@ describe('hooksCommand', () => {
|
||||
});
|
||||
|
||||
it('should handle error when disabling hook fails', async () => {
|
||||
mockContext.services.settings.merged.hooks = {
|
||||
disabled: [],
|
||||
};
|
||||
mockContext.services.settings.merged.hooks.disabled = [];
|
||||
mockSettings.setValue.mockImplementationOnce(() => {
|
||||
throw new Error('Failed to save settings');
|
||||
});
|
||||
|
||||
@@ -76,8 +76,7 @@ async function enableAction(
|
||||
|
||||
// Get current disabled hooks from settings
|
||||
const settings = context.services.settings;
|
||||
const disabledHooks = settings.merged.hooks?.disabled || ([] as string[]);
|
||||
|
||||
const disabledHooks = settings.merged.hooks.disabled;
|
||||
// Remove from disabled list if present
|
||||
const newDisabledHooks = disabledHooks.filter(
|
||||
(name: string) => name !== hookName,
|
||||
@@ -143,8 +142,7 @@ async function disableAction(
|
||||
|
||||
// Get current disabled hooks from settings
|
||||
const settings = context.services.settings;
|
||||
const disabledHooks = settings.merged.hooks?.disabled || ([] as string[]);
|
||||
|
||||
const disabledHooks = settings.merged.hooks.disabled;
|
||||
// Add to disabled list if not already present
|
||||
if (!disabledHooks.includes(hookName)) {
|
||||
const newDisabledHooks = [...disabledHooks, hookName];
|
||||
|
||||
@@ -26,7 +26,7 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{!(settings.merged.ui?.hideBanner || config.getScreenReader()) && (
|
||||
{!(settings.merged.ui.hideBanner || config.getScreenReader()) && (
|
||||
<>
|
||||
<Header version={version} nightly={nightly} />
|
||||
{bannerVisible && bannerText && (
|
||||
@@ -38,7 +38,7 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!(settings.merged.ui?.hideTips || config.getScreenReader()) && (
|
||||
{!(settings.merged.ui.hideTips || config.getScreenReader()) && (
|
||||
<Tips config={config} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -24,6 +24,7 @@ vi.mock('../contexts/VimModeContext.js', () => ({
|
||||
}));
|
||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||
import { StreamingState } from '../types.js';
|
||||
import { mergeSettings } from '../../config/settings.js';
|
||||
|
||||
// Mock child components
|
||||
vi.mock('./LoadingIndicator.js', () => ({
|
||||
@@ -163,13 +164,20 @@ const createMockConfig = (overrides = {}) => ({
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const createMockSettings = (merged = {}) => ({
|
||||
merged: {
|
||||
hideFooter: false,
|
||||
showMemoryUsage: false,
|
||||
...merged,
|
||||
},
|
||||
});
|
||||
const createMockSettings = (merged = {}) => {
|
||||
const defaultMergedSettings = mergeSettings({}, {}, {}, {}, true);
|
||||
return {
|
||||
merged: {
|
||||
...defaultMergedSettings,
|
||||
ui: {
|
||||
...defaultMergedSettings.ui,
|
||||
hideFooter: false,
|
||||
showMemoryUsage: false,
|
||||
...merged,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const renderComposer = (
|
||||
|
||||
@@ -82,9 +82,7 @@ export const Composer = () => {
|
||||
<Box
|
||||
marginTop={1}
|
||||
justifyContent={
|
||||
settings.merged.ui?.hideContextSummary
|
||||
? 'flex-start'
|
||||
: 'space-between'
|
||||
settings.merged.ui.hideContextSummary ? 'flex-start' : 'space-between'
|
||||
}
|
||||
width="100%"
|
||||
flexDirection={isNarrow ? 'column' : 'row'}
|
||||
@@ -153,7 +151,7 @@ export const Composer = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{!settings.merged.ui?.hideFooter && !isScreenReaderEnabled && <Footer />}
|
||||
{!settings.merged.ui.hideFooter && !isScreenReaderEnabled && <Footer />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -124,12 +124,12 @@ export function EditorSettingsDialog({
|
||||
|
||||
let mergedEditorName = 'None';
|
||||
if (
|
||||
settings.merged.general?.preferredEditor &&
|
||||
isEditorAvailable(settings.merged.general?.preferredEditor)
|
||||
settings.merged.general.preferredEditor &&
|
||||
isEditorAvailable(settings.merged.general.preferredEditor)
|
||||
) {
|
||||
mergedEditorName =
|
||||
EDITOR_DISPLAY_NAMES[
|
||||
settings.merged.general?.preferredEditor as EditorType
|
||||
settings.merged.general.preferredEditor as EditorType
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -59,12 +59,11 @@ export const Footer: React.FC = () => {
|
||||
};
|
||||
|
||||
const showMemoryUsage =
|
||||
config.getDebugMode() || settings.merged.ui?.showMemoryUsage || false;
|
||||
const hideCWD = settings.merged.ui?.footer?.hideCWD;
|
||||
const hideSandboxStatus = settings.merged.ui?.footer?.hideSandboxStatus;
|
||||
const hideModelInfo = settings.merged.ui?.footer?.hideModelInfo;
|
||||
const hideContextPercentage =
|
||||
settings.merged.ui?.footer?.hideContextPercentage;
|
||||
config.getDebugMode() || settings.merged.ui.showMemoryUsage;
|
||||
const hideCWD = settings.merged.ui.footer.hideCWD;
|
||||
const hideSandboxStatus = settings.merged.ui.footer.hideSandboxStatus;
|
||||
const hideModelInfo = settings.merged.ui.footer.hideModelInfo;
|
||||
const hideContextPercentage = settings.merged.ui.footer.hideContextPercentage;
|
||||
|
||||
const pathLength = Math.max(20, Math.floor(mainAreaWidth * 0.25));
|
||||
const displayPath = shortenPath(tildeifyPath(targetDir), pathLength);
|
||||
|
||||
@@ -52,14 +52,11 @@ export const StatusDisplay: React.FC<StatusDisplayProps> = ({
|
||||
return <Text color={theme.status.error}>{uiState.queueErrorMessage}</Text>;
|
||||
}
|
||||
|
||||
if (
|
||||
uiState.activeHooks.length > 0 &&
|
||||
(settings.merged.hooks?.notifications ?? true)
|
||||
) {
|
||||
if (uiState.activeHooks.length > 0 && settings.merged.hooks.notifications) {
|
||||
return <HookStatusDisplay activeHooks={uiState.activeHooks} />;
|
||||
}
|
||||
|
||||
if (!settings.merged.ui?.hideContextSummary && !hideContextSummary) {
|
||||
if (!settings.merged.ui.hideContextSummary && !hideContextSummary) {
|
||||
return (
|
||||
<ContextSummaryDisplay
|
||||
ideContext={uiState.ideContextState}
|
||||
|
||||
@@ -95,7 +95,7 @@ export function ThemeDialog({
|
||||
const [highlightedThemeName, setHighlightedThemeName] = useState<string>(
|
||||
() => {
|
||||
// If a theme is already set, use it.
|
||||
if (settings.merged.ui?.theme) {
|
||||
if (settings.merged.ui.theme) {
|
||||
return settings.merged.ui.theme;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export function ThemeDialog({
|
||||
const customThemes =
|
||||
selectedScope === SettingScope.User
|
||||
? settings.user.settings.ui?.customThemes || {}
|
||||
: settings.merged.ui?.customThemes || {};
|
||||
: settings.merged.ui.customThemes;
|
||||
const builtInThemes = themeManager
|
||||
.getAvailableThemes()
|
||||
.filter((theme) => theme.type !== 'custom');
|
||||
|
||||
@@ -42,7 +42,7 @@ export const ToolConfirmationMessage: React.FC<
|
||||
|
||||
const settings = useSettings();
|
||||
const allowPermanentApproval =
|
||||
settings.merged.security?.enablePermanentToolApproval ?? false;
|
||||
settings.merged.security.enablePermanentToolApproval;
|
||||
|
||||
const [ideClient, setIdeClient] = useState<IdeClient | null>(null);
|
||||
const [isDiffingEnabled, setIsDiffingEnabled] = useState(false);
|
||||
|
||||
@@ -32,7 +32,7 @@ export const VimModeProvider = ({
|
||||
children: React.ReactNode;
|
||||
settings: LoadedSettings;
|
||||
}) => {
|
||||
const initialVimEnabled = settings.merged.general?.vimMode ?? false;
|
||||
const initialVimEnabled = settings.merged.general.vimMode;
|
||||
const [vimEnabled, setVimEnabled] = useState(initialVimEnabled);
|
||||
const [vimMode, setVimMode] = useState<VimMode>(
|
||||
initialVimEnabled ? 'NORMAL' : 'INSERT',
|
||||
@@ -40,13 +40,13 @@ export const VimModeProvider = ({
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize vimEnabled from settings on mount
|
||||
const enabled = settings.merged.general?.vimMode ?? false;
|
||||
const enabled = settings.merged.general.vimMode;
|
||||
setVimEnabled(enabled);
|
||||
// When vim mode is enabled, always start in NORMAL mode
|
||||
if (enabled) {
|
||||
setVimMode('NORMAL');
|
||||
}
|
||||
}, [settings.merged.general?.vimMode]);
|
||||
}, [settings.merged.general.vimMode]);
|
||||
|
||||
const toggleVimEnabled = useCallback(async () => {
|
||||
const newValue = !vimEnabled;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
export const isAlternateBufferEnabled = (settings: LoadedSettings): boolean =>
|
||||
settings.merged.ui?.useAlternateBuffer === true;
|
||||
settings.merged.ui.useAlternateBuffer === true;
|
||||
|
||||
export const useAlternateBuffer = (): boolean => {
|
||||
const settings = useSettings();
|
||||
|
||||
@@ -27,7 +27,7 @@ export const useFolderTrust = (
|
||||
const [isRestarting, setIsRestarting] = useState(false);
|
||||
const startupMessageSent = useRef(false);
|
||||
|
||||
const folderTrust = settings.merged.security?.folderTrust?.enabled;
|
||||
const folderTrust = settings.merged.security.folderTrust.enabled;
|
||||
|
||||
useEffect(() => {
|
||||
const { isTrusted: trusted } = isWorkspaceTrusted(settings.merged);
|
||||
|
||||
@@ -83,7 +83,7 @@ enum StreamProcessingStatus {
|
||||
}
|
||||
|
||||
function showCitations(settings: LoadedSettings): boolean {
|
||||
const enabled = settings?.merged?.ui?.showCitations;
|
||||
const enabled = settings.merged.ui.showCitations;
|
||||
if (enabled !== undefined) {
|
||||
return enabled;
|
||||
}
|
||||
@@ -782,7 +782,7 @@ export const useGeminiStream = (
|
||||
|
||||
const handleChatModelEvent = useCallback(
|
||||
(eventValue: string, userMessageTimestamp: number) => {
|
||||
if (!settings?.merged?.ui?.showModelInfoInChat) {
|
||||
if (!settings.merged.ui.showModelInfoInChat) {
|
||||
return;
|
||||
}
|
||||
if (pendingHistoryItemRef.current) {
|
||||
|
||||
@@ -85,7 +85,7 @@ export const usePermissionsModifyTrust = (
|
||||
);
|
||||
const [needsRestart, setNeedsRestart] = useState(false);
|
||||
|
||||
const isFolderTrustEnabled = !!settings.merged.security?.folderTrust?.enabled;
|
||||
const isFolderTrustEnabled = !!settings.merged.security.folderTrust.enabled;
|
||||
|
||||
const updateTrustLevel = useCallback(
|
||||
(trustLevel: TrustLevel) => {
|
||||
|
||||
@@ -32,7 +32,7 @@ export function createShowMemoryAction(
|
||||
|
||||
const currentMemory = config.getUserMemory();
|
||||
const fileCount = config.getGeminiMdFileCount();
|
||||
const contextFileName = settings.merged.context?.fileName;
|
||||
const contextFileName = settings.merged.context.fileName;
|
||||
const contextFileNames = Array.isArray(contextFileName)
|
||||
? contextFileName
|
||||
: [contextFileName];
|
||||
|
||||
@@ -67,7 +67,7 @@ export const useThemeCommand = (
|
||||
|
||||
const closeThemeDialog = useCallback(() => {
|
||||
// Re-apply the saved theme to revert any preview changes from highlighting
|
||||
applyTheme(loadedSettings.merged.ui?.theme);
|
||||
applyTheme(loadedSettings.merged.ui.theme);
|
||||
setIsThemeDialogOpen(false);
|
||||
}, [applyTheme, loadedSettings]);
|
||||
|
||||
@@ -88,10 +88,10 @@ export const useThemeCommand = (
|
||||
return;
|
||||
}
|
||||
loadedSettings.setValue(scope, 'ui.theme', themeName); // Update the merged settings
|
||||
if (loadedSettings.merged.ui?.customThemes) {
|
||||
themeManager.loadCustomThemes(loadedSettings.merged.ui?.customThemes);
|
||||
if (loadedSettings.merged.ui.customThemes) {
|
||||
themeManager.loadCustomThemes(loadedSettings.merged.ui.customThemes);
|
||||
}
|
||||
applyTheme(loadedSettings.merged.ui?.theme); // Apply the current theme
|
||||
applyTheme(loadedSettings.merged.ui.theme); // Apply the current theme
|
||||
setThemeError(null);
|
||||
} finally {
|
||||
setIsThemeDialogOpen(false); // Close the dialog
|
||||
|
||||
@@ -149,7 +149,7 @@ export function colorizeCode({
|
||||
const activeTheme = theme || themeManager.getActiveTheme();
|
||||
const showLineNumbers = hideLineNumbers
|
||||
? false
|
||||
: (settings?.merged.ui?.showLineNumbers ?? true);
|
||||
: settings.merged.ui.showLineNumbers;
|
||||
|
||||
const useMaxSizedBox = !isAlternateBufferEnabled(settings);
|
||||
try {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const calculateMainAreaWidth = (
|
||||
terminalWidth: number,
|
||||
settings: LoadedSettings,
|
||||
): number => {
|
||||
if (settings.merged.ui?.useFullWidth) {
|
||||
if (settings.merged.ui.useFullWidth) {
|
||||
if (isAlternateBufferEnabled(settings)) {
|
||||
return terminalWidth - 1;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('checkForUpdates', () => {
|
||||
});
|
||||
|
||||
it('should return null if disableUpdateNag is true', async () => {
|
||||
mockSettings.merged.general!.disableUpdateNag = true;
|
||||
mockSettings.merged.general.disableUpdateNag = true;
|
||||
const result = await checkForUpdates(mockSettings);
|
||||
expect(result).toBeNull();
|
||||
expect(getPackageJson).not.toHaveBeenCalled();
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function checkForUpdates(
|
||||
settings: LoadedSettings,
|
||||
): Promise<UpdateObject | null> {
|
||||
try {
|
||||
if (settings.merged.general?.disableUpdateNag) {
|
||||
if (settings.merged.general.disableUpdateNag) {
|
||||
return null;
|
||||
}
|
||||
// Skip update check when running from source (development mode)
|
||||
|
||||
Reference in New Issue
Block a user