summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui
diff options
context:
space:
mode:
authorchristine betts <[email protected]>2025-07-09 21:16:42 +0000
committerGitHub <[email protected]>2025-07-09 21:16:42 +0000
commitda50a1eefbd0751aaf137882595d500e6b6b4179 (patch)
tree4a07182ca01f20074a5544db5caf5a198cabae6c /packages/cli/src/ui
parent063481faa4b1c86868689580ff0fbd8cb04141e3 (diff)
Add system-wide settings config for administrators (#3498)
Co-authored-by: Jack Wotherspoon <[email protected]>
Diffstat (limited to 'packages/cli/src/ui')
-rw-r--r--packages/cli/src/ui/App.test.tsx62
-rw-r--r--packages/cli/src/ui/components/AuthDialog.test.tsx14
-rw-r--r--packages/cli/src/ui/components/ThemeDialog.tsx20
3 files changed, 73 insertions, 23 deletions
diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx
index 8390dac1..22547ae1 100644
--- a/packages/cli/src/ui/App.test.tsx
+++ b/packages/cli/src/ui/App.test.tsx
@@ -185,19 +185,30 @@ describe('App UI', () => {
let currentUnmount: (() => void) | undefined;
const createMockSettings = (
- settings: Partial<Settings> = {},
+ settings: {
+ system?: Partial<Settings>;
+ user?: Partial<Settings>;
+ workspace?: Partial<Settings>;
+ } = {},
): LoadedSettings => {
+ const systemSettingsFile: SettingsFile = {
+ path: '/system/settings.json',
+ settings: settings.system || {},
+ };
const userSettingsFile: SettingsFile = {
path: '/user/settings.json',
- settings: {},
+ settings: settings.user || {},
};
const workspaceSettingsFile: SettingsFile = {
path: '/workspace/.gemini/settings.json',
- settings: {
- ...settings,
- },
+ settings: settings.workspace || {},
};
- return new LoadedSettings(userSettingsFile, workspaceSettingsFile, []);
+ return new LoadedSettings(
+ systemSettingsFile,
+ userSettingsFile,
+ workspaceSettingsFile,
+ [],
+ );
};
beforeEach(() => {
@@ -222,7 +233,7 @@ describe('App UI', () => {
mockConfig.getShowMemoryUsage.mockReturnValue(false); // Default for most tests
// Ensure a theme is set so the theme dialog does not appear.
- mockSettings = createMockSettings({ theme: 'Default' });
+ mockSettings = createMockSettings({ workspace: { theme: 'Default' } });
});
afterEach(() => {
@@ -268,8 +279,7 @@ describe('App UI', () => {
it('should display custom contextFileName in footer when set and count is 1', async () => {
mockSettings = createMockSettings({
- contextFileName: 'AGENTS.md',
- theme: 'Default',
+ workspace: { contextFileName: 'AGENTS.md', theme: 'Default' },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(1);
mockConfig.getDebugMode.mockReturnValue(false);
@@ -288,8 +298,10 @@ describe('App UI', () => {
it('should display a generic message when multiple context files with different names are provided', async () => {
mockSettings = createMockSettings({
- contextFileName: ['AGENTS.md', 'CONTEXT.md'],
- theme: 'Default',
+ workspace: {
+ contextFileName: ['AGENTS.md', 'CONTEXT.md'],
+ theme: 'Default',
+ },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(2);
mockConfig.getDebugMode.mockReturnValue(false);
@@ -308,8 +320,7 @@ describe('App UI', () => {
it('should display custom contextFileName with plural when set and count is > 1', async () => {
mockSettings = createMockSettings({
- contextFileName: 'MY_NOTES.TXT',
- theme: 'Default',
+ workspace: { contextFileName: 'MY_NOTES.TXT', theme: 'Default' },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(3);
mockConfig.getDebugMode.mockReturnValue(false);
@@ -328,8 +339,7 @@ describe('App UI', () => {
it('should not display context file message if count is 0, even if contextFileName is set', async () => {
mockSettings = createMockSettings({
- contextFileName: 'ANY_FILE.MD',
- theme: 'Default',
+ workspace: { contextFileName: 'ANY_FILE.MD', theme: 'Default' },
});
mockConfig.getGeminiMdFileCount.mockReturnValue(0);
mockConfig.getDebugMode.mockReturnValue(false);
@@ -399,7 +409,9 @@ describe('App UI', () => {
it('should not display Tips component when hideTips is true', async () => {
mockSettings = createMockSettings({
- hideTips: true,
+ workspace: {
+ hideTips: true,
+ },
});
const { unmount } = render(
@@ -413,6 +425,24 @@ describe('App UI', () => {
expect(vi.mocked(Tips)).not.toHaveBeenCalled();
});
+ it('should show tips if system says show, but workspace and user settings say hide', async () => {
+ mockSettings = createMockSettings({
+ system: { hideTips: false },
+ user: { hideTips: true },
+ workspace: { hideTips: true },
+ });
+
+ const { unmount } = render(
+ <App
+ config={mockConfig as unknown as ServerConfig}
+ settings={mockSettings}
+ />,
+ );
+ currentUnmount = unmount;
+ await Promise.resolve();
+ expect(vi.mocked(Tips)).toHaveBeenCalled();
+ });
+
describe('when no theme is set', () => {
let originalNoColor: string | undefined;
diff --git a/packages/cli/src/ui/components/AuthDialog.test.tsx b/packages/cli/src/ui/components/AuthDialog.test.tsx
index 60a8a930..d9f1bd2c 100644
--- a/packages/cli/src/ui/components/AuthDialog.test.tsx
+++ b/packages/cli/src/ui/components/AuthDialog.test.tsx
@@ -30,6 +30,10 @@ describe('AuthDialog', () => {
const settings: LoadedSettings = new LoadedSettings(
{
+ settings: {},
+ path: '',
+ },
+ {
settings: {
selectedAuthType: AuthType.USE_GEMINI,
},
@@ -87,6 +91,12 @@ describe('AuthDialog', () => {
path: '',
},
{
+ settings: {
+ selectedAuthType: undefined,
+ },
+ path: '',
+ },
+ {
settings: {},
path: '',
},
@@ -148,6 +158,10 @@ describe('AuthDialog', () => {
const onSelect = vi.fn();
const settings: LoadedSettings = new LoadedSettings(
{
+ settings: {},
+ path: '',
+ },
+ {
settings: {
selectedAuthType: AuthType.USE_GEMINI,
},
diff --git a/packages/cli/src/ui/components/ThemeDialog.tsx b/packages/cli/src/ui/components/ThemeDialog.tsx
index 9351d5a1..ba49f8e3 100644
--- a/packages/cli/src/ui/components/ThemeDialog.tsx
+++ b/packages/cli/src/ui/components/ThemeDialog.tsx
@@ -57,6 +57,7 @@ export function ThemeDialog({
const scopeItems = [
{ label: 'User Settings', value: SettingScope.User },
{ label: 'Workspace Settings', value: SettingScope.Workspace },
+ { label: 'System Settings', value: SettingScope.System },
];
const handleThemeSelect = (themeName: string) => {
@@ -86,16 +87,21 @@ export function ThemeDialog({
}
});
+ const otherScopes = Object.values(SettingScope).filter(
+ (scope) => scope !== selectedScope,
+ );
+
+ const modifiedInOtherScopes = otherScopes.filter(
+ (scope) => settings.forScope(scope).settings.theme !== undefined,
+ );
+
let otherScopeModifiedMessage = '';
- const otherScope =
- selectedScope === SettingScope.User
- ? SettingScope.Workspace
- : SettingScope.User;
- if (settings.forScope(otherScope).settings.theme !== undefined) {
+ if (modifiedInOtherScopes.length > 0) {
+ const modifiedScopesStr = modifiedInOtherScopes.join(', ');
otherScopeModifiedMessage =
settings.forScope(selectedScope).settings.theme !== undefined
- ? `(Also modified in ${otherScope})`
- : `(Modified in ${otherScope})`;
+ ? `(Also modified in ${modifiedScopesStr})`
+ : `(Modified in ${modifiedScopesStr})`;
}
// Constants for calculating preview pane layout.