mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 13:22:35 -07:00
Merge branch 'main' into memory_usage3
This commit is contained in:
@@ -153,5 +153,49 @@ describe('footerItems', () => {
|
||||
expect(state.orderedIds).toContain('auth');
|
||||
expect(state.selectedIds.has('auth')).toBe(true);
|
||||
});
|
||||
|
||||
it('includes context-used in selectedIds when hideContextPercentage is false and items is undefined', () => {
|
||||
const settings = createMockSettings({
|
||||
ui: {
|
||||
footer: {
|
||||
hideContextPercentage: false,
|
||||
},
|
||||
},
|
||||
}).merged;
|
||||
|
||||
const state = resolveFooterState(settings);
|
||||
expect(state.selectedIds.has('context-used')).toBe(true);
|
||||
expect(state.orderedIds).toContain('context-used');
|
||||
});
|
||||
|
||||
it('does not include context-used in selectedIds when hideContextPercentage is true (default)', () => {
|
||||
const settings = createMockSettings({
|
||||
ui: {
|
||||
footer: {
|
||||
hideContextPercentage: true,
|
||||
},
|
||||
},
|
||||
}).merged;
|
||||
|
||||
const state = resolveFooterState(settings);
|
||||
expect(state.selectedIds.has('context-used')).toBe(false);
|
||||
// context-used should still be in orderedIds (as unselected)
|
||||
expect(state.orderedIds).toContain('context-used');
|
||||
});
|
||||
|
||||
it('persisted items array takes precedence over hideContextPercentage', () => {
|
||||
const settings = createMockSettings({
|
||||
ui: {
|
||||
footer: {
|
||||
items: ['workspace', 'model-name'],
|
||||
hideContextPercentage: false,
|
||||
},
|
||||
},
|
||||
}).merged;
|
||||
|
||||
const state = resolveFooterState(settings);
|
||||
// items array explicitly omits context-used, so it should not be selected
|
||||
expect(state.selectedIds.has('context-used')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -304,6 +304,25 @@ describe('gemini.tsx main function', () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should suppress AbortError and not open debug console', async () => {
|
||||
const debugLoggerErrorSpy = vi.spyOn(debugLogger, 'error');
|
||||
const debugLoggerLogSpy = vi.spyOn(debugLogger, 'log');
|
||||
const abortError = new DOMException(
|
||||
'The operation was aborted.',
|
||||
'AbortError',
|
||||
);
|
||||
|
||||
setupUnhandledRejectionHandler();
|
||||
process.emit('unhandledRejection', abortError, Promise.resolve());
|
||||
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(debugLoggerErrorSpy).not.toHaveBeenCalled();
|
||||
expect(debugLoggerLogSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Suppressed unhandled AbortError'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log unhandled promise rejections and open debug console on first error', async () => {
|
||||
const processExitSpy = vi
|
||||
.spyOn(process, 'exit')
|
||||
|
||||
@@ -164,6 +164,14 @@ export function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
||||
export function setupUnhandledRejectionHandler() {
|
||||
let unhandledRejectionOccurred = false;
|
||||
process.on('unhandledRejection', (reason, _promise) => {
|
||||
// AbortError is expected when the user cancels a request (e.g. pressing ESC).
|
||||
// It may surface as an unhandled rejection due to async timing in the
|
||||
// streaming pipeline, but it is not a bug.
|
||||
if (reason instanceof Error && reason.name === 'AbortError') {
|
||||
debugLogger.log(`Suppressed unhandled AbortError: ${reason.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMessage = `=========================================
|
||||
This is an unexpected error. Please file a bug report using the /bug tool.
|
||||
CRITICAL: Unhandled Promise Rejection!
|
||||
|
||||
@@ -13,7 +13,11 @@ import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useKeypress, type Key } from '../hooks/useKeypress.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { FooterRow, type FooterRowItem } from './Footer.js';
|
||||
import { ALL_ITEMS, resolveFooterState } from '../../config/footerItems.js';
|
||||
import {
|
||||
ALL_ITEMS,
|
||||
resolveFooterState,
|
||||
deriveItemsFromLegacySettings,
|
||||
} from '../../config/footerItems.js';
|
||||
import { SettingScope } from '../../config/settings.js';
|
||||
import { BaseSelectionList } from './shared/BaseSelectionList.js';
|
||||
import type { SelectionListItem } from '../hooks/useSelectionList.js';
|
||||
@@ -137,17 +141,16 @@ export const FooterConfigDialog: React.FC<FooterConfigDialogProps> = ({
|
||||
const handleSaveAndClose = useCallback(() => {
|
||||
const finalItems = orderedIds.filter((id: string) => selectedIds.has(id));
|
||||
const currentSetting = settings.merged.ui?.footer?.items;
|
||||
if (JSON.stringify(finalItems) !== JSON.stringify(currentSetting)) {
|
||||
// When items haven't been explicitly set yet (legacy mode), compare against
|
||||
// the legacy-derived items to avoid persisting items and silently overriding
|
||||
// legacy boolean settings like hideContextPercentage.
|
||||
const effectiveCurrent =
|
||||
currentSetting ?? deriveItemsFromLegacySettings(settings.merged);
|
||||
if (JSON.stringify(finalItems) !== JSON.stringify(effectiveCurrent)) {
|
||||
setSetting(SettingScope.User, 'ui.footer.items', finalItems);
|
||||
}
|
||||
onClose?.();
|
||||
}, [
|
||||
orderedIds,
|
||||
selectedIds,
|
||||
setSetting,
|
||||
settings.merged.ui?.footer?.items,
|
||||
onClose,
|
||||
]);
|
||||
}, [orderedIds, selectedIds, setSetting, settings.merged, onClose]);
|
||||
|
||||
const handleResetToDefaults = useCallback(() => {
|
||||
setSetting(SettingScope.User, 'ui.footer.items', undefined);
|
||||
|
||||
Reference in New Issue
Block a user