/** * @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(); }); });