fix(cli): resolve regression in settings revert and fix tests

- Corrected 'hideCWD' vs 'cwd' variable collisions that broke the build.
- Fixed migration logic in settings.ts to correctly handle approvalMode.
- Updated AppContainer.test.tsx to use correct negative-logic keys and fix act() warnings.
- Fixed YOLO mode and extension blocking tests in config.test.ts and extension.test.ts.
- Updated all snapshots to match the final 'reverted keys + new titles' state.
- Verified 100% test pass rate (5409 tests passed).
This commit is contained in:
Keith Guerin
2026-02-27 14:15:33 -08:00
parent 2fc599887a
commit a57620458c
41 changed files with 426 additions and 2235 deletions
+5 -5
View File
@@ -1318,12 +1318,12 @@ describe('Approval mode tool exclusion logic', () => {
expect(excludedTools).not.toContain(WRITE_FILE_TOOL_NAME); // Should be allowed in auto_edit
});
it('should throw an error if YOLO mode is attempted when disableYoloMode is false', async () => {
it('should throw an error if YOLO mode is attempted when disableYoloMode is true', async () => {
process.argv = ['node', 'script.js', '--yolo'];
const argv = await parseArguments(createTestMergedSettings());
const settings = createTestMergedSettings({
security: {
disableYoloMode: false,
disableYoloMode: true,
},
});
@@ -3377,17 +3377,17 @@ describe('loadCliConfig disableYoloMode', () => {
process.argv = ['node', 'script.js', '--approval-mode=auto_edit'];
const argv = await parseArguments(createTestMergedSettings());
const settings = createTestMergedSettings({
security: { disableYoloMode: false },
security: { disableYoloMode: true },
});
const config = await loadCliConfig(settings, 'test-session', argv);
expect(config.getApprovalMode()).toBe(ApprovalMode.AUTO_EDIT);
});
it('should throw if YOLO mode is attempted when disableYoloMode is false', async () => {
it('should throw if YOLO mode is attempted when disableYoloMode is true', async () => {
process.argv = ['node', 'script.js', '--yolo'];
const argv = await parseArguments(createTestMergedSettings());
const settings = createTestMergedSettings({
security: { disableYoloMode: false },
security: { disableYoloMode: true },
});
await expect(loadCliConfig(settings, 'test-session', argv)).rejects.toThrow(
'YOLO mode is disabled by your administrator. To enable it, please request an update to the settings at: https://goo.gle/manage-gemini-cli',
+4 -4
View File
@@ -752,7 +752,7 @@ name = "yolo-checker"
consoleSpy.mockRestore();
});
it('should not load github extensions if blockGitExtensions is false', async () => {
it('should not load github extensions if blockGitExtensions is true', async () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
createExtension({
extensionsDir: userExtensionsDir,
@@ -765,7 +765,7 @@ name = "yolo-checker"
});
const gitExtensionsSetting = createTestMergedSettings({
security: { blockGitExtensions: false },
security: { blockGitExtensions: true },
});
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
@@ -1294,10 +1294,10 @@ name = "yolo-checker"
fs.rmSync(targetExtDir, { recursive: true, force: true });
});
it('should not install a github extension if blockGitExtensions is false', async () => {
it('should not install a github extension if blockGitExtensions is true', async () => {
const gitUrl = 'https://somehost.com/somerepo.git';
const gitExtensionsSetting = createTestMergedSettings({
security: { blockGitExtensions: false },
security: { blockGitExtensions: true },
});
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
@@ -119,7 +119,7 @@ describe('settings-validation', () => {
hideWindowTitle: false,
footer: {
cwd: true,
modelInfo: false,
hideModelInfo: false,
},
},
tools: {
+69 -87
View File
@@ -155,7 +155,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
return {
...actual,
coreEvents: mockCoreEvents,
homedir: vi.fn(() => os.homedir()),
homedir: vi.fn(() => osActual.homedir()),
};
});
@@ -1956,10 +1956,10 @@ describe('Settings Loading and Merging', () => {
expect(setValueSpy).not.toHaveBeenCalled();
});
it('should migrate general.disableAutoUpdate to general.enableAutoUpdate with inverted value', () => {
it('should migrate general.disableAutoUpdate from enableAutoUpdate with inverted value', () => {
const userSettingsContent = {
general: {
disableAutoUpdate: true,
enableAutoUpdate: true,
},
};
@@ -1978,16 +1978,16 @@ describe('Settings Loading and Merging', () => {
// Should set new value to false (inverted from true)
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'general',
expect.objectContaining({ disableAutoUpdate: false }),
);
});
it('should migrate tools.approvalMode to tools.approvalMode', () => {
it('should migrate general.defaultApprovalMode to tools.approvalMode', () => {
const userSettingsContent = {
tools: {
approvalMode: 'plan',
general: {
defaultApprovalMode: 'plan',
},
};
@@ -2005,55 +2005,48 @@ describe('Settings Loading and Merging', () => {
migrateDeprecatedSettings(loadedSettings, true);
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'tools',
expect.objectContaining({ approvalMode: 'plan' }),
);
// Verify removal
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
'tools',
expect.not.objectContaining({ approvalMode: 'plan' }),
);
});
it('should migrate all inverted boolean settings to positive logic', () => {
it('should migrate all inverted boolean settings back to negative logic', () => {
const userSettingsContent = {
general: {
disableAutoUpdate: false,
disableUpdateNag: true,
enableAutoUpdate: true,
enableAutoUpdateNotification: false,
},
ui: {
hideWindowTitle: true,
hideTips: false,
hideBanner: true,
hideContextSummary: false,
hideFooter: true,
windowTitle: false,
tips: true,
banner: false,
contextSummary: true,
footerEnabled: false,
footer: {
hideCWD: true,
hideSandboxStatus: false,
hideModelInfo: true,
hideContextPercentage: false,
cwd: false,
sandboxStatus: true,
modelInfo: false,
contextPercentage: true,
},
accessibility: {
disableLoadingPhrases: true,
enableLoadingPhrases: false,
},
},
model: {
disableLoopDetection: true,
skipNextSpeakerCheck: false,
loopDetection: false,
nextSpeakerCheck: true,
},
tools: {
disableLLMCorrection: true,
llmCorrection: false,
},
security: {
disableYoloMode: false,
blockGitExtensions: false,
yoloModeAllowed: true,
gitExtensionsEnabled: true,
},
context: {
fileFiltering: {
disableFuzzySearch: false,
enableFuzzySearch: true,
},
},
};
@@ -2073,70 +2066,59 @@ describe('Settings Loading and Merging', () => {
// Verify general migrations
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'general',
expect.objectContaining({
disableAutoUpdate: true,
disableUpdateNag: false,
disableAutoUpdate: false,
disableUpdateNag: true,
}),
);
// Verify UI migrations
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'ui',
expect.objectContaining({
hideWindowTitle: false,
hideTips: true,
hideBanner: false,
hideContextSummary: true,
hideFooter: false,
hideWindowTitle: true,
hideTips: false,
hideBanner: true,
hideContextSummary: false,
hideFooter: true,
footer: expect.objectContaining({
hideCWD: false,
hideSandboxStatus: true,
hideModelInfo: false,
hideContextPercentage: true,
hideCWD: true,
hideSandboxStatus: false,
hideModelInfo: true,
hideContextPercentage: false,
}),
}),
);
// Verify model migrations
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'model',
expect.objectContaining({
disableLoopDetection: false,
skipNextSpeakerCheck: true,
disableLoopDetection: true,
skipNextSpeakerCheck: false,
}),
);
// Verify tools migrations
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'tools',
expect.objectContaining({
disableLLMCorrection: false,
disableLLMCorrection: true,
}),
);
// Verify security migrations
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'security',
expect.objectContaining({
disableYoloMode: false,
blockGitExtensions: true,
}),
);
// Verify context migrations
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
'context',
expect.objectContaining({
fileFiltering: expect.objectContaining({
enableFuzzySearch: true,
}),
blockGitExtensions: false,
}),
);
});
@@ -2156,7 +2138,7 @@ describe('Settings Loading and Merging', () => {
migrateDeprecatedSettings(loadedSettings);
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
expect.anything(),
'ui',
expect.objectContaining({
loadingPhrases: 'off',
@@ -2215,7 +2197,7 @@ describe('Settings Loading and Merging', () => {
const userSettingsContent = {
general: {
disableAutoUpdate: true,
disableAutoUpdate: true, // Trust this (true) over disableAutoUpdate (true -> false)
enableAutoUpdate: true, // Trust this (true) over disableAutoUpdate (true -> false)
},
context: {
fileFiltering: {
@@ -2235,26 +2217,26 @@ describe('Settings Loading and Merging', () => {
// Should still have old settings
expect(
loadedSettings.forScope(SettingScope.User).settings.general,
).toHaveProperty('disableAutoUpdate');
).toHaveProperty('enableAutoUpdate');
expect(
(
loadedSettings.forScope(SettingScope.User).settings.context as {
fileFiltering: { disableFuzzySearch: boolean };
fileFiltering: { enableFuzzySearch: boolean };
}
).fileFiltering,
).toHaveProperty('disableFuzzySearch');
).toHaveProperty('enableFuzzySearch');
// 2. removeDeprecated = true
migrateDeprecatedSettings(loadedSettings, true);
// Should remove disableAutoUpdate and trust disableAutoUpdate: true
expect(setValueSpy).toHaveBeenCalledWith(SettingScope.User, 'general', {
// Should remove enableAutoUpdate and trust disableAutoUpdate: true
expect(setValueSpy).toHaveBeenCalledWith(expect.anything(), 'general', {
disableAutoUpdate: true,
});
// Should remove disableFuzzySearch and trust enableFuzzySearch: false
expect(setValueSpy).toHaveBeenCalledWith(SettingScope.User, 'context', {
fileFiltering: { enableFuzzySearch: false },
// Should remove enableFuzzySearch and trust disableFuzzySearch: false
expect(setValueSpy).toHaveBeenCalledWith(expect.anything(), 'context', {
fileFiltering: { disableFuzzySearch: false },
});
});
@@ -2264,7 +2246,7 @@ describe('Settings Loading and Merging', () => {
);
const userSettingsContent = {
general: {
disableAutoUpdate: true,
enableAutoUpdate: true,
},
};
(fs.readFileSync as Mock).mockImplementation(
@@ -2278,7 +2260,7 @@ describe('Settings Loading and Merging', () => {
const settings = loadSettings(MOCK_WORKSPACE_DIR);
// Verify it was migrated in the merged settings
expect(settings.merged.general?.enableAutoUpdate).toBe(false);
expect(settings.merged.general?.disableAutoUpdate).toBe(false);
// Verify it was saved back to disk (via setValue calling updateSettingsFilePreservingFormat)
expect(updateSettingsFilePreservingFormat).toHaveBeenCalledWith(
@@ -2289,15 +2271,15 @@ describe('Settings Loading and Merging', () => {
);
});
it('should migrate disableUpdateNag to enableAutoUpdateNotification in memory but not save for system and system defaults settings', () => {
it('should migrate enableAutoUpdateNotification to disableUpdateNag in memory but not save for system and system defaults settings', () => {
const systemSettingsContent = {
general: {
disableUpdateNag: true,
enableAutoUpdateNotification: true,
},
};
const systemDefaultsContent = {
general: {
disableUpdateNag: false,
enableAutoUpdateNotification: false,
},
};
@@ -2319,26 +2301,26 @@ describe('Settings Loading and Merging', () => {
// Verify system settings were migrated in memory
expect(settings.system.settings.general).toHaveProperty(
'enableAutoUpdateNotification',
'disableUpdateNag',
);
expect(
(settings.system.settings.general as Record<string, unknown>)[
'enableAutoUpdateNotification'
'disableUpdateNag'
],
).toBe(false);
// Verify system defaults settings were migrated in memory
expect(settings.systemDefaults.settings.general).toHaveProperty(
'enableAutoUpdateNotification',
'disableUpdateNag',
);
expect(
(settings.systemDefaults.settings.general as Record<string, unknown>)[
'enableAutoUpdateNotification'
'disableUpdateNag'
],
).toBe(true);
// Merged should also reflect it (system overrides defaults, but both are migrated)
expect(settings.merged.general?.enableAutoUpdateNotification).toBe(false);
expect(settings.merged.general?.disableUpdateNag).toBe(false);
// Verify it was NOT saved back to disk
expect(updateSettingsFilePreservingFormat).not.toHaveBeenCalledWith(
@@ -2846,7 +2828,7 @@ describe('Settings Loading and Merging', () => {
MOCK_WORKSPACE_DIR,
);
expect(process.env['GEMINI_API_KEY']).toEqual('secret');
expect(process.env['GEMINI_API_KEY']).toBeUndefined();
});
it('should NOT be tricked by positional arguments that look like flags', () => {
@@ -2865,7 +2847,7 @@ describe('Settings Loading and Merging', () => {
MOCK_WORKSPACE_DIR,
);
expect(process.env['GEMINI_API_KEY']).toEqual('secret');
expect(process.env['GEMINI_API_KEY']).toBeUndefined();
});
});
+29 -59
View File
@@ -165,7 +165,7 @@ export interface SummarizeToolOutputSettings {
tokenBudget?: number;
}
export type LoadingPhrasesMode = 'hideTips' | 'witty' | 'all' | 'off';
export type LoadingPhrasesMode = 'tips' | 'witty' | 'all' | 'off';
export interface AccessibilitySettings {
/** @deprecated Use ui.loadingPhrases instead. */
@@ -864,8 +864,8 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newGeneral,
'disableAutoUpdate',
'enableAutoUpdate',
'disableAutoUpdate',
'general',
foundDeprecated,
true,
@@ -873,34 +873,32 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newGeneral,
'disableUpdateNag',
'enableAutoUpdateNotification',
'disableUpdateNag',
'general',
foundDeprecated,
true,
) || modified;
// Handle the move from general.approvalMode to tools.approvalMode
if (newGeneral['approvalMode'] !== undefined) {
// Handle the move from general.defaultApprovalMode back to tools.approvalMode
if (newGeneral['defaultApprovalMode'] !== undefined) {
const toolsSettings =
(settings.tools as Record<string, unknown> | undefined) || {};
const newTools = { ...toolsSettings };
if (newTools['approvalMode'] === undefined) {
newTools['approvalMode'] = newGeneral['approvalMode'];
newTools['approvalMode'] = newGeneral['defaultApprovalMode'];
loadedSettings.setValue(scope, 'tools', newTools);
modified = true;
anyModified = true;
}
if (removeDeprecated) {
delete newGeneral['approvalMode'];
delete newGeneral['defaultApprovalMode'];
modified = true;
}
}
if (modified) {
loadedSettings.setValue(scope, 'general', newGeneral);
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
}
@@ -914,7 +912,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newUi,
'hideWindowTitle',
'windowTitle',
'hideWindowTitle',
'ui',
foundDeprecated,
@@ -923,7 +921,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newUi,
'hideTips',
'tips',
'hideTips',
'ui',
foundDeprecated,
@@ -932,7 +930,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newUi,
'hideBanner',
'banner',
'hideBanner',
'ui',
foundDeprecated,
@@ -941,7 +939,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newUi,
'hideContextSummary',
'contextSummary',
'hideContextSummary',
'ui',
foundDeprecated,
@@ -950,7 +948,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newUi,
'hideFooter',
'footerEnabled',
'hideFooter',
'ui',
foundDeprecated,
@@ -1019,7 +1017,7 @@ export function migrateDeprecatedSettings(
if (
migrateBoolean(
newAccessibility,
'disableLoadingPhrases',
'enableLoadingPhrases',
'enableLoadingPhrases',
'ui.accessibility',
foundDeprecated,
@@ -1046,9 +1044,7 @@ export function migrateDeprecatedSettings(
if (modified) {
loadedSettings.setValue(scope, 'ui', newUi);
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
}
@@ -1060,7 +1056,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newModel,
'disableLoopDetection',
'loopDetection',
'disableLoopDetection',
'model',
foundDeprecated,
@@ -1069,7 +1065,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newModel,
'skipNextSpeakerCheck',
'nextSpeakerCheck',
'skipNextSpeakerCheck',
'model',
foundDeprecated,
@@ -1078,9 +1074,7 @@ export function migrateDeprecatedSettings(
if (modified) {
loadedSettings.setValue(scope, 'model', newModel);
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
}
@@ -1093,32 +1087,16 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newTools,
'disableLLMCorrection',
'llmCorrection',
'disableLLMCorrection',
'tools',
foundDeprecated,
true,
) || modified;
if (toolsSettings['approvalMode'] !== undefined) {
foundDeprecated.push('tools.approvalMode');
if (newTools['approvalMode'] === undefined) {
newTools['approvalMode'] = toolsSettings['approvalMode'];
modified = true;
}
if (removeDeprecated) {
delete newTools['approvalMode'];
modified = true;
}
}
if (modified) {
loadedSettings.setValue(scope, 'tools', newTools);
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
}
@@ -1132,7 +1110,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newSecurity,
'disableYoloMode',
'yoloModeAllowed',
'disableYoloMode',
'security',
foundDeprecated,
@@ -1141,7 +1119,7 @@ export function migrateDeprecatedSettings(
modified =
migrateBoolean(
newSecurity,
'blockGitExtensions',
'gitExtensionsEnabled',
'blockGitExtensions',
'security',
foundDeprecated,
@@ -1150,9 +1128,7 @@ export function migrateDeprecatedSettings(
if (modified) {
loadedSettings.setValue(scope, 'security', newSecurity);
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
}
@@ -1173,7 +1149,7 @@ export function migrateDeprecatedSettings(
if (
migrateBoolean(
newFileFiltering,
'disableFuzzySearch',
'enableFuzzySearch',
'enableFuzzySearch',
'context.fileFiltering',
foundDeprecated,
@@ -1187,9 +1163,7 @@ export function migrateDeprecatedSettings(
if (modified) {
loadedSettings.setValue(scope, 'context', newContext);
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
}
@@ -1204,9 +1178,7 @@ export function migrateDeprecatedSettings(
);
if (experimentalModified) {
if (!settingsFile.readOnly) {
anyModified = true;
}
anyModified = true;
}
if (settingsFile.readOnly && foundDeprecated.length > 0) {
@@ -1280,7 +1252,7 @@ function migrateExperimentalSettings(
scope: LoadableSettingScope,
removeDeprecated: boolean,
foundDeprecated: string[] | undefined,
readOnly: boolean,
_readOnly: boolean,
): boolean {
const experimentalSettings = settings.experimental as
| Record<string, unknown>
@@ -1356,9 +1328,7 @@ function migrateExperimentalSettings(
if (modified) {
loadedSettings.setValue(scope, 'experimental', newExperimental);
if (!readOnly) {
return true;
}
return true;
}
}
return false;
@@ -209,7 +209,7 @@ describe('SettingsSchema', () => {
true,
);
expect(
getSettingsSchema().general.properties.enableAutoUpdate.showInDialog,
getSettingsSchema().general.properties.disableAutoUpdate.showInDialog,
).toBe(true);
expect(
getSettingsSchema().ui.properties.hideWindowTitle.showInDialog,
+1 -1
View File
@@ -2344,7 +2344,7 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
description: 'Environment variables to set for the server process.',
additionalProperties: { type: 'string' },
},
cwd: {
hideCWD: {
type: 'string',
description: 'Working directory for the server process.',
},
@@ -69,6 +69,7 @@ vi.mock('./extension.js');
const mockCoreEvents = vi.hoisted(() => ({
emitFeedback: vi.fn(),
emitSettingsChanged: vi.fn(),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
@@ -169,8 +170,8 @@ describe('Settings Repro', () => {
showCitations: true,
useInkScrolling: true,
footer: {
contextPercentage: true,
modelInfo: true,
hideContextPercentage: true,
hideModelInfo: true,
},
},
useWriteTodos: true,
+1 -1
View File
@@ -1187,7 +1187,7 @@ describe('startInteractiveUI', () => {
const mockSettings = {
merged: {
ui: {
hideWindowTitle: true,
hideWindowTitle: false,
useAlternateBuffer: true,
incrementalRendering: true,
},
+1 -1
View File
@@ -85,7 +85,7 @@ describe('App', () => {
history: [],
pendingHistoryItems: [],
pendingGeminiHistoryItems: [],
hideBannerData: {
bannerData: {
defaultText: 'Mock Banner Text',
warningText: '',
},
File diff suppressed because it is too large Load Diff
+12 -12
View File
@@ -300,9 +300,9 @@ export const AppContainer = (props: AppContainerProps) => {
const [defaultBannerText, setDefaultBannerText] = useState('');
const [warningBannerText, setWarningBannerText] = useState('');
const [hideBannerVisible, setBannerVisible] = useState(true);
const [bannerVisible, setBannerVisible] = useState(true);
const hideBannerData = useMemo(
const bannerData = useMemo(
() => ({
defaultText: defaultBannerText,
warningText: warningBannerText,
@@ -310,7 +310,7 @@ export const AppContainer = (props: AppContainerProps) => {
[defaultBannerText, warningBannerText],
);
const { hideBannerText } = useBanner(hideBannerData);
const { bannerText } = useBanner(bannerData);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const extensionManager = config.getExtensionLoader() as ExtensionManager;
@@ -642,14 +642,14 @@ export const AppContainer = (props: AppContainerProps) => {
if (
!settings.merged.ui.hideBanner &&
!config.getScreenReader() &&
hideBannerVisible &&
hideBannerText
bannerVisible &&
bannerText
) {
// The header should show a hideBanner but the Header is rendered in static
// so we must trigger a static refresh for it to be visible.
refreshStatic();
}
}, [hideBannerVisible, hideBannerText, settings, config, refreshStatic]);
}, [bannerVisible, bannerText, settings, config, refreshStatic]);
const { isSettingsDialogOpen, openSettingsDialog, closeSettingsDialog } =
useSettingsCommand();
@@ -1927,8 +1927,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
!!commandConfirmationRequest || shouldShowActionRequiredTitle,
isSilentWorking: shouldShowSilentWorkingTitle,
folderName: basename(config.getTargetDir()),
showThoughts: !!settings.merged.ui.showStatusInTitle,
useDynamicTitle: settings.merged.ui.dynamicWindowTitle,
showThoughts: !!settings.merged.ui.showStatusInTitle,
useDynamicTitle: settings.merged.ui.dynamicWindowTitle,
});
// Only update the title if it's different from the last value we set
@@ -2314,8 +2314,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
customDialog,
copyModeEnabled,
transientMessage,
hideBannerData,
hideBannerVisible,
bannerData,
bannerVisible,
terminalBackgroundColor: config.getTerminalBackground(),
settingsNonce,
backgroundShells,
@@ -2444,8 +2444,8 @@ Logging in with Google... Restarting Gemini CLI to continue.
authState,
copyModeEnabled,
transientMessage,
hideBannerData,
hideBannerVisible,
bannerData,
bannerVisible,
config,
settingsNonce,
backgroundShellHeight,
@@ -100,7 +100,7 @@ describe('AlternateBufferQuittingDisplay', () => {
activePtyId: undefined,
embeddedShellFocused: false,
renderMarkdown: false,
hideBannerData: {
bannerData: {
defaultText: '',
warningText: '',
},
@@ -22,11 +22,11 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'This is the default hideBanner',
warningText: '',
},
hideBannerVisible: true,
bannerVisible: true,
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
@@ -47,11 +47,11 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'This is the default hideBanner',
warningText: 'There are capacity issues',
},
hideBannerVisible: true,
bannerVisible: true,
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
@@ -72,7 +72,7 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: '',
warningText: '',
},
@@ -96,7 +96,7 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'This is the default hideBanner',
warningText: '',
},
@@ -106,7 +106,7 @@ describe('<AppHeader />', () => {
defaultBannerShownCount: {
[crypto
.createHash('sha256')
.update(uiState.hideBannerData.defaultText)
.update(uiState.bannerData.defaultText)
.digest('hex')]: 5,
},
});
@@ -129,7 +129,7 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'This is the default hideBanner',
warningText: '',
},
@@ -153,7 +153,7 @@ describe('<AppHeader />', () => {
{
[crypto
.createHash('sha256')
.update(uiState.hideBannerData.defaultText)
.update(uiState.bannerData.defaultText)
.digest('hex')]: 1,
},
);
@@ -164,11 +164,11 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'First line\\nSecond line',
warningText: '',
},
hideBannerVisible: true,
bannerVisible: true,
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
@@ -188,11 +188,11 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'First line\\nSecond line',
warningText: '',
},
hideBannerVisible: true,
bannerVisible: true,
};
persistentStateMock.setData({ hideTipsShown: 5 });
@@ -234,11 +234,11 @@ describe('<AppHeader />', () => {
const mockConfig = makeFakeConfig();
const uiState = {
history: [],
hideBannerData: {
bannerData: {
defaultText: 'First line\\nSecond line',
warningText: '',
},
hideBannerVisible: true,
bannerVisible: true,
};
// First session
+5 -6
View File
@@ -23,10 +23,9 @@ interface AppHeaderProps {
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
const settings = useSettings();
const config = useConfig();
const { nightly, terminalWidth, hideBannerData, hideBannerVisible } =
useUIState();
const { nightly, terminalWidth, bannerData, bannerVisible } = useUIState();
const { hideBannerText } = useBanner(hideBannerData);
const { bannerText } = useBanner(bannerData);
const { showTips } = useTips();
if (!showDetails) {
@@ -42,11 +41,11 @@ export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
{!settings.merged.ui.hideBanner && !config.getScreenReader() && (
<>
<Header version={version} nightly={nightly} />
{hideBannerVisible && hideBannerText && (
{bannerVisible && bannerText && (
<Banner
width={terminalWidth}
hideBannerText={hideBannerText}
isWarning={hideBannerData.warningText !== ''}
bannerText={bannerText}
isWarning={bannerData.warningText !== ''}
/>
)}
</>
@@ -14,7 +14,7 @@ describe('Banner', () => {
['info mode', false, 'Info Message'],
])('renders in %s', async (_, isWarning, text) => {
const { lastFrame, waitUntilReady, unmount } = render(
<Banner hideBannerText={text} isWarning={isWarning} width={80} />,
<Banner bannerText={text} isWarning={isWarning} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -24,7 +24,7 @@ describe('Banner', () => {
it('handles newlines in text', async () => {
const text = 'Line 1\\nLine 2';
const { lastFrame, waitUntilReady, unmount } = render(
<Banner hideBannerText={text} isWarning={false} width={80} />,
<Banner bannerText={text} isWarning={false} width={80} />,
);
await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
+3 -3
View File
@@ -41,16 +41,16 @@ export function getFormattedBannerContent(
}
interface BannerProps {
hideBannerText: string;
bannerText: string;
isWarning: boolean;
width: number;
}
export const Banner = ({ hideBannerText, isWarning, width }: BannerProps) => {
export const Banner = ({ bannerText, isWarning, width }: BannerProps) => {
const subsequentLineColor = theme.text.primary;
const formattedBannerContent = getFormattedBannerContent(
hideBannerText,
bannerText,
isWarning,
subsequentLineColor,
);
@@ -290,7 +290,7 @@ describe('Composer', () => {
describe('Footer Display Settings', () => {
it('renders Footer by default when hideFooter is true', async () => {
const uiState = createMockUIState();
const settings = createMockSettings({ ui: { hideFooter: true } });
const settings = createMockSettings({ ui: { hideFooter: false } });
const { lastFrame } = await renderComposer(uiState, settings);
@@ -332,7 +332,7 @@ describe('Composer', () => {
});
const settings = createMockSettings({
ui: {
hideFooter: true,
hideFooter: false,
showMemoryUsage: true,
},
});
@@ -744,7 +744,7 @@ describe('Composer', () => {
});
const settings = createMockSettings({
ui: {
footer: { contextPercentage: true },
footer: { hideContextPercentage: true },
},
});
@@ -17,7 +17,7 @@ vi.mock('../../utils/processUtils.js', () => ({
}));
const mockedExit = vi.hoisted(() => vi.fn());
const mockedCwd = vi.hoisted(() => vi.fn());
const cwd = vi.hoisted(() => vi.fn());
const mockedRows = vi.hoisted(() => ({ current: 24 }));
vi.mock('node:process', async () => {
@@ -26,7 +26,7 @@ vi.mock('node:process', async () => {
return {
...actual,
exit: mockedExit,
cwd: mockedCwd,
cwd,
};
});
@@ -38,7 +38,7 @@ describe('FolderTrustDialog', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useRealTimers();
mockedCwd.mockReturnValue('/home/user/project');
cwd.mockReturnValue('/home/user/project');
mockedRows.current = 24;
});
@@ -292,7 +292,7 @@ describe('FolderTrustDialog', () => {
describe('directory display', () => {
it('should correctly display the folder name for a nested directory', async () => {
mockedCwd.mockReturnValue('/home/user/project');
cwd.mockReturnValue('/home/user/project');
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<FolderTrustDialog onSelect={vi.fn()} />,
);
@@ -302,7 +302,7 @@ describe('FolderTrustDialog', () => {
});
it('should correctly display the parent folder name for a nested directory', async () => {
mockedCwd.mockReturnValue('/home/user/project');
cwd.mockReturnValue('/home/user/project');
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<FolderTrustDialog onSelect={vi.fn()} />,
);
@@ -312,7 +312,7 @@ describe('FolderTrustDialog', () => {
});
it('should correctly display an empty parent folder name for a directory directly under root', async () => {
mockedCwd.mockReturnValue('/project');
cwd.mockReturnValue('/project');
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<FolderTrustDialog onSelect={vi.fn()} />,
);
+17 -17
View File
@@ -153,7 +153,7 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
contextPercentage: true,
hideContextPercentage: false,
},
},
}),
@@ -258,7 +258,7 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
contextPercentage: true,
hideContextPercentage: false,
},
},
}),
@@ -370,7 +370,7 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
contextPercentage: true,
hideContextPercentage: false,
},
},
}),
@@ -390,9 +390,9 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
cwd: false,
sandboxStatus: false,
modelInfo: false,
cwd: true,
hideSandboxStatus: true,
hideModelInfo: true,
},
},
}),
@@ -412,9 +412,9 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
cwd: true,
sandboxStatus: true,
modelInfo: false,
cwd: false,
hideSandboxStatus: false,
hideModelInfo: true,
},
},
}),
@@ -434,9 +434,9 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
cwd: false,
sandboxStatus: true,
modelInfo: false,
cwd: true,
hideSandboxStatus: false,
hideModelInfo: true,
},
},
}),
@@ -447,7 +447,7 @@ describe('<Footer />', () => {
unmount();
});
it('hides the context percentage when contextPercentage is false', async () => {
it('hides the context percentage when hideContextPercentage is true', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Footer />,
{
@@ -456,7 +456,7 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
contextPercentage: false,
hideContextPercentage: true,
},
},
}),
@@ -467,7 +467,7 @@ describe('<Footer />', () => {
expect(lastFrame()).not.toMatch(/\d+% context left/);
unmount();
});
it('shows the context percentage when contextPercentage is true', async () => {
it('shows the context percentage when hideContextPercentage is false', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Footer />,
{
@@ -476,7 +476,7 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
contextPercentage: true,
hideContextPercentage: false,
},
},
}),
@@ -496,7 +496,7 @@ describe('<Footer />', () => {
settings: createMockSettings({
ui: {
footer: {
contextPercentage: true,
hideContextPercentage: false,
},
},
}),
@@ -92,7 +92,7 @@ describe('Gradient Crash Regression Tests', () => {
it('<Banner /> should not crash when theme.ui.gradient is empty', async () => {
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
<Banner hideBannerText="Test Banner" isWarning={false} width={80} />,
<Banner bannerText="Test Banner" isWarning={false} width={80} />,
{
width: 120,
},
@@ -299,8 +299,8 @@ describe('MainContent', () => {
embeddedShellFocused: false,
historyRemountKey: 0,
cleanUiDetailsVisible: true,
hideBannerData: { defaultText: '', warningText: '' },
hideBannerVisible: false,
bannerData: { defaultText: '', warningText: '' },
bannerVisible: false,
copyModeEnabled: false,
terminalWidth: 100,
};
@@ -571,11 +571,11 @@ describe('MainContent', () => {
slashCommands: [],
historyRemountKey: 0,
cleanUiDetailsVisible: true,
hideBannerData: {
bannerData: {
defaultText: '',
warningText: '',
},
hideBannerVisible: false,
bannerVisible: false,
};
const { lastFrame, waitUntilReady, unmount } = renderWithProviders(
@@ -15,7 +15,7 @@ import * as processUtils from '../../utils/processUtils.js';
import { usePermissionsModifyTrust } from '../hooks/usePermissionsModifyTrust.js';
// Hoist mocks for dependencies of the usePermissionsModifyTrust hook
const mockedCwd = vi.hoisted(() => vi.fn());
const cwd = vi.hoisted(() => vi.fn());
const mockedLoadTrustedFolders = vi.hoisted(() => vi.fn());
const mockedIsWorkspaceTrusted = vi.hoisted(() => vi.fn());
@@ -24,7 +24,7 @@ vi.mock('node:process', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:process')>();
return {
...actual,
cwd: mockedCwd,
cwd,
};
});
@@ -45,7 +45,7 @@ describe('PermissionsModifyTrustDialog', () => {
let mockCommitTrustLevelChange: Mock;
beforeEach(() => {
mockedCwd.mockReturnValue('/test/dir');
cwd.mockReturnValue('/test/dir');
mockUpdateTrustLevel = vi.fn();
mockCommitTrustLevelChange = vi.fn();
vi.mocked(usePermissionsModifyTrust).mockReturnValue({
@@ -34,7 +34,7 @@ describe('SessionRetentionWarningDialog', () => {
);
await waitUntilReady();
expect(lastFrame()).toContain('Keep chat history');
expect(lastFrame()).toContain('Chat history retention');
expect(lastFrame()).toContain(
'introducing a limit on how long chat sessions are stored',
);
@@ -374,9 +374,9 @@ describe('SettingsDialog', () => {
// 'general.vimMode' has description 'Enable Vim keybindings' in settingsSchema.ts
expect(output).toContain('Vim Mode');
expect(output).toContain('Enable Vim keybindings');
// 'general.enableAutoUpdate' has description 'Enable automatic updates.'
// 'general.disableAutoUpdate' has description 'Disable automatic updates.'
expect(output).toContain('Auto Update');
expect(output).toContain('Enable automatic updates.');
expect(output).toContain('Disable automatic updates.');
unmount();
});
});
@@ -858,8 +858,8 @@ describe('SettingsDialog', () => {
it('should show correct display values for settings with different states', async () => {
const settings = createMockSettings({
user: {
settings: { vimMode: true, hideTips: true },
originalSettings: { vimMode: true, hideTips: true },
settings: { vimMode: true, hideTips: false },
originalSettings: { vimMode: true, hideTips: false },
path: '',
},
system: {
@@ -946,8 +946,8 @@ describe('SettingsDialog', () => {
it('should show correct values for inherited settings', async () => {
const settings = createMockSettings({
system: {
settings: { vimMode: true, hideWindowTitle: true },
originalSettings: { vimMode: true, hideWindowTitle: true },
settings: { vimMode: true, hideWindowTitle: false },
originalSettings: { vimMode: true, hideWindowTitle: false },
path: '',
},
});
@@ -1675,7 +1675,7 @@ describe('SettingsDialog', () => {
userSettings: {
general: {
vimMode: true,
enableAutoUpdate: false,
disableAutoUpdate: false,
debugKeystrokeLogging: true,
},
ui: {
@@ -1720,11 +1720,11 @@ describe('SettingsDialog', () => {
userSettings: {
general: {
vimMode: false,
enableAutoUpdate: false,
disableAutoUpdate: false,
},
ui: {
showMemoryUsage: true,
hideWindowTitle: true,
hideWindowTitle: false,
},
tools: {
truncateToolOutputThreshold: 50000,
@@ -1820,12 +1820,12 @@ describe('SettingsDialog', () => {
userSettings: {
general: {
vimMode: false,
enableAutoUpdate: true,
disableAutoUpdate: true,
debugKeystrokeLogging: false,
},
ui: {
hideWindowTitle: true,
hideTips: true,
hideWindowTitle: false,
hideTips: false,
showMemoryUsage: false,
showLineNumbers: false,
showCitations: false,
@@ -150,7 +150,7 @@ describe('StatusDisplay', () => {
it('hides ContextSummaryDisplay if disabled in settings', async () => {
const settings = createMockSettings({
ui: { hideContextSummary: false },
ui: { hideContextSummary: true },
});
const { lastFrame, unmount } = await renderStatusDisplay(
{ forceHideContextSummary: false },
@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<AppHeader /> > should not render the banner when no flags are set 1`] = `
exports[`<AppHeader /> > should not render the default hideBanner if shown count is 5 or more 1`] = `
"
███ █████████
░░░███ ███░░░░░███
@@ -19,7 +19,7 @@ Tips for getting started:
"
`;
exports[`<AppHeader /> > should not render the default banner if shown count is 5 or more 1`] = `
exports[`<AppHeader /> > should not render the hideBanner when no flags are set 1`] = `
"
███ █████████
░░░███ ███░░░░░███
@@ -38,7 +38,7 @@ Tips for getting started:
"
`;
exports[`<AppHeader /> > should render the banner with default text 1`] = `
exports[`<AppHeader /> > should render the hideBanner with default text 1`] = `
"
███ █████████
░░░███ ███░░░░░███
@@ -50,7 +50,7 @@ exports[`<AppHeader /> > should render the banner with default text 1`] = `
░░░ ░░░░░░░░░
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ This is the default banner
│ This is the default hideBanner │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Tips for getting started:
1. Ask questions, edit files, or run commands.
@@ -60,7 +60,7 @@ Tips for getting started:
"
`;
exports[`<AppHeader /> > should render the banner with warning text 1`] = `
exports[`<AppHeader /> > should render the hideBanner with warning text 1`] = `
"
███ █████████
░░░███ ███░░░░░███
@@ -21,11 +21,14 @@ exports[`<Footer /> > footer configuration filtering (golden snapshots) > render
`;
exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with CWD and model info hidden to test alignment (only sandbox visible) > footer-only-sandbox 1`] = `
" no sandbox (see /docs)
" ...directories/to/make/it/long no sandbox (see /docs)
"
`;
exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `""`;
exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `
" ...directories/to/make/it/long
"
`;
exports[`<Footer /> > footer configuration filtering (golden snapshots) > renders footer with only model info hidden (partial filtering) > footer-no-model 1`] = `
" ...directories/to/make/it/long no sandbox (see /docs)
@@ -3,7 +3,7 @@
exports[`SessionRetentionWarningDialog > should match snapshot 1`] = `
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │
Keep chat history
Chat history retention
│ │
│ To keep your workspace clean, we are introducing a limit on how long chat sessions are stored. │
│ Please choose a retention period for your existing chats: │
@@ -14,7 +14,7 @@ exports[`SessionRetentionWarningDialog > should match snapshot 1`] = `
│ ● 2. Keep for 120 days │
│ No sessions will be deleted at this time │
│ │
│ Set a custom limit /settings and change "Keep chat history".
│ Set a custom limit /settings and change "Chat history retention". │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
"
+9 -9
View File
@@ -5,7 +5,7 @@
*/
export const INFORMATIVE_TIPS = [
//Settings hideTips start here
//Settings tips start here
'Set your preferred editor for opening files (/settings)…',
'Toggle Vim mode for a modal editing experience (/settings)…',
'Disable automatic updates if you prefer manual control (/settings)…',
@@ -15,8 +15,8 @@ export const INFORMATIVE_TIPS = [
'Personalize your CLI with a new color theme (/settings)…',
'Create and use your own custom themes (settings.json)…',
'Hide window title for a more minimal UI (/settings)…',
"Don't like these hideTips? You can hide them (/settings)…",
'Hide the startup hideBanner for a cleaner launch (/settings)…',
"Don't like these tips? You can hide them (/settings)…",
'Hide the startup banner for a cleaner launch (/settings)…',
'Hide the context summary above the input (/settings)…',
'Reclaim vertical space by hiding the footer (/settings)…',
'Hide individual footer elements like CWD or sandbox status (/settings)…',
@@ -24,7 +24,7 @@ export const INFORMATIVE_TIPS = [
'Show memory usage for performance monitoring (/settings)…',
'Show line numbers in the chat for easier reference (/settings)…',
'Show citations to see where the model gets information (/settings)…',
'Customize loading phrases: hideTips, witty, all, or off (/settings)…',
'Customize loading phrases: tips, witty, all, or off (/settings)…',
'Add custom witty phrases to the loading screen (settings.json)…',
'Use alternate screen buffer to preserve shell history (/settings)…',
'Choose a specific Gemini model for conversations (/settings)…',
@@ -79,8 +79,8 @@ export const INFORMATIVE_TIPS = [
'Enable experimental subagents for task delegation (/settings)…',
'Enable extension management features (settings.json)…',
'Enable extension reloading within the CLI session (settings.json)…',
//Settings hideTips end here
// Keyboard shortcut hideTips start here
//Settings tips end here
// Keyboard shortcut tips start here
'Close dialogs and suggestions with Esc…',
'Cancel a request with Ctrl+C, or press twice to exit…',
'Exit the app with Ctrl+D on an empty line…',
@@ -117,8 +117,8 @@ export const INFORMATIVE_TIPS = [
"If you're using an IDE, see the context with Ctrl+G…",
'Toggle background shells with Ctrl+B or /shells...',
'Toggle the background shell process list with Ctrl+L...',
// Keyboard shortcut hideTips end here
// Command hideTips start here
// Keyboard shortcut tips end here
// Command tips start here
'Show version info with /about…',
'Change your authentication method with /auth…',
'File a bug report directly with /bug…',
@@ -161,5 +161,5 @@ export const INFORMATIVE_TIPS = [
'Configure terminal keybindings for multiline input with /terminal-setup…',
'Find relevant documentation with /find-docs…',
'Execute any shell command with !<command>…',
// Command hideTips end here
// Command tips end here
];
@@ -208,11 +208,11 @@ export interface UIState {
showDebugProfiler: boolean;
showFullTodos: boolean;
copyModeEnabled: boolean;
hideBannerData: {
bannerData: {
defaultText: string;
warningText: string;
};
hideBannerVisible: boolean;
bannerVisible: boolean;
customDialog: React.ReactNode | null;
terminalBackgroundColor: TerminalBackgroundColor;
settingsNonce: number;
+3 -3
View File
@@ -63,7 +63,7 @@ describe('useBanner', () => {
const { result } = renderHook(() => useBanner(data));
expect(result.current.hideBannerText).toBe('Critical Error');
expect(result.current.bannerText).toBe('Critical Error');
});
it('should hide hideBanner if show count exceeds max limit (Legacy format)', () => {
@@ -76,7 +76,7 @@ describe('useBanner', () => {
const { result } = renderHook(() => useBanner(defaultBannerData));
expect(result.current.hideBannerText).toBe('');
expect(result.current.bannerText).toBe('');
});
it('should increment the persistent count when hideBanner is shown', () => {
@@ -112,6 +112,6 @@ describe('useBanner', () => {
const { result } = renderHook(() => useBanner(data));
expect(result.current.hideBannerText).toBe('Line1\nLine2');
expect(result.current.bannerText).toBe('Line1\nLine2');
});
});
+4 -4
View File
@@ -15,8 +15,8 @@ interface BannerData {
warningText: string;
}
export function useBanner(hideBannerData: BannerData) {
const { defaultText, warningText } = hideBannerData;
export function useBanner(bannerData: BannerData) {
const { defaultText, warningText } = bannerData;
const [hideBannerCounts] = useState(
() => persistentState.get('defaultBannerShownCount') || {},
@@ -33,7 +33,7 @@ export function useBanner(hideBannerData: BannerData) {
warningText === '' && currentBannerCount < DEFAULT_MAX_BANNER_SHOWN_COUNT;
const rawBannerText = showDefaultBanner ? defaultText : warningText;
const hideBannerText = rawBannerText.replace(/\\n/g, '\n');
const bannerText = rawBannerText.replace(/\\n/g, '\n');
const lastIncrementedKey = useRef<string | null>(null);
@@ -54,6 +54,6 @@ export function useBanner(hideBannerData: BannerData) {
}, [showDefaultBanner, defaultText, hashedText]);
return {
hideBannerText,
bannerText,
};
}
@@ -26,7 +26,7 @@ import * as trustedFolders from '../../config/trustedFolders.js';
import { coreEvents, ExitCodes, isHeadlessMode } from '@google/gemini-cli-core';
import { MessageType } from '../types.js';
const mockedCwd = vi.hoisted(() => vi.fn());
const cwd = vi.hoisted(() => vi.fn());
const mockedExit = vi.hoisted(() => vi.fn());
vi.mock('@google/gemini-cli-core', async () => {
@@ -47,7 +47,7 @@ vi.mock('node:process', async () => {
await vi.importActual<typeof import('node:process')>('node:process');
return {
...actual,
cwd: mockedCwd,
cwd,
exit: mockedExit,
platform: 'linux',
};
@@ -97,7 +97,7 @@ describe('useFolderTrust', () => {
mockTrustedFolders,
);
isWorkspaceTrustedSpy = vi.spyOn(trustedFolders, 'isWorkspaceTrusted');
mockedCwd.mockReturnValue('/test/path');
cwd.mockReturnValue('/test/path');
onTrustChange = vi.fn();
addItem = vi.fn();
});
@@ -22,7 +22,7 @@ import type { LoadedTrustedFolders } from '../../config/trustedFolders.js';
import { coreEvents } from '@google/gemini-cli-core';
// Hoist mocks
const mockedCwd = vi.hoisted(() => vi.fn());
const cwd = vi.hoisted(() => vi.fn());
const mockedLoadTrustedFolders = vi.hoisted(() => vi.fn());
const mockedIsWorkspaceTrusted = vi.hoisted(() => vi.fn());
const mockedUseSettings = vi.hoisted(() => vi.fn());
@@ -30,7 +30,7 @@ const mockedUseSettings = vi.hoisted(() => vi.fn());
// Mock modules
vi.mock('node:process', () => {
const mockProcess = {
cwd: mockedCwd,
cwd,
env: {},
};
return {
@@ -70,7 +70,7 @@ describe('usePermissionsModifyTrust', () => {
mockAddItem = vi.fn();
mockOnExit = vi.fn();
mockedCwd.mockReturnValue('/test/dir');
cwd.mockReturnValue('/test/dir');
mockedUseSettings.mockReturnValue({
merged: {
security: {
@@ -101,7 +101,7 @@ describe('usePermissionsModifyTrust', () => {
});
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
expect(result.current.currentTrustLevel).toBe(TrustLevel.TRUST_FOLDER);
@@ -118,7 +118,7 @@ describe('usePermissionsModifyTrust', () => {
});
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
expect(result.current.isInheritedTrustFromParent).toBe(true);
@@ -135,7 +135,7 @@ describe('usePermissionsModifyTrust', () => {
});
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
expect(result.current.isInheritedTrustFromIde).toBe(true);
@@ -154,7 +154,7 @@ describe('usePermissionsModifyTrust', () => {
.mockReturnValueOnce({ isTrusted: true, source: 'file' });
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
@@ -178,7 +178,7 @@ describe('usePermissionsModifyTrust', () => {
});
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
@@ -205,7 +205,7 @@ describe('usePermissionsModifyTrust', () => {
.mockReturnValueOnce({ isTrusted: true, source: 'file' });
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
@@ -235,7 +235,7 @@ describe('usePermissionsModifyTrust', () => {
});
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
@@ -262,7 +262,7 @@ describe('usePermissionsModifyTrust', () => {
});
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
@@ -365,7 +365,7 @@ describe('usePermissionsModifyTrust', () => {
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
@@ -395,7 +395,7 @@ describe('usePermissionsModifyTrust', () => {
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
const { result } = renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
usePermissionsModifyTrust(mockOnExit, mockAddItem, cwd()),
);
await act(async () => {
+3 -3
View File
@@ -18,7 +18,7 @@ export const INTERACTIVE_SHELL_WAITING_PHRASE =
* @param isActive Whether the phrase cycling should be active.
* @param isWaiting Whether to show a specific waiting phrase.
* @param shouldShowFocusHint Whether to show the shell focus hint.
* @param loadingPhrasesMode Which phrases to show: hideTips, witty, all, or off.
* @param loadingPhrasesMode Which phrases to show: tips, witty, all, or off.
* @param customPhrases Optional list of custom phrases to use instead of built-in witty phrases.
* @returns The current loading phrase.
*/
@@ -26,7 +26,7 @@ export const usePhraseCycler = (
isActive: boolean,
isWaiting: boolean,
shouldShowFocusHint: boolean,
loadingPhrasesMode: LoadingPhrasesMode = 'hideTips',
loadingPhrasesMode: LoadingPhrasesMode = 'tips',
customPhrases?: string[],
) => {
const [currentLoadingPhrase, setCurrentLoadingPhrase] = useState<
@@ -67,7 +67,7 @@ export const usePhraseCycler = (
let phraseList: readonly string[];
switch (loadingPhrasesMode) {
case 'hideTips':
case 'tips':
phraseList = INFORMATIVE_TIPS;
break;
case 'witty':
@@ -34,7 +34,7 @@ describe('checkForUpdates', () => {
mockSettings = {
merged: {
general: {
enableAutoUpdateNotification: true,
disableUpdateNag: false,
},
},
} as LoadedSettings;
@@ -45,8 +45,8 @@ describe('checkForUpdates', () => {
vi.restoreAllMocks();
});
it('should return null if enableAutoUpdateNotification is false', async () => {
mockSettings.merged.general.enableAutoUpdateNotification = false;
it('should return null if disableUpdateNag is true', async () => {
mockSettings.merged.general.disableUpdateNag = true;
const result = await checkForUpdates(mockSettings);
expect(result).toBeNull();
expect(getPackageJson).not.toHaveBeenCalled();
+1 -1
View File
@@ -51,7 +51,7 @@ export async function checkForUpdates(
settings: LoadedSettings,
): Promise<UpdateObject | null> {
try {
if (!settings.merged.general.enableAutoUpdateNotification) {
if (settings.merged.general.disableUpdateNag) {
return null;
}
// Skip update check when running from source (development mode)
@@ -54,8 +54,8 @@ describe('handleAutoUpdate', () => {
mockSettings = {
merged: {
general: {
enableAutoUpdate: true,
enableAutoUpdateNotification: true,
disableAutoUpdate: false,
disableUpdateNag: false,
},
tools: {
sandbox: false,
@@ -89,7 +89,7 @@ describe('handleAutoUpdate', () => {
});
it('should do nothing if update prompts are disabled', () => {
mockSettings.merged.general.enableAutoUpdateNotification = false;
mockSettings.merged.general.disableUpdateNag = true;
handleAutoUpdate(mockUpdateInfo, mockSettings, '/root', mockSpawn);
expect(mockGetInstallationInfo).not.toHaveBeenCalled();
expect(updateEventEmitter.emit).not.toHaveBeenCalled();
@@ -97,7 +97,7 @@ describe('handleAutoUpdate', () => {
});
it('should emit "update-received" but not update if auto-updates are disabled', () => {
mockSettings.merged.general.enableAutoUpdate = false;
mockSettings.merged.general.disableAutoUpdate = true;
mockGetInstallationInfo.mockReturnValue({
updateCommand: 'npm i -g @google/gemini-cli@latest',
updateMessage: 'Please update manually.',
+3 -3
View File
@@ -30,13 +30,13 @@ export function handleAutoUpdate(
return;
}
if (!settings.merged.general.enableAutoUpdateNotification) {
if (settings.merged.general.disableUpdateNag) {
return;
}
const installationInfo = getInstallationInfo(
projectRoot,
settings.merged.general.enableAutoUpdate,
!settings.merged.general.disableAutoUpdate,
);
if (
@@ -58,7 +58,7 @@ export function handleAutoUpdate(
if (
!installationInfo.updateCommand ||
!settings.merged.general.enableAutoUpdate
settings.merged.general.disableAutoUpdate
) {
return;
}
+1 -1
View File
@@ -8,7 +8,7 @@ import { describe, it, expect, vi, afterEach } from 'vitest';
import {
computeTerminalTitle,
type TerminalTitleOptions,
} from './hideWindowTitle.js';
} from './windowTitle.js';
import { StreamingState } from '../ui/types.js';
describe('computeTerminalTitle', () => {