Merge pull request #2834 from kulikrch/fix/theme-esc-cancel-2833-main

fix(cli): restore previous theme on /theme cancel (refs #2833)
This commit is contained in:
tanzhenxin 2026-04-05 14:43:34 +08:00 committed by GitHub
commit 6d9ee19dc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 68 additions and 1 deletions

View file

@ -0,0 +1,53 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { act } from 'react';
import { renderHook } from '@testing-library/react';
import type { LoadedSettings } from '../../config/settings.js';
import { SettingScope } from '../../config/settings.js';
import { useThemeCommand } from './useThemeCommand.js';
import { themeManager } from '../themes/theme-manager.js';
describe('useThemeCommand', () => {
beforeEach(() => {
vi.restoreAllMocks();
themeManager.setActiveTheme('Qwen Dark');
});
it('restores previous theme on cancel (Esc)', () => {
const setValue =
vi.fn<(scope: SettingScope, key: string, value: unknown) => void>();
const settings = {
merged: { ui: { theme: 'Qwen Dark' } },
user: { settings: { ui: {} } },
workspace: { settings: { ui: {} } },
setValue,
} as unknown as LoadedSettings;
const setThemeError = vi.fn<(error: string | null) => void>();
const addItem = vi.fn();
const { result } = renderHook(() =>
useThemeCommand(settings, setThemeError, addItem, null),
);
act(() => {
themeManager.setActiveTheme('Dracula');
result.current.openThemeDialog();
result.current.handleThemeHighlight('Default');
});
expect(themeManager.getActiveTheme().name).toBe('Default');
act(() => {
result.current.handleThemeSelect(undefined, SettingScope.User);
});
expect(themeManager.getActiveTheme().name).toBe('Dracula');
expect(setValue).not.toHaveBeenCalled();
expect(result.current.isThemeDialogOpen).toBe(false);
});
});

View file

@ -29,6 +29,9 @@ export const useThemeCommand = (
): UseThemeCommandReturn => {
const [isThemeDialogOpen, setIsThemeDialogOpen] =
useState(!!initialThemeError);
const [themeBeforeDialogOpen, setThemeBeforeDialogOpen] = useState<
string | undefined
>(themeManager.getActiveTheme().name);
const openThemeDialog = useCallback(() => {
if (process.env['NO_COLOR']) {
@ -43,6 +46,9 @@ export const useThemeCommand = (
);
return;
}
// The theme may temporarily change while navigating the list; keep the
// original value to restore it if user cancels with Esc/Ctrl+C.
setThemeBeforeDialogOpen(themeManager.getActiveTheme().name);
setIsThemeDialogOpen(true);
}, [addItem]);
@ -72,6 +78,14 @@ export const useThemeCommand = (
const handleThemeSelect = useCallback(
(themeName: string | undefined, scope: SettingScope) => {
// Undefined means "cancel": close dialog and restore original theme.
if (themeName === undefined) {
applyTheme(themeBeforeDialogOpen);
setThemeError(null);
setIsThemeDialogOpen(false);
return;
}
try {
// Merge user and workspace custom themes (workspace takes precedence)
const mergedCustomThemes = {
@ -100,7 +114,7 @@ export const useThemeCommand = (
setIsThemeDialogOpen(false); // Close the dialog
}
},
[applyTheme, loadedSettings, setThemeError],
[applyTheme, loadedSettings, setThemeError, themeBeforeDialogOpen],
);
return {