fix(cli): allow closing debug console after auto-open via flicker (#18795)

This commit is contained in:
Sandy Tao
2026-02-10 21:37:23 -08:00
committed by GitHub
parent 0d034b8c18
commit 2af5a3a01e
3 changed files with 128 additions and 22 deletions

View File

@@ -1520,28 +1520,14 @@ Logging in with Google... Restarting Gemini CLI to continue.
if (keyMatchers[Command.SHOW_ERROR_DETAILS](key)) {
if (settings.merged.general.devtools) {
void (async () => {
try {
const { startDevToolsServer } = await import(
'../utils/devtoolsService.js'
);
const { openBrowserSecurely, shouldLaunchBrowser } = await import(
'@google/gemini-cli-core'
);
const url = await startDevToolsServer(config);
if (shouldLaunchBrowser()) {
try {
await openBrowserSecurely(url);
} catch (e) {
setShowErrorDetails((prev) => !prev);
debugLogger.warn('Failed to open browser securely:', e);
}
} else {
setShowErrorDetails((prev) => !prev);
}
} catch (e) {
setShowErrorDetails(true);
debugLogger.error('Failed to start DevTools server:', e);
}
const { toggleDevToolsPanel } = await import(
'../utils/devtoolsService.js'
);
await toggleDevToolsPanel(
config,
() => setShowErrorDetails((prev) => !prev),
() => setShowErrorDetails(true),
);
})();
} else {
setShowErrorDetails((prev) => !prev);

View File

@@ -70,12 +70,20 @@ vi.mock('./activityLogger.js', () => ({
},
}));
const mockShouldLaunchBrowser = vi.hoisted(() => vi.fn(() => true));
const mockOpenBrowserSecurely = vi.hoisted(() =>
vi.fn(() => Promise.resolve()),
);
vi.mock('@google/gemini-cli-core', () => ({
debugLogger: {
log: vi.fn(),
debug: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
},
shouldLaunchBrowser: mockShouldLaunchBrowser,
openBrowserSecurely: mockOpenBrowserSecurely,
}));
vi.mock('ws', () => ({
@@ -92,6 +100,7 @@ vi.mock('gemini-cli-devtools', () => ({
import {
setupInitialActivityLogger,
startDevToolsServer,
toggleDevToolsPanel,
resetForTesting,
} from './devtoolsService.js';
@@ -426,4 +435,86 @@ describe('devtoolsService', () => {
expect(mockAddNetworkTransport).toHaveBeenCalledTimes(3);
});
});
describe('toggleDevToolsPanel', () => {
it('calls toggle when browser opens successfully', async () => {
const config = createMockConfig();
const toggle = vi.fn();
const setOpen = vi.fn();
mockShouldLaunchBrowser.mockReturnValue(true);
mockOpenBrowserSecurely.mockResolvedValue(undefined);
mockDevToolsInstance.start.mockResolvedValue('http://127.0.0.1:25417');
mockDevToolsInstance.getPort.mockReturnValue(25417);
const promise = toggleDevToolsPanel(config, toggle, setOpen);
await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1));
MockWebSocket.instances[0].simulateError();
await promise;
expect(toggle).toHaveBeenCalledTimes(1);
expect(setOpen).not.toHaveBeenCalled();
});
it('calls toggle when browser fails to open', async () => {
const config = createMockConfig();
const toggle = vi.fn();
const setOpen = vi.fn();
mockShouldLaunchBrowser.mockReturnValue(true);
mockOpenBrowserSecurely.mockRejectedValue(new Error('no browser'));
mockDevToolsInstance.start.mockResolvedValue('http://127.0.0.1:25417');
mockDevToolsInstance.getPort.mockReturnValue(25417);
const promise = toggleDevToolsPanel(config, toggle, setOpen);
await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1));
MockWebSocket.instances[0].simulateError();
await promise;
expect(toggle).toHaveBeenCalledTimes(1);
expect(setOpen).not.toHaveBeenCalled();
});
it('calls toggle when shouldLaunchBrowser returns false', async () => {
const config = createMockConfig();
const toggle = vi.fn();
const setOpen = vi.fn();
mockShouldLaunchBrowser.mockReturnValue(false);
mockDevToolsInstance.start.mockResolvedValue('http://127.0.0.1:25417');
mockDevToolsInstance.getPort.mockReturnValue(25417);
const promise = toggleDevToolsPanel(config, toggle, setOpen);
await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1));
MockWebSocket.instances[0].simulateError();
await promise;
expect(toggle).toHaveBeenCalledTimes(1);
expect(setOpen).not.toHaveBeenCalled();
});
it('calls setOpen when DevTools server fails to start', async () => {
const config = createMockConfig();
const toggle = vi.fn();
const setOpen = vi.fn();
mockDevToolsInstance.start.mockRejectedValue(new Error('fail'));
const promise = toggleDevToolsPanel(config, toggle, setOpen);
await vi.waitFor(() => expect(MockWebSocket.instances.length).toBe(1));
MockWebSocket.instances[0].simulateError();
await promise;
expect(toggle).not.toHaveBeenCalled();
expect(setOpen).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -210,6 +210,35 @@ async function startDevToolsServerImpl(config: Config): Promise<string> {
return url;
}
/**
* Handles the F12 key toggle for the DevTools panel.
* Starts the DevTools server, attempts to open the browser,
* and always calls the toggle callback regardless of the outcome.
*/
export async function toggleDevToolsPanel(
config: Config,
toggle: () => void,
setOpen: () => void,
): Promise<void> {
try {
const { openBrowserSecurely, shouldLaunchBrowser } = await import(
'@google/gemini-cli-core'
);
const url = await startDevToolsServer(config);
if (shouldLaunchBrowser()) {
try {
await openBrowserSecurely(url);
} catch (e) {
debugLogger.warn('Failed to open browser securely:', e);
}
}
toggle();
} catch (e) {
setOpen();
debugLogger.error('Failed to start DevTools server:', e);
}
}
/** Reset module-level state — test only. */
export function resetForTesting() {
promotionAttempts = 0;