summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/App.test.tsx
blob: a13b4997d554ea5ae78610af38c50983eff549e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 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
});