mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-15 08:31:14 -07:00
209 lines
5.5 KiB
TypeScript
209 lines
5.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import {
|
|
describe,
|
|
it,
|
|
expect,
|
|
vi,
|
|
beforeEach,
|
|
afterEach,
|
|
type Mock,
|
|
} from 'vitest';
|
|
import { act } from 'react';
|
|
import { renderHook } from '../../test-utils/render.js';
|
|
import { useSuspend } from './useSuspend.js';
|
|
import {
|
|
writeToStdout,
|
|
disableMouseEvents,
|
|
enableMouseEvents,
|
|
enterAlternateScreen,
|
|
exitAlternateScreen,
|
|
enableLineWrapping,
|
|
disableLineWrapping,
|
|
} from '@google/gemini-cli-core';
|
|
import {
|
|
cleanupTerminalOnExit,
|
|
terminalCapabilityManager,
|
|
} from '../utils/terminalCapabilityManager.js';
|
|
import { formatCommand } from '../key/keybindingUtils.js';
|
|
import { Command } from '../key/keyBindings.js';
|
|
|
|
vi.mock('@google/gemini-cli-core', async () => {
|
|
const actual = await vi.importActual('@google/gemini-cli-core');
|
|
return {
|
|
...actual,
|
|
writeToStdout: vi.fn(),
|
|
disableMouseEvents: vi.fn(),
|
|
enableMouseEvents: vi.fn(),
|
|
enterAlternateScreen: vi.fn(),
|
|
exitAlternateScreen: vi.fn(),
|
|
enableLineWrapping: vi.fn(),
|
|
disableLineWrapping: vi.fn(),
|
|
};
|
|
});
|
|
|
|
vi.mock('../utils/terminalCapabilityManager.js', () => ({
|
|
cleanupTerminalOnExit: vi.fn(),
|
|
terminalCapabilityManager: {
|
|
enableSupportedModes: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('useSuspend', () => {
|
|
const originalPlatform = process.platform;
|
|
let killSpy: Mock;
|
|
|
|
const setPlatform = (platform: NodeJS.Platform) => {
|
|
Object.defineProperty(process, 'platform', {
|
|
value: platform,
|
|
configurable: true,
|
|
});
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.clearAllMocks();
|
|
killSpy = vi
|
|
.spyOn(process, 'kill')
|
|
.mockReturnValue(true) as unknown as Mock;
|
|
// Default tests to a POSIX platform so suspend path assertions are stable.
|
|
setPlatform('linux');
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
killSpy.mockRestore();
|
|
setPlatform(originalPlatform);
|
|
});
|
|
|
|
it('cleans terminal state on suspend and restores/repaints on resume in alternate screen mode', () => {
|
|
const handleWarning = vi.fn();
|
|
const setRawMode = vi.fn();
|
|
const refreshStatic = vi.fn();
|
|
const setForceRerenderKey = vi.fn();
|
|
const enableSupportedModes =
|
|
terminalCapabilityManager.enableSupportedModes as unknown as Mock;
|
|
|
|
const { result, unmount } = renderHook(() =>
|
|
useSuspend({
|
|
handleWarning,
|
|
setRawMode,
|
|
refreshStatic,
|
|
setForceRerenderKey,
|
|
shouldUseAlternateScreen: true,
|
|
}),
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleSuspend();
|
|
});
|
|
|
|
const suspendKey = formatCommand(Command.SUSPEND_APP);
|
|
const undoKey = formatCommand(Command.UNDO);
|
|
|
|
expect(handleWarning).toHaveBeenCalledWith(
|
|
`Press ${suspendKey} again to suspend. Undo has moved to ${undoKey}.`,
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleSuspend();
|
|
});
|
|
|
|
expect(exitAlternateScreen).toHaveBeenCalledTimes(1);
|
|
expect(enableLineWrapping).toHaveBeenCalledTimes(1);
|
|
expect(writeToStdout).toHaveBeenCalledWith('\x1b[2J\x1b[H');
|
|
expect(disableMouseEvents).toHaveBeenCalledTimes(1);
|
|
expect(cleanupTerminalOnExit).toHaveBeenCalledTimes(1);
|
|
expect(setRawMode).toHaveBeenCalledWith(false);
|
|
expect(killSpy).toHaveBeenCalledWith(0, 'SIGTSTP');
|
|
|
|
act(() => {
|
|
process.emit('SIGCONT');
|
|
vi.runAllTimers();
|
|
});
|
|
|
|
expect(enterAlternateScreen).toHaveBeenCalledTimes(1);
|
|
expect(disableLineWrapping).toHaveBeenCalledTimes(1);
|
|
expect(enableSupportedModes).toHaveBeenCalledTimes(1);
|
|
expect(enableMouseEvents).toHaveBeenCalledTimes(1);
|
|
expect(setRawMode).toHaveBeenCalledWith(true);
|
|
expect(refreshStatic).toHaveBeenCalledTimes(1);
|
|
expect(setForceRerenderKey).toHaveBeenCalledTimes(1);
|
|
|
|
unmount();
|
|
});
|
|
|
|
it('does not toggle alternate screen or mouse restore when alternate screen mode is disabled', () => {
|
|
const handleWarning = vi.fn();
|
|
const setRawMode = vi.fn();
|
|
const refreshStatic = vi.fn();
|
|
const setForceRerenderKey = vi.fn();
|
|
|
|
const { result, unmount } = renderHook(() =>
|
|
useSuspend({
|
|
handleWarning,
|
|
setRawMode,
|
|
refreshStatic,
|
|
setForceRerenderKey,
|
|
shouldUseAlternateScreen: false,
|
|
}),
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleSuspend();
|
|
result.current.handleSuspend();
|
|
process.emit('SIGCONT');
|
|
vi.runAllTimers();
|
|
});
|
|
|
|
expect(exitAlternateScreen).not.toHaveBeenCalled();
|
|
expect(enterAlternateScreen).not.toHaveBeenCalled();
|
|
expect(enableLineWrapping).not.toHaveBeenCalled();
|
|
expect(disableLineWrapping).not.toHaveBeenCalled();
|
|
expect(enableMouseEvents).not.toHaveBeenCalled();
|
|
|
|
unmount();
|
|
});
|
|
|
|
it('warns and skips suspension on windows', () => {
|
|
setPlatform('win32');
|
|
|
|
const handleWarning = vi.fn();
|
|
const setRawMode = vi.fn();
|
|
const refreshStatic = vi.fn();
|
|
const setForceRerenderKey = vi.fn();
|
|
|
|
const { result, unmount } = renderHook(() =>
|
|
useSuspend({
|
|
handleWarning,
|
|
setRawMode,
|
|
refreshStatic,
|
|
setForceRerenderKey,
|
|
shouldUseAlternateScreen: true,
|
|
}),
|
|
);
|
|
|
|
act(() => {
|
|
result.current.handleSuspend();
|
|
});
|
|
handleWarning.mockClear();
|
|
|
|
act(() => {
|
|
result.current.handleSuspend();
|
|
});
|
|
|
|
const suspendKey = formatCommand(Command.SUSPEND_APP);
|
|
expect(handleWarning).toHaveBeenCalledWith(
|
|
`${suspendKey} suspend is not supported on Windows.`,
|
|
);
|
|
expect(killSpy).not.toHaveBeenCalled();
|
|
expect(cleanupTerminalOnExit).not.toHaveBeenCalled();
|
|
|
|
unmount();
|
|
});
|
|
});
|