feat(ux): Surface internal errors via unified event system (#11803)

This commit is contained in:
Abhi
2025-10-23 14:14:14 -04:00
committed by GitHub
parent fd773a2d6b
commit 27b7279608
9 changed files with 676 additions and 3 deletions
+78
View File
@@ -64,9 +64,12 @@ import {
loadEnvironment,
migrateDeprecatedSettings,
SettingScope,
saveSettings,
type SettingsFile,
} from './settings.js';
import { FatalConfigError, GEMINI_DIR, Storage } from '@google/gemini-cli-core';
import { ExtensionEnablementManager } from './extensions/extensionEnablement.js';
import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js';
const MOCK_WORKSPACE_DIR = '/mock/workspace';
// Use the (mocked) GEMINI_DIR for consistency
@@ -96,6 +99,23 @@ vi.mock('fs', async (importOriginal) => {
vi.mock('./extension.js');
const mockCoreEvents = vi.hoisted(() => ({
emitFeedback: vi.fn(),
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
coreEvents: mockCoreEvents,
};
});
vi.mock('../utils/commentJson.js', () => ({
updateSettingsFilePreservingFormat: vi.fn(),
}));
vi.mock('strip-json-comments', () => ({
default: vi.fn((content) => content),
}));
@@ -2495,4 +2515,62 @@ describe('Settings Loading and Merging', () => {
expect(setValueSpy).not.toHaveBeenCalled();
});
});
describe('saveSettings', () => {
it('should save settings using updateSettingsFilePreservingFormat', () => {
const mockUpdateSettings = vi.mocked(updateSettingsFilePreservingFormat);
const settingsFile = {
path: '/mock/settings.json',
settings: { ui: { theme: 'dark' } },
originalSettings: { ui: { theme: 'dark' } },
} as unknown as SettingsFile;
saveSettings(settingsFile);
expect(mockUpdateSettings).toHaveBeenCalledWith('/mock/settings.json', {
ui: { theme: 'dark' },
});
});
it('should create directory if it does not exist', () => {
const mockFsExistsSync = vi.mocked(fs.existsSync);
const mockFsMkdirSync = vi.mocked(fs.mkdirSync);
mockFsExistsSync.mockReturnValue(false);
const settingsFile = {
path: '/mock/new/dir/settings.json',
settings: {},
originalSettings: {},
} as unknown as SettingsFile;
saveSettings(settingsFile);
expect(mockFsExistsSync).toHaveBeenCalledWith('/mock/new/dir');
expect(mockFsMkdirSync).toHaveBeenCalledWith('/mock/new/dir', {
recursive: true,
});
});
it('should emit error feedback if saving fails', () => {
const mockUpdateSettings = vi.mocked(updateSettingsFilePreservingFormat);
const error = new Error('Write failed');
mockUpdateSettings.mockImplementation(() => {
throw error;
});
const settingsFile = {
path: '/mock/settings.json',
settings: {},
originalSettings: {},
} as unknown as SettingsFile;
saveSettings(settingsFile);
expect(mockCoreEvents.emitFeedback).toHaveBeenCalledWith(
'error',
'There was an error saving your latest settings changes.',
error,
);
});
});
});
+6 -1
View File
@@ -15,6 +15,7 @@ import {
GEMINI_DIR,
getErrorMessage,
Storage,
coreEvents,
} from '@google/gemini-cli-core';
import stripJsonComments from 'strip-json-comments';
import { DefaultLight } from '../ui/themes/default-light.js';
@@ -799,6 +800,10 @@ export function saveSettings(settingsFile: SettingsFile): void {
settingsToSave as Record<string, unknown>,
);
} catch (error) {
console.error('Error saving user settings file:', error);
coreEvents.emitFeedback(
'error',
'There was an error saving your latest settings changes.',
error,
);
}
}