fix(cli): hide read-only settings scopes (#26249)

This commit is contained in:
Christian Van
2026-05-06 15:03:48 -04:00
committed by GitHub
parent 7fb5146c6b
commit e4242edf61
6 changed files with 251 additions and 29 deletions
@@ -25,7 +25,10 @@ import { waitFor } from '../../test-utils/async.js';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { SettingsDialog } from './SettingsDialog.js';
import { SettingScope } from '../../config/settings.js';
import { createMockSettings } from '../../test-utils/settings.js';
import {
createMockSettings,
type MockSettingsFile,
} from '../../test-utils/settings.js';
import { makeFakeConfig } from '@google/gemini-cli-core';
import { act } from 'react';
import { TEST_ONLY } from '../../utils/settingsUtils.js';
@@ -250,6 +253,17 @@ const renderDialog = async (
},
);
const createSettingsFile = (
path: string,
settings: Record<string, unknown> = {},
readOnly?: boolean,
): MockSettingsFile => ({
settings,
originalSettings: settings,
path,
readOnly,
});
describe('SettingsDialog', () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -618,6 +632,131 @@ describe('SettingsDialog', () => {
unmount();
});
it('should not offer read-only system settings as an editable target', async () => {
const settings = createMockSettings({
system: createSettingsFile('', {}, true),
});
const onSelect = vi.fn();
const { lastFrame, unmount } = await renderDialog(settings, onSelect);
await waitFor(() => {
expect(lastFrame()).toContain('Apply To');
});
const output = lastFrame();
expect(output).toContain('User Settings');
expect(output).toContain('Workspace Settings');
expect(output).not.toContain('System Settings');
unmount();
});
it('should not offer a read-only home-directory workspace as an editable target', async () => {
const settings = createMockSettings({
user: createSettingsFile('/mock/home/.gemini/settings.json'),
system: createSettingsFile('/mock/system/settings.json', {}, true),
systemDefaults: createSettingsFile(
'/mock/system-defaults/settings.json',
{},
true,
),
workspace: createSettingsFile('', {}, true),
});
const setValueSpy = vi.spyOn(settings, 'setValue');
const onSelect = vi.fn();
const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
settings,
onSelect,
);
await waitFor(() => {
expect(lastFrame()).toContain('Vim Mode');
});
const output = lastFrame();
expect(output).not.toContain('Workspace Settings');
expect(output).not.toContain('System Settings');
await act(async () => {
stdin.write(TerminalKeys.ENTER as string);
});
await waitUntilReady();
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.User,
'general.vimMode',
true,
);
unmount();
});
it('should fall back to the first writable scope when the selected scope is read-only', async () => {
const settings = createMockSettings({
user: createSettingsFile('', {}, true),
system: createSettingsFile('', {}, true),
workspace: createSettingsFile('/mock/workspace/.gemini/settings.json'),
});
const setValueSpy = vi.spyOn(settings, 'setValue');
const onSelect = vi.fn();
const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
settings,
onSelect,
);
await waitFor(() => {
expect(lastFrame()).toContain('Vim Mode');
});
await act(async () => {
stdin.write(TerminalKeys.ENTER as string);
});
await waitUntilReady();
expect(setValueSpy).toHaveBeenCalledWith(
SettingScope.Workspace,
'general.vimMode',
true,
);
unmount();
});
it('should not save when all editable scopes are read-only', async () => {
const settings = createMockSettings({
user: createSettingsFile('', {}, true),
system: createSettingsFile('', {}, true),
workspace: createSettingsFile(
'/mock/workspace/.gemini/settings.json',
{},
true,
),
});
const setValueSpy = vi.spyOn(settings, 'setValue');
const onSelect = vi.fn();
const { lastFrame, stdin, unmount, waitUntilReady } = await renderDialog(
settings,
onSelect,
);
await waitFor(() => {
expect(lastFrame()).toContain('Vim Mode');
});
await act(async () => {
stdin.write(TerminalKeys.ENTER as string);
});
await waitUntilReady();
expect(setValueSpy).not.toHaveBeenCalled();
unmount();
});
});
describe('Restart Prompt', () => {