Improve code coverage for cli package (#13724)

This commit is contained in:
Megha Bansal
2025-11-24 23:11:46 +05:30
committed by GitHub
parent 569c6f1dd0
commit 95693e265e
47 changed files with 5115 additions and 489 deletions
+725
View File
@@ -18,7 +18,10 @@ import {
setupUnhandledRejectionHandler,
validateDnsResolutionOrder,
startInteractiveUI,
getNodeMemoryArgs,
} from './gemini.js';
import os from 'node:os';
import v8 from 'node:v8';
import { type LoadedSettings } from './config/settings.js';
import { appEvents, AppEvent } from './utils/events.js';
import {
@@ -162,6 +165,7 @@ vi.mock('./utils/sandbox.js', () => ({
vi.mock('./utils/relaunch.js', () => ({
relaunchAppInChildProcess: vi.fn(),
relaunchOnExitCode: vi.fn(),
}));
vi.mock('./config/sandboxConfig.js', () => ({
@@ -339,6 +343,88 @@ describe('gemini.tsx main function', () => {
});
});
describe('setWindowTitle', () => {
it('should set window title when hideWindowTitle is false', async () => {
// setWindowTitle is not exported, but we can test its effect if we had a way to call it.
// Since we can't easily call it directly without exporting it, we skip direct testing
// and rely on startInteractiveUI tests which call it.
});
});
describe('initializeOutputListenersAndFlush', () => {
afterEach(() => {
vi.restoreAllMocks();
});
it('should flush backlogs and setup listeners if no listeners exist', async () => {
const { coreEvents } = await import('@google/gemini-cli-core');
const { initializeOutputListenersAndFlush } = await import('./gemini.js');
// Mock listenerCount to return 0
vi.spyOn(coreEvents, 'listenerCount').mockReturnValue(0);
const drainSpy = vi.spyOn(coreEvents, 'drainBacklogs');
initializeOutputListenersAndFlush();
expect(drainSpy).toHaveBeenCalled();
// We can't easily check if listeners were added without access to the internal state of coreEvents,
// but we can verify that drainBacklogs was called.
});
});
describe('getNodeMemoryArgs', () => {
let osTotalMemSpy: MockInstance;
let v8GetHeapStatisticsSpy: MockInstance;
beforeEach(() => {
osTotalMemSpy = vi.spyOn(os, 'totalmem');
v8GetHeapStatisticsSpy = vi.spyOn(v8, 'getHeapStatistics');
delete process.env['GEMINI_CLI_NO_RELAUNCH'];
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should return empty array if GEMINI_CLI_NO_RELAUNCH is set', () => {
process.env['GEMINI_CLI_NO_RELAUNCH'] = 'true';
expect(getNodeMemoryArgs(false)).toEqual([]);
});
it('should return empty array if current heap limit is sufficient', () => {
osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024); // 16GB
v8GetHeapStatisticsSpy.mockReturnValue({
heap_size_limit: 8 * 1024 * 1024 * 1024, // 8GB
});
// Target is 50% of 16GB = 8GB. Current is 8GB. No relaunch needed.
expect(getNodeMemoryArgs(false)).toEqual([]);
});
it('should return memory args if current heap limit is insufficient', () => {
osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024); // 16GB
v8GetHeapStatisticsSpy.mockReturnValue({
heap_size_limit: 4 * 1024 * 1024 * 1024, // 4GB
});
// Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed.
expect(getNodeMemoryArgs(false)).toEqual(['--max-old-space-size=8192']);
});
it('should log debug info when isDebugMode is true', () => {
const debugSpy = vi.spyOn(debugLogger, 'debug');
osTotalMemSpy.mockReturnValue(16 * 1024 * 1024 * 1024);
v8GetHeapStatisticsSpy.mockReturnValue({
heap_size_limit: 4 * 1024 * 1024 * 1024,
});
getNodeMemoryArgs(true);
expect(debugSpy).toHaveBeenCalledWith(
expect.stringContaining('Current heap size'),
);
expect(debugSpy).toHaveBeenCalledWith(
expect.stringContaining('Need to relaunch with more memory'),
);
});
});
describe('gemini.tsx main function kitty protocol', () => {
let originalEnvNoRelaunch: string | undefined;
let setRawModeSpy: MockInstance<
@@ -460,6 +546,618 @@ describe('gemini.tsx main function kitty protocol', () => {
expect(setRawModeSpy).toHaveBeenCalledWith(true);
expect(detectAndEnableKittyProtocol).toHaveBeenCalledTimes(1);
});
it.each([
{ flag: 'listExtensions' },
{ flag: 'listSessions' },
{ flag: 'deleteSession', value: 'session-id' },
])('should handle --$flag flag', async ({ flag, value }) => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
const { listSessions, deleteSession } = await import('./utils/sessions.js');
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
vi.mocked(loadSettings).mockReturnValue({
merged: {
advanced: {},
security: { auth: {} },
ui: {},
},
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
const mockConfig = {
isInteractive: () => false,
getQuestion: () => '',
getSandbox: () => false,
getDebugMode: () => false,
getListExtensions: () => flag === 'listExtensions',
getListSessions: () => flag === 'listSessions',
getDeleteSession: () => (flag === 'deleteSession' ? value : undefined),
getExtensions: () => [{ name: 'ext1' }],
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
} as unknown as Config;
vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
vi.mock('./utils/sessions.js', () => ({
listSessions: vi.fn(),
deleteSession: vi.fn(),
}));
const debugLoggerLogSpy = vi
.spyOn(debugLogger, 'log')
.mockImplementation(() => {});
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
if (flag === 'listExtensions') {
expect(debugLoggerLogSpy).toHaveBeenCalledWith(
expect.stringContaining('ext1'),
);
} else if (flag === 'listSessions') {
expect(listSessions).toHaveBeenCalledWith(mockConfig);
} else if (flag === 'deleteSession') {
expect(deleteSession).toHaveBeenCalledWith(mockConfig, value);
}
expect(processExitSpy).toHaveBeenCalledWith(0);
processExitSpy.mockRestore();
});
it('should handle sandbox activation', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
const { start_sandbox } = await import('./utils/sandbox.js');
const { relaunchOnExitCode } = await import('./utils/relaunch.js');
const { loadSettings } = await import('./config/settings.js');
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadSettings).mockReturnValue({
merged: {
advanced: {},
security: { auth: {} },
ui: {},
},
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
const mockConfig = {
isInteractive: () => false,
getQuestion: () => '',
getSandbox: () => true,
getDebugMode: () => false,
getListExtensions: () => false,
getListSessions: () => false,
getDeleteSession: () => undefined,
getExtensions: () => [],
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
refreshAuth: vi.fn(),
} as unknown as Config;
vi.mocked(loadCliConfig).mockResolvedValue(mockConfig);
vi.mocked(loadSandboxConfig).mockResolvedValue({} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(relaunchOnExitCode).mockImplementation(async (fn) => {
await fn();
});
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(start_sandbox).toHaveBeenCalled();
expect(processExitSpy).toHaveBeenCalledWith(0);
processExitSpy.mockRestore();
});
it('should exit with error when --prompt-interactive is used with piped input', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
const core = await import('@google/gemini-cli-core');
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
const writeToStderrSpy = vi
.spyOn(core, 'writeToStderr')
.mockImplementation(() => true);
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: true,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue({
isInteractive: () => false,
getQuestion: () => '',
getSandbox: () => false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
// Mock stdin to be non-TTY
Object.defineProperty(process.stdin, 'isTTY', {
value: false,
configurable: true,
});
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(writeToStderrSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Error: The --prompt-interactive flag cannot be used',
),
);
expect(processExitSpy).toHaveBeenCalledWith(1);
processExitSpy.mockRestore();
writeToStderrSpy.mockRestore();
Object.defineProperty(process.stdin, 'isTTY', {
value: true,
configurable: true,
}); // Restore TTY
});
it('should log warning when theme is not found', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
const { themeManager } = await import('./ui/themes/theme-manager.js');
const debugLoggerWarnSpy = vi
.spyOn(debugLogger, 'warn')
.mockImplementation(() => {});
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
vi.mocked(loadSettings).mockReturnValue({
merged: {
advanced: {},
security: { auth: {} },
ui: { theme: 'non-existent-theme' },
},
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue({
isInteractive: () => false,
getQuestion: () => 'test',
getSandbox: () => false,
getDebugMode: () => false,
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
getListExtensions: () => false,
getListSessions: () => false,
getDeleteSession: () => undefined,
getToolRegistry: vi.fn(),
getExtensions: () => [],
getModel: () => 'gemini-pro',
getEmbeddingModel: () => 'embedding-001',
getApprovalMode: () => 'default',
getCoreTools: () => [],
getTelemetryEnabled: () => false,
getTelemetryLogPromptsEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getOutputFormat: () => 'text',
getUsageStatisticsEnabled: () => false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.spyOn(themeManager, 'setActiveTheme').mockReturnValue(false);
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(debugLoggerWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Warning: Theme "non-existent-theme" not found.'),
);
processExitSpy.mockRestore();
});
it('should handle session selector error', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
vi.mock('./utils/sessionUtils.js', () => ({
SessionSelector: class {
resolveSession = vi
.fn()
.mockRejectedValue(new Error('Session not found'));
},
}));
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: { theme: 'test' } },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
resume: 'session-id',
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue({
isInteractive: () => true,
getQuestion: () => '',
getSandbox: () => false,
getDebugMode: () => false,
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
getListExtensions: () => false,
getListSessions: () => false,
getDeleteSession: () => undefined,
getToolRegistry: vi.fn(),
getExtensions: () => [],
getModel: () => 'gemini-pro',
getEmbeddingModel: () => 'embedding-001',
getApprovalMode: () => 'default',
getCoreTools: () => [],
getTelemetryEnabled: () => false,
getTelemetryLogPromptsEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getOutputFormat: () => 'text',
getUsageStatisticsEnabled: () => false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('Error resuming session: Session not found'),
);
expect(processExitSpy).toHaveBeenCalledWith(1);
processExitSpy.mockRestore();
consoleErrorSpy.mockRestore();
});
it.skip('should log error when cleanupExpiredSessions fails', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
const { cleanupExpiredSessions } = await import(
'./utils/sessionCleanup.js'
);
vi.mocked(cleanupExpiredSessions).mockRejectedValue(
new Error('Cleanup failed'),
);
const debugLoggerErrorSpy = vi
.spyOn(debugLogger, 'error')
.mockImplementation(() => {});
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue({
isInteractive: () => false,
getQuestion: () => 'test',
getSandbox: () => false,
getDebugMode: () => false,
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
getListExtensions: () => false,
getListSessions: () => false,
getDeleteSession: () => undefined,
getToolRegistry: vi.fn(),
getExtensions: () => [],
getModel: () => 'gemini-pro',
getEmbeddingModel: () => 'embedding-001',
getApprovalMode: () => 'default',
getCoreTools: () => [],
getTelemetryEnabled: () => false,
getTelemetryLogPromptsEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getOutputFormat: () => 'text',
getUsageStatisticsEnabled: () => false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
// The mock is already set up at the top of the test
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(debugLoggerErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
'Failed to cleanup expired sessions: Cleanup failed',
),
);
expect(processExitSpy).toHaveBeenCalledWith(0); // Should not exit on cleanup failure
processExitSpy.mockRestore();
});
it('should handle refreshAuth failure', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
const debugLoggerErrorSpy = vi
.spyOn(debugLogger, 'error')
.mockImplementation(() => {});
vi.mocked(loadSettings).mockReturnValue({
merged: {
advanced: {},
security: { auth: { selectedType: 'google' } },
ui: {},
},
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadSandboxConfig).mockResolvedValue({} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue({
isInteractive: () => true,
getQuestion: () => '',
getSandbox: () => false,
getDebugMode: () => false,
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
getListExtensions: () => false,
getListSessions: () => false,
getDeleteSession: () => undefined,
getToolRegistry: vi.fn(),
getExtensions: () => [],
getModel: () => 'gemini-pro',
getEmbeddingModel: () => 'embedding-001',
getApprovalMode: () => 'default',
getCoreTools: () => [],
getTelemetryEnabled: () => false,
getTelemetryLogPromptsEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getOutputFormat: () => 'text',
getUsageStatisticsEnabled: () => false,
refreshAuth: vi.fn().mockRejectedValue(new Error('Auth refresh failed')),
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(debugLoggerErrorSpy).toHaveBeenCalledWith(
'Error authenticating:',
expect.any(Error),
);
expect(processExitSpy).toHaveBeenCalledWith(1);
processExitSpy.mockRestore();
});
it('should read from stdin in non-interactive mode', async () => {
const { loadCliConfig, parseArguments } = await import(
'./config/config.js'
);
const { loadSettings } = await import('./config/settings.js');
const { readStdin } = await import('./utils/readStdin.js');
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation((code) => {
throw new MockProcessExitError(code);
});
vi.mocked(loadSettings).mockReturnValue({
merged: { advanced: {}, security: { auth: {} }, ui: {} },
setValue: vi.fn(),
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
errors: [],
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(parseArguments).mockResolvedValue({
promptInteractive: false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mocked(loadCliConfig).mockResolvedValue({
isInteractive: () => false,
getQuestion: () => 'test-question',
getSandbox: () => false,
getDebugMode: () => false,
getPolicyEngine: vi.fn(),
getMessageBus: () => ({ subscribe: vi.fn() }),
initialize: vi.fn(),
getContentGeneratorConfig: vi.fn(),
getMcpServers: () => ({}),
getMcpClientManager: vi.fn(),
getIdeMode: () => false,
getExperimentalZedIntegration: () => false,
getScreenReader: () => false,
getGeminiMdFileCount: () => 0,
getProjectRoot: () => '/',
getListExtensions: () => false,
getListSessions: () => false,
getDeleteSession: () => undefined,
getToolRegistry: vi.fn(),
getExtensions: () => [],
getModel: () => 'gemini-pro',
getEmbeddingModel: () => 'embedding-001',
getApprovalMode: () => 'default',
getCoreTools: () => [],
getTelemetryEnabled: () => false,
getTelemetryLogPromptsEnabled: () => false,
getFileFilteringRespectGitIgnore: () => true,
getOutputFormat: () => 'text',
getUsageStatisticsEnabled: () => false,
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
vi.mock('./utils/readStdin.js', () => ({
readStdin: vi.fn().mockResolvedValue('stdin-data'),
}));
const runNonInteractiveSpy = vi.hoisted(() => vi.fn());
vi.mock('./nonInteractiveCli.js', () => ({
runNonInteractive: runNonInteractiveSpy,
}));
runNonInteractiveSpy.mockClear();
vi.mock('./validateNonInterActiveAuth.js', () => ({
validateNonInteractiveAuth: vi.fn().mockResolvedValue({}),
}));
// Mock stdin to be non-TTY
Object.defineProperty(process.stdin, 'isTTY', {
value: false,
configurable: true,
});
try {
await main();
} catch (e) {
if (!(e instanceof MockProcessExitError)) throw e;
}
expect(readStdin).toHaveBeenCalled();
// In this test setup, runNonInteractive might be called on the mocked module,
// but we need to ensure we are checking the correct spy instance.
// Since vi.mock is hoisted, runNonInteractiveSpy is defined early.
expect(runNonInteractiveSpy).toHaveBeenCalled();
const callArgs = runNonInteractiveSpy.mock.calls[0][0];
expect(callArgs.input).toBe('test-question');
expect(processExitSpy).toHaveBeenCalledWith(0);
processExitSpy.mockRestore();
Object.defineProperty(process.stdin, 'isTTY', {
value: true,
configurable: true,
});
});
});
describe('validateDnsResolutionOrder', () => {
@@ -599,6 +1297,33 @@ describe('startInteractiveUI', () => {
expect(reactElement).toBeDefined();
});
it('should enable mouse events when alternate buffer is enabled', async () => {
const { enableMouseEvents } = await import('@google/gemini-cli-core');
await startTestInteractiveUI(
mockConfig,
mockSettings,
mockStartupWarnings,
mockWorkspaceRoot,
undefined,
mockInitializationResult,
);
expect(enableMouseEvents).toHaveBeenCalled();
});
it('should patch console', async () => {
const { ConsolePatcher } = await import('./ui/utils/ConsolePatcher.js');
const patchSpy = vi.spyOn(ConsolePatcher.prototype, 'patch');
await startTestInteractiveUI(
mockConfig,
mockSettings,
mockStartupWarnings,
mockWorkspaceRoot,
undefined,
mockInitializationResult,
);
expect(patchSpy).toHaveBeenCalled();
});
it('should perform all startup tasks in correct order', async () => {
const { getCliVersion } = await import('./utils/version.js');
const { checkForUpdates } = await import('./ui/utils/updateCheck.js');