gemini-cli/packages/cli/src/ui/App.test.tsx

122 lines
3.6 KiB
TypeScript

// packages/cli/src/ui/App.test.tsx
import React from 'react';
import { render, cleanup } from 'ink-testing-library';
import { vi, describe, test, expect, beforeEach, afterEach } from 'vitest';
import App from './App.js';
import { useInputHistory } from './hooks/useInputHistory.js';
import { useGeminiStream } from './hooks/useGeminiStream.js';
import { StreamingState } from '../core/gemini-stream.js';
import type { HistoryItem } from './types.js';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { initializeConfig } from '../config/globalConfig.js';
// --- Mocks ---
// Mock the useGeminiStream hook
vi.mock('./hooks/useGeminiStream.js', () => ({
useGeminiStream: vi.fn(),
}));
// Mock the useInputHistory hook
vi.mock('./hooks/useInputHistory.js', () => ({
useInputHistory: vi.fn(),
}));
// Mock fs/path/os used for warnings check
vi.mock('fs', () => ({
default: {
existsSync: vi.fn().mockReturnValue(false),
readFileSync: vi.fn(),
unlinkSync: vi.fn(),
writeFileSync: vi.fn(),
},
}));
vi.mock('path', async (importOriginal) => {
const originalPath = await importOriginal<typeof import('path')>();
return {
...originalPath,
default: originalPath,
join: originalPath.join,
resolve: originalPath.resolve,
relative: originalPath.relative,
};
});
vi.mock('os', async (importOriginal) => {
const originalOs = await importOriginal<typeof import('os')>();
return {
...originalOs,
default: originalOs,
tmpdir: vi.fn().mockReturnValue('/tmp'),
};
});
// --- Test Suite ---
describe('App Component Rendering', () => {
// Define mock return values for the hooks
let mockSetQuery: ReturnType<typeof vi.fn>;
let mockResetHistoryNav: ReturnType<typeof vi.fn>;
let mockSubmitQuery: ReturnType<typeof vi.fn>;
beforeEach(() => {
// Reset mocks
vi.clearAllMocks();
(fs.existsSync as ReturnType<typeof vi.fn>).mockReturnValue(false);
// Initialize global config
initializeConfig({ model: 'test-model-v1' });
// Setup mock return values for hooks
mockSetQuery = vi.fn();
mockResetHistoryNav = vi.fn();
mockSubmitQuery = vi.fn().mockResolvedValue(undefined);
(useInputHistory as ReturnType<typeof vi.fn>).mockReturnValue({
query: '',
setQuery: mockSetQuery,
resetHistoryNav: mockResetHistoryNav,
inputKey: 0,
});
(useGeminiStream as ReturnType<typeof vi.fn>).mockReturnValue({
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
initError: null,
});
});
afterEach(() => {
cleanup();
vi.resetModules();
});
// Helper function to render App
const renderApp = (initialHookQuery = '') => {
(useInputHistory as ReturnType<typeof vi.fn>).mockReturnValue({
query: initialHookQuery,
setQuery: mockSetQuery,
resetHistoryNav: mockResetHistoryNav,
inputKey: 0,
});
return render(<App directory="/test/dir" />);
};
// --- Tests ---
test('should render initial placeholder with model', () => {
const { lastFrame } = renderApp();
expect(lastFrame()).toContain('Ask Gemini (test-model-v1)');
});
test('should pass query from useInputHistory to InputPrompt', () => {
const { lastFrame } = renderApp('test query from hook');
expect(lastFrame()).toContain('> test query from hook');
});
// Add more tests here for App's behavior, like:
// - Displaying startup warnings when the mocked fs.existsSync returns true
// - Displaying initError from useGeminiStream when it's not null
// - Ensuring handleInputSubmit calls the correct functions from the hooks
});