fix(cli): Fix type errors in UI hooks tests (#11483)

This commit is contained in:
Sandy Tao
2025-10-19 17:16:16 -07:00
committed by GitHub
parent 23e52f0ff3
commit 0fd9ff0f53
11 changed files with 356 additions and 277 deletions
@@ -149,10 +149,16 @@ describe('useSlashCommandProcessor', () => {
openPrivacyNotice: vi.fn(),
openSettingsDialog: vi.fn(),
openModelDialog: mockOpenModelDialog,
openPermissionsDialog: vi.fn(),
quit: mockSetQuittingMessages,
setDebugMessage: vi.fn(),
toggleCorgiMode: vi.fn(),
toggleDebugProfiler: vi.fn(),
dispatchExtensionStateUpdate: vi.fn(),
addConfirmUpdateExtensionRequest: vi.fn(),
},
new Map(), // extensionsUpdateState
true, // isConfigInitialized
),
);
@@ -175,7 +181,7 @@ describe('useSlashCommandProcessor', () => {
expect(result.current.slashCommands).toHaveLength(1);
});
expect(result.current.slashCommands[0]?.name).toBe('test');
expect(result.current.slashCommands?.[0]?.name).toBe('test');
expect(mockBuiltinLoadCommands).toHaveBeenCalledTimes(1);
expect(mockFileLoadCommands).toHaveBeenCalledTimes(1);
expect(mockMcpLoadCommands).toHaveBeenCalledTimes(1);
@@ -456,7 +462,7 @@ describe('useSlashCommandProcessor', () => {
name: 'loadwiththoughts',
action: vi.fn().mockResolvedValue({
type: 'load_history',
history: [{ type: MessageType.MODEL, text: 'response' }],
history: [{ type: MessageType.GEMINI, text: 'response' }],
clientHistory: historyWithThoughts,
}),
});
@@ -901,18 +907,26 @@ describe('useSlashCommandProcessor', () => {
mockClearItems,
mockLoadHistory,
vi.fn(), // refreshStatic
vi.fn(), // onDebugMessage
vi.fn(), // openThemeDialog
mockOpenAuthDialog,
vi.fn(), // openEditorDialog
vi.fn(), // toggleCorgiMode
mockSetQuittingMessages,
vi.fn(), // openPrivacyNotice
vi.fn(), // openSettingsDialog
vi.fn(), // toggleVimEnabled
vi.fn().mockResolvedValue(false), // toggleVimEnabled
vi.fn(), // setIsProcessing
vi.fn(), // setGeminiMdFileCount
{
openAuthDialog: vi.fn(),
openThemeDialog: vi.fn(),
openEditorDialog: vi.fn(),
openPrivacyNotice: vi.fn(),
openSettingsDialog: vi.fn(),
openModelDialog: vi.fn(),
openPermissionsDialog: vi.fn(),
quit: vi.fn(),
setDebugMessage: vi.fn(),
toggleCorgiMode: vi.fn(),
toggleDebugProfiler: vi.fn(),
dispatchExtensionStateUpdate: vi.fn(),
addConfirmUpdateExtensionRequest: vi.fn(),
},
new Map(), // extensionsUpdateState
true, // isConfigInitialized
),
);
@@ -959,7 +973,7 @@ describe('useSlashCommandProcessor', () => {
it('should log a simple slash command', async () => {
const result = setupProcessorHook(loggingTestCommands);
await waitFor(() =>
expect(result.current.slashCommands.length).toBeGreaterThan(0),
expect(result.current.slashCommands?.length).toBeGreaterThan(0),
);
await act(async () => {
await result.current.handleSlashCommand('/logtest');
@@ -978,7 +992,7 @@ describe('useSlashCommandProcessor', () => {
it('logs nothing for a bogus command', async () => {
const result = setupProcessorHook(loggingTestCommands);
await waitFor(() =>
expect(result.current.slashCommands.length).toBeGreaterThan(0),
expect(result.current.slashCommands?.length).toBeGreaterThan(0),
);
await act(async () => {
await result.current.handleSlashCommand('/bogusbogusbogus');
@@ -990,7 +1004,7 @@ describe('useSlashCommandProcessor', () => {
it('logs a failure event for a failed command', async () => {
const result = setupProcessorHook(loggingTestCommands);
await waitFor(() =>
expect(result.current.slashCommands.length).toBeGreaterThan(0),
expect(result.current.slashCommands?.length).toBeGreaterThan(0),
);
await act(async () => {
await result.current.handleSlashCommand('/fail');
@@ -1009,7 +1023,7 @@ describe('useSlashCommandProcessor', () => {
it('should log a slash command with a subcommand', async () => {
const result = setupProcessorHook(loggingTestCommands);
await waitFor(() =>
expect(result.current.slashCommands.length).toBeGreaterThan(0),
expect(result.current.slashCommands?.length).toBeGreaterThan(0),
);
await act(async () => {
await result.current.handleSlashCommand('/logwithsub sub');
@@ -1027,7 +1041,7 @@ describe('useSlashCommandProcessor', () => {
it('should log the command path when an alias is used', async () => {
const result = setupProcessorHook(loggingTestCommands);
await waitFor(() =>
expect(result.current.slashCommands.length).toBeGreaterThan(0),
expect(result.current.slashCommands?.length).toBeGreaterThan(0),
);
await act(async () => {
await result.current.handleSlashCommand('/la');
@@ -1043,7 +1057,7 @@ describe('useSlashCommandProcessor', () => {
it('should not log for unknown commands', async () => {
const result = setupProcessorHook(loggingTestCommands);
await waitFor(() =>
expect(result.current.slashCommands.length).toBeGreaterThan(0),
expect(result.current.slashCommands?.length).toBeGreaterThan(0),
);
await act(async () => {
await result.current.handleSlashCommand('/unknown');
@@ -148,6 +148,7 @@ describe('useAtCompletion', () => {
useGitignore: false,
useGeminiignore: false,
cache: false,
cacheTtl: 0,
enableRecursiveFileSearch: true,
disableFuzzySearch: false,
});
@@ -239,8 +240,8 @@ describe('useAtCompletion', () => {
initialize: vi.fn().mockResolvedValue(undefined),
search: vi
.fn()
.mockImplementation(async (...args) =>
realFileSearch.search(...args),
.mockImplementation(async (pattern, options) =>
realFileSearch.search(pattern, options),
),
};
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
@@ -6,7 +6,15 @@
/** @vitest-environment jsdom */
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import {
describe,
it,
expect,
beforeEach,
vi,
afterEach,
type Mock,
} from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useCommandCompletion } from './useCommandCompletion.js';
import type { CommandContext } from '../commands/types.js';
@@ -45,7 +53,7 @@ const setupMocks = ({
slashCompletionRange?: { completionStart: number; completionEnd: number };
}) => {
// Mock for @-completions
(useAtCompletion as vi.Mock).mockImplementation(
(useAtCompletion as Mock).mockImplementation(
({
enabled,
setSuggestions,
@@ -61,7 +69,7 @@ const setupMocks = ({
);
// Mock for /-completions
(useSlashCompletion as vi.Mock).mockImplementation(
(useSlashCompletion as Mock).mockImplementation(
({
enabled,
setSuggestions,
@@ -6,7 +6,7 @@
import { act, renderHook } from '@testing-library/react';
import { vi } from 'vitest';
import { useConsoleMessages } from './useConsoleMessages';
import { useConsoleMessages } from './useConsoleMessages.js';
import { useCallback } from 'react';
describe('useConsoleMessages', () => {
+10 -7
View File
@@ -7,7 +7,7 @@
import { renderHook, act } from '@testing-library/react';
import { EventEmitter } from 'node:events';
import { useFocus } from './useFocus.js';
import { vi } from 'vitest';
import { vi, type Mock } from 'vitest';
import { useStdin, useStdout } from 'ink';
import { KeypressProvider } from '../contexts/KeypressContext.js';
import React from 'react';
@@ -29,15 +29,18 @@ const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(KeypressProvider, null, children);
describe('useFocus', () => {
let stdin: EventEmitter;
let stdout: { write: vi.Func };
let stdin: EventEmitter & { resume: Mock; pause: Mock };
let stdout: { write: Mock };
beforeEach(() => {
stdin = new EventEmitter();
stdin.resume = vi.fn();
stdin.pause = vi.fn();
stdin = Object.assign(new EventEmitter(), {
resume: vi.fn(),
pause: vi.fn(),
});
stdout = { write: vi.fn() };
mockedUseStdin.mockReturnValue({ stdin } as ReturnType<typeof useStdin>);
mockedUseStdin.mockReturnValue({ stdin } as unknown as ReturnType<
typeof useStdin
>);
mockedUseStdout.mockReturnValue({ stdout } as unknown as ReturnType<
typeof useStdout
>);
@@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { vi } from 'vitest';
import { vi, type Mock, type MockInstance } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useFolderTrust } from './useFolderTrust.js';
import type { LoadedSettings } from '../../config/settings.js';
@@ -28,10 +28,10 @@ vi.mock('node:process', async () => {
describe('useFolderTrust', () => {
let mockSettings: LoadedSettings;
let mockTrustedFolders: LoadedTrustedFolders;
let loadTrustedFoldersSpy: vi.SpyInstance;
let isWorkspaceTrustedSpy: vi.SpyInstance;
let loadTrustedFoldersSpy: MockInstance;
let isWorkspaceTrustedSpy: MockInstance;
let onTrustChange: (isTrusted: boolean | undefined) => void;
let addItem: vi.Mock;
let addItem: Mock;
beforeEach(() => {
mockSettings = {
@@ -1396,10 +1396,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirm,
onCancel: vi.fn(),
message: 'Replace text?',
displayedText: 'Replace old with new',
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: 'old',
newContent: 'new',
},
tool: {
name: 'replace',
@@ -1422,10 +1426,10 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'info',
title: 'Read File',
onConfirm: mockOnConfirm,
onCancel: vi.fn(),
message: 'Read file?',
displayedText: 'Read /test/file.txt',
prompt: 'Read /test/file.txt?',
},
tool: {
name: 'read_file',
@@ -1474,10 +1478,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirmReplace,
onCancel: vi.fn(),
message: 'Replace text?',
displayedText: 'Replace old with new',
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: 'old',
newContent: 'new',
},
tool: {
name: 'replace',
@@ -1500,10 +1508,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirmWrite,
onCancel: vi.fn(),
message: 'Write file?',
displayedText: 'Write to /test/new.txt',
fileName: 'new.txt',
filePath: '/test/new.txt',
fileDiff: 'fake diff',
originalContent: null,
newContent: 'content',
},
tool: {
name: 'write_file',
@@ -1526,10 +1538,10 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'info',
title: 'Read File',
onConfirm: mockOnConfirmRead,
onCancel: vi.fn(),
message: 'Read file?',
displayedText: 'Read /test/file.txt',
prompt: 'Read /test/file.txt?',
},
tool: {
name: 'read_file',
@@ -1577,10 +1589,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirm,
onCancel: vi.fn(),
message: 'Replace text?',
displayedText: 'Replace old with new',
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: 'old',
newContent: 'new',
},
tool: {
name: 'replace',
@@ -1597,9 +1613,7 @@ describe('useGeminiStream', () => {
const { result } = renderTestHook(awaitingApprovalToolCalls);
await act(async () => {
await result.current.handleApprovalModeChange(
ApprovalMode.REQUIRE_CONFIRMATION,
);
await result.current.handleApprovalModeChange(ApprovalMode.DEFAULT);
});
// No tools should be auto-approved
@@ -1627,10 +1641,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirmSuccess,
onCancel: vi.fn(),
message: 'Replace text?',
displayedText: 'Replace old with new',
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: 'old',
newContent: 'new',
},
tool: {
name: 'replace',
@@ -1653,10 +1671,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirmError,
onCancel: vi.fn(),
message: 'Write file?',
displayedText: 'Write to /test/file.txt',
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: null,
newContent: 'content',
},
tool: {
name: 'write_file',
@@ -1711,7 +1733,7 @@ describe('useGeminiStream', () => {
invocation: {
getDescription: () => 'Mock description',
} as unknown as AnyToolInvocation,
} as TrackedWaitingToolCall,
} as unknown as TrackedWaitingToolCall,
];
const { result } = renderTestHook(awaitingApprovalToolCalls);
@@ -1735,10 +1757,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
onCancel: vi.fn(),
message: 'Replace text?',
displayedText: 'Replace old with new',
type: 'edit',
title: 'Confirm Edit',
// No onConfirm method
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: 'old',
newContent: 'new',
} as any,
tool: {
name: 'replace',
@@ -1776,10 +1802,14 @@ describe('useGeminiStream', () => {
status: 'awaiting_approval',
responseSubmittedToGemini: false,
confirmationDetails: {
type: 'edit',
title: 'Confirm Edit',
onConfirm: mockOnConfirmAwaiting,
onCancel: vi.fn(),
message: 'Replace text?',
displayedText: 'Replace old with new',
fileName: 'file.txt',
filePath: '/test/file.txt',
fileDiff: 'fake diff',
originalContent: 'old',
newContent: 'new',
},
tool: {
name: 'replace',
@@ -1801,12 +1831,6 @@ describe('useGeminiStream', () => {
},
status: 'executing',
responseSubmittedToGemini: false,
confirmationDetails: {
onConfirm: mockOnConfirmExecuting,
onCancel: vi.fn(),
message: 'Write file?',
displayedText: 'Write to /test/file.txt',
},
tool: {
name: 'write_file',
displayName: 'write_file',
@@ -12,6 +12,7 @@ import { KeypressProvider } from '../contexts/KeypressContext.js';
import { useStdin } from 'ink';
import { EventEmitter } from 'node:events';
import { PassThrough } from 'node:stream';
import type { Mock } from 'vitest';
// Mock the 'ink' module to control stdin
vi.mock('ink', async (importOriginal) => {
@@ -56,8 +57,8 @@ class MockStdin extends EventEmitter {
isTTY = true;
isRaw = false;
setRawMode = vi.fn();
on = this.addListener;
removeListener = this.removeListener;
override on = this.addListener;
override removeListener = super.removeListener;
write = vi.fn();
resume = vi.fn();
@@ -112,7 +113,7 @@ describe('useKeypress', () => {
beforeEach(() => {
vi.clearAllMocks();
stdin = new MockStdin();
(useStdin as vi.Mock).mockReturnValue({
(useStdin as Mock).mockReturnValue({
stdin,
setRawMode: mockSetRawMode,
});
@@ -50,7 +50,6 @@ describe('usePhraseCycler', () => {
const { result } = renderHook(() => usePhraseCycler(true, false));
// Initial phrase should be one of the witty phrases
expect(WITTY_LOADING_PHRASES).toContain(result.current);
const _initialPhrase = result.current;
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
@@ -58,7 +57,6 @@ describe('usePhraseCycler', () => {
// Phrase should change and be one of the witty phrases
expect(WITTY_LOADING_PHRASES).toContain(result.current);
const _secondPhrase = result.current;
act(() => {
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
});
@@ -159,7 +157,7 @@ describe('usePhraseCycler', () => {
randomMock.mockRestore();
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
rerender({ isActive: true, isWaiting: false, customPhrases: undefined });
rerender({ isActive: true, isWaiting: false, customPhrases: [] });
expect(WITTY_LOADING_PHRASES).toContain(result.current);
});
@@ -188,8 +186,7 @@ describe('usePhraseCycler', () => {
{ initialProps: { isActive: true, isWaiting: false } },
);
const _initialPhrase = result.current;
expect(WITTY_LOADING_PHRASES).toContain(_initialPhrase);
expect(WITTY_LOADING_PHRASES).toContain(result.current);
// Cycle to a different phrase (potentially)
act(() => {
File diff suppressed because it is too large Load Diff