diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index 8762583494..6016381f26 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -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'); diff --git a/packages/cli/src/ui/hooks/useAtCompletion.test.ts b/packages/cli/src/ui/hooks/useAtCompletion.test.ts index eb374c421a..5b4687f02c 100644 --- a/packages/cli/src/ui/hooks/useAtCompletion.test.ts +++ b/packages/cli/src/ui/hooks/useAtCompletion.test.ts @@ -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); diff --git a/packages/cli/src/ui/hooks/useCommandCompletion.test.ts b/packages/cli/src/ui/hooks/useCommandCompletion.test.ts index a900d0bbac..4cc53f9885 100644 --- a/packages/cli/src/ui/hooks/useCommandCompletion.test.ts +++ b/packages/cli/src/ui/hooks/useCommandCompletion.test.ts @@ -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, diff --git a/packages/cli/src/ui/hooks/useConsoleMessages.test.ts b/packages/cli/src/ui/hooks/useConsoleMessages.test.ts index b1d1acd668..a6c6409af3 100644 --- a/packages/cli/src/ui/hooks/useConsoleMessages.test.ts +++ b/packages/cli/src/ui/hooks/useConsoleMessages.test.ts @@ -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', () => { diff --git a/packages/cli/src/ui/hooks/useFocus.test.ts b/packages/cli/src/ui/hooks/useFocus.test.ts index cf0dad0dcc..a4f784a18a 100644 --- a/packages/cli/src/ui/hooks/useFocus.test.ts +++ b/packages/cli/src/ui/hooks/useFocus.test.ts @@ -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); + mockedUseStdin.mockReturnValue({ stdin } as unknown as ReturnType< + typeof useStdin + >); mockedUseStdout.mockReturnValue({ stdout } as unknown as ReturnType< typeof useStdout >); diff --git a/packages/cli/src/ui/hooks/useFolderTrust.test.ts b/packages/cli/src/ui/hooks/useFolderTrust.test.ts index 4812efc38b..6be20a3e63 100644 --- a/packages/cli/src/ui/hooks/useFolderTrust.test.ts +++ b/packages/cli/src/ui/hooks/useFolderTrust.test.ts @@ -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 = { diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx index ffce1348bc..f10d0f41c1 100644 --- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx +++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx @@ -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', diff --git a/packages/cli/src/ui/hooks/useKeypress.test.ts b/packages/cli/src/ui/hooks/useKeypress.test.ts index 22d729e4bc..495946efb5 100644 --- a/packages/cli/src/ui/hooks/useKeypress.test.ts +++ b/packages/cli/src/ui/hooks/useKeypress.test.ts @@ -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, }); diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts index 1ed99b05df..538f6d204b 100644 --- a/packages/cli/src/ui/hooks/usePhraseCycler.test.ts +++ b/packages/cli/src/ui/hooks/usePhraseCycler.test.ts @@ -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(() => { diff --git a/packages/cli/src/ui/hooks/vim.test.ts b/packages/cli/src/ui/hooks/vim.test.ts index c7af542612..2bfba0c31f 100644 --- a/packages/cli/src/ui/hooks/vim.test.ts +++ b/packages/cli/src/ui/hooks/vim.test.ts @@ -4,17 +4,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { renderHook, act } from '@testing-library/react'; import type React from 'react'; import { useVim } from './vim.js'; -import type { TextBuffer } from '../components/shared/text-buffer.js'; +import type { VimMode } from './vim.js'; +import type { Key } from './useKeypress.js'; +import type { + TextBuffer, + TextBufferState, +} from '../components/shared/text-buffer.js'; import { textBufferReducer } from '../components/shared/text-buffer.js'; // Mock the VimModeContext const mockVimContext = { vimEnabled: true, - vimMode: 'NORMAL' as const, + vimMode: 'NORMAL' as VimMode, toggleVimEnabled: vi.fn(), setVimMode: vi.fn(), }; @@ -24,29 +29,64 @@ vi.mock('../contexts/VimModeContext.js', () => ({ VimModeProvider: ({ children }: { children: React.ReactNode }) => children, })); +// Helper to create a full Key object from partial data +const createKey = (partial: Partial): Key => ({ + name: partial.name || '', + sequence: partial.sequence || '', + ctrl: partial.ctrl || false, + meta: partial.meta || false, + shift: partial.shift || false, + paste: partial.paste || false, + ...partial, +}); + +const createMockTextBufferState = ( + partial: Partial, +): TextBufferState => { + const lines = partial.lines || ['']; + return { + lines, + cursorRow: 0, + cursorCol: 0, + preferredCol: null, + undoStack: [], + redoStack: [], + clipboard: null, + selectionAnchor: null, + viewportWidth: 80, + viewportHeight: 24, + visualLayout: { + visualLines: lines, + logicalToVisualMap: lines.map((_, i) => [[i, 0]]), + visualToLogicalMap: lines.map((_, i) => [i, 0]), + }, + ...partial, + }; +}; + // Test constants const TEST_SEQUENCES = { - ESCAPE: { sequence: '\u001b', name: 'escape' }, - LEFT: { sequence: 'h' }, - RIGHT: { sequence: 'l' }, - UP: { sequence: 'k' }, - DOWN: { sequence: 'j' }, - INSERT: { sequence: 'i' }, - APPEND: { sequence: 'a' }, - DELETE_CHAR: { sequence: 'x' }, - DELETE: { sequence: 'd' }, - CHANGE: { sequence: 'c' }, - WORD_FORWARD: { sequence: 'w' }, - WORD_BACKWARD: { sequence: 'b' }, - WORD_END: { sequence: 'e' }, - LINE_START: { sequence: '0' }, - LINE_END: { sequence: '$' }, - REPEAT: { sequence: '.' }, + ESCAPE: createKey({ sequence: '\u001b', name: 'escape' }), + LEFT: createKey({ sequence: 'h' }), + RIGHT: createKey({ sequence: 'l' }), + UP: createKey({ sequence: 'k' }), + DOWN: createKey({ sequence: 'j' }), + INSERT: createKey({ sequence: 'i' }), + APPEND: createKey({ sequence: 'a' }), + DELETE_CHAR: createKey({ sequence: 'x' }), + DELETE: createKey({ sequence: 'd' }), + CHANGE: createKey({ sequence: 'c' }), + WORD_FORWARD: createKey({ sequence: 'w' }), + WORD_BACKWARD: createKey({ sequence: 'b' }), + WORD_END: createKey({ sequence: 'e' }), + LINE_START: createKey({ sequence: '0' }), + LINE_END: createKey({ sequence: '$' }), + REPEAT: createKey({ sequence: '.' }), } as const; describe('useVim hook', () => { let mockBuffer: Partial; - let mockHandleFinalSubmit: vi.Mock; + let mockHandleFinalSubmit: Mock; const createMockBuffer = ( text = 'hello world', @@ -66,7 +106,7 @@ describe('useVim hook', () => { text, move: vi.fn().mockImplementation((direction: string) => { let [row, col] = cursorState.pos; - const _line = lines[row] || ''; + const line = lines[row] || ''; if (direction === 'left') { col = Math.max(0, col - 1); } else if (direction === 'right') { @@ -109,7 +149,6 @@ describe('useVim hook', () => { vimAppendAtCursor: vi.fn().mockImplementation(() => { // Append moves cursor right (vim 'a' behavior - position after current char) const [row, col] = cursorState.pos; - const _line = lines[row] || ''; // In vim, 'a' moves cursor to position after current character // This allows inserting at the end of the line cursorState.pos = [row, col + 1]; @@ -134,12 +173,6 @@ describe('useVim hook', () => { }; }; - const _createMockSettings = (vimMode = true) => ({ - getValue: vi.fn().mockReturnValue(vimMode), - setValue: vi.fn(), - merged: { vimMode }, - }); - const renderVimHook = (buffer?: Partial) => renderHook(() => useVim((buffer || mockBuffer) as TextBuffer, mockHandleFinalSubmit), @@ -147,11 +180,11 @@ describe('useVim hook', () => { const exitInsertMode = (result: { current: { - handleInput: (input: { sequence: string; name: string }) => void; + handleInput: (key: Key) => boolean; }; }) => { act(() => { - result.current.handleInput({ sequence: '\u001b', name: 'escape' }); + result.current.handleInput(TEST_SEQUENCES.ESCAPE); }); }; @@ -200,7 +233,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'i' }); + result.current.handleInput(createKey({ sequence: 'i' })); }); expect(result.current.mode).toBe('INSERT'); @@ -210,7 +243,7 @@ describe('useVim hook', () => { expect(result.current.mode).toBe('NORMAL'); act(() => { - result.current.handleInput({ sequence: 'b' }); + result.current.handleInput(createKey({ sequence: 'b' })); }); expect(testBuffer.vimMoveWordBackward).toHaveBeenCalledWith(1); @@ -222,7 +255,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'h' }); + result.current.handleInput(createKey({ sequence: 'h' })); }); expect(mockBuffer.vimMoveLeft).toHaveBeenCalledWith(1); @@ -232,7 +265,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'l' }); + result.current.handleInput(createKey({ sequence: 'l' })); }); expect(mockBuffer.vimMoveRight).toHaveBeenCalledWith(1); @@ -243,7 +276,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'j' }); + result.current.handleInput(createKey({ sequence: 'j' })); }); expect(testBuffer.vimMoveDown).toHaveBeenCalledWith(1); @@ -254,7 +287,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'k' }); + result.current.handleInput(createKey({ sequence: 'k' })); }); expect(testBuffer.vimMoveUp).toHaveBeenCalledWith(1); @@ -264,7 +297,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: '0' }); + result.current.handleInput(createKey({ sequence: '0' })); }); expect(mockBuffer.vimMoveToLineStart).toHaveBeenCalled(); @@ -274,7 +307,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: '$' }); + result.current.handleInput(createKey({ sequence: '$' })); }); expect(mockBuffer.vimMoveToLineEnd).toHaveBeenCalled(); @@ -286,7 +319,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'a' }); + result.current.handleInput(createKey({ sequence: 'a' })); }); expect(mockBuffer.vimAppendAtCursor).toHaveBeenCalled(); @@ -297,7 +330,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'A' }); + result.current.handleInput(createKey({ sequence: 'A' })); }); expect(mockBuffer.vimAppendAtLineEnd).toHaveBeenCalled(); @@ -308,7 +341,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'o' }); + result.current.handleInput(createKey({ sequence: 'o' })); }); expect(mockBuffer.vimOpenLineBelow).toHaveBeenCalled(); @@ -319,7 +352,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'O' }); + result.current.handleInput(createKey({ sequence: 'O' })); }); expect(mockBuffer.vimOpenLineAbove).toHaveBeenCalled(); @@ -333,7 +366,7 @@ describe('useVim hook', () => { vi.clearAllMocks(); act(() => { - result.current.handleInput({ sequence: 'x' }); + result.current.handleInput(createKey({ sequence: 'x' })); }); expect(mockBuffer.vimDeleteChar).toHaveBeenCalledWith(1); @@ -344,7 +377,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'x' }); + result.current.handleInput(createKey({ sequence: 'x' })); }); expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1); @@ -354,7 +387,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); expect(mockBuffer.replaceRangeByOffset).not.toHaveBeenCalled(); @@ -366,12 +399,16 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - const handled = result.current.handleInput({ sequence: '3' }); + const handled = result.current.handleInput( + createKey({ sequence: '3' }), + ); expect(handled).toBe(true); }); act(() => { - const handled = result.current.handleInput({ sequence: 'h' }); + const handled = result.current.handleInput( + createKey({ sequence: 'h' }), + ); expect(handled).toBe(true); }); @@ -383,7 +420,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'x' }); + result.current.handleInput(createKey({ sequence: 'x' })); }); expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1); @@ -419,7 +456,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimMoveWordForward).toHaveBeenCalledWith(1); @@ -430,7 +467,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'b' }); + result.current.handleInput(createKey({ sequence: 'b' })); }); expect(testBuffer.vimMoveWordBackward).toHaveBeenCalledWith(1); @@ -441,7 +478,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'e' }); + result.current.handleInput(createKey({ sequence: 'e' })); }); expect(testBuffer.vimMoveWordEnd).toHaveBeenCalledWith(1); @@ -452,7 +489,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimMoveWordForward).toHaveBeenCalledWith(1); @@ -462,7 +499,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); expect(result.current.mode).toBe('NORMAL'); @@ -473,8 +510,8 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'd' }); - result.current.handleInput({ sequence: 'f' }); + result.current.handleInput(createKey({ sequence: 'd' })); + result.current.handleInput(createKey({ sequence: 'f' })); }); expect(mockBuffer.replaceRangeByOffset).not.toHaveBeenCalled(); @@ -485,7 +522,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); exitInsertMode(result); @@ -500,7 +537,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(mockBuffer); act(() => { - result.current.handleInput({ sequence: 'h' }); + result.current.handleInput(createKey({ sequence: 'h' })); }); expect(mockBuffer.move).not.toHaveBeenCalled(); @@ -515,14 +552,14 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'x' }); + result.current.handleInput(createKey({ sequence: 'x' })); }); expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1); testBuffer.cursor = [1, 2]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1); }); @@ -532,17 +569,17 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); expect(testBuffer.vimDeleteLine).toHaveBeenCalledTimes(1); testBuffer.cursor = [0, 0]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimDeleteLine).toHaveBeenCalledTimes(2); @@ -553,10 +590,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'e' }); + result.current.handleInput(createKey({ sequence: 'e' })); }); expect(testBuffer.vimChangeWordEnd).toHaveBeenCalledTimes(1); @@ -566,7 +603,7 @@ describe('useVim hook', () => { testBuffer.cursor = [0, 2]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimChangeWordEnd).toHaveBeenCalledTimes(2); @@ -577,10 +614,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); expect(testBuffer.vimChangeLine).toHaveBeenCalledTimes(1); @@ -590,7 +627,7 @@ describe('useVim hook', () => { testBuffer.cursor = [0, 1]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimChangeLine).toHaveBeenCalledTimes(2); @@ -601,10 +638,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimChangeWordForward).toHaveBeenCalledTimes(1); @@ -614,7 +651,7 @@ describe('useVim hook', () => { testBuffer.cursor = [0, 0]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimChangeWordForward).toHaveBeenCalledTimes(2); @@ -625,7 +662,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'D' }); + result.current.handleInput(createKey({ sequence: 'D' })); }); expect(testBuffer.vimDeleteToEndOfLine).toHaveBeenCalledTimes(1); @@ -633,7 +670,7 @@ describe('useVim hook', () => { vi.clearAllMocks(); // Clear all mocks instead of just one method act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimDeleteToEndOfLine).toHaveBeenCalledTimes(1); @@ -644,7 +681,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'C' }); + result.current.handleInput(createKey({ sequence: 'C' })); }); expect(testBuffer.vimChangeToEndOfLine).toHaveBeenCalledTimes(1); @@ -654,7 +691,7 @@ describe('useVim hook', () => { testBuffer.cursor = [0, 2]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimChangeToEndOfLine).toHaveBeenCalledTimes(2); @@ -665,14 +702,14 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'x' }); + result.current.handleInput(createKey({ sequence: 'x' })); }); expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1); testBuffer.cursor = [0, 2]; act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1); }); @@ -682,7 +719,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'a' }); + result.current.handleInput(createKey({ sequence: 'a' })); }); expect(result.current.mode).toBe('INSERT'); expect(testBuffer.cursor).toEqual([0, 11]); @@ -699,7 +736,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '^' }); + result.current.handleInput(createKey({ sequence: '^' })); }); expect(testBuffer.vimMoveToFirstNonWhitespace).toHaveBeenCalled(); @@ -710,7 +747,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'G' }); + result.current.handleInput(createKey({ sequence: 'G' })); }); expect(testBuffer.vimMoveToLastLine).toHaveBeenCalled(); @@ -722,12 +759,12 @@ describe('useVim hook', () => { // First 'g' sets pending state act(() => { - result.current.handleInput({ sequence: 'g' }); + result.current.handleInput(createKey({ sequence: 'g' })); }); // Second 'g' executes the command act(() => { - result.current.handleInput({ sequence: 'g' }); + result.current.handleInput(createKey({ sequence: 'g' })); }); expect(testBuffer.vimMoveToFirstLine).toHaveBeenCalled(); @@ -738,7 +775,7 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '3' }); + result.current.handleInput(createKey({ sequence: '3' })); }); act(() => { @@ -756,10 +793,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimDeleteWordForward).toHaveBeenCalledWith(1); @@ -767,7 +804,7 @@ describe('useVim hook', () => { it('should actually delete the complete word including trailing space', () => { // This test uses the real text-buffer reducer instead of mocks - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 0, @@ -776,7 +813,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_forward', @@ -790,7 +827,7 @@ describe('useVim hook', () => { }); it('should delete word from middle of word correctly', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 2, // cursor on 'l' in "hello" @@ -799,7 +836,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_forward', @@ -813,7 +850,7 @@ describe('useVim hook', () => { }); it('should handle dw at end of line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world'], cursorRow: 0, cursorCol: 6, // cursor on 'w' in "world" @@ -822,7 +859,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_forward', @@ -840,13 +877,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '2' }); + result.current.handleInput(createKey({ sequence: '2' })); }); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimDeleteWordForward).toHaveBeenCalledWith(2); @@ -858,17 +895,17 @@ describe('useVim hook', () => { // Execute dw act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); vi.clearAllMocks(); // Execute dot repeat act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimDeleteWordForward).toHaveBeenCalledWith(1); @@ -881,10 +918,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'e' }); + result.current.handleInput(createKey({ sequence: 'e' })); }); expect(testBuffer.vimDeleteWordEnd).toHaveBeenCalledWith(1); @@ -895,13 +932,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '3' }); + result.current.handleInput(createKey({ sequence: '3' })); }); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'e' }); + result.current.handleInput(createKey({ sequence: 'e' })); }); expect(testBuffer.vimDeleteWordEnd).toHaveBeenCalledWith(3); @@ -914,10 +951,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimChangeWordForward).toHaveBeenCalledWith(1); @@ -930,13 +967,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '2' }); + result.current.handleInput(createKey({ sequence: '2' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimChangeWordForward).toHaveBeenCalledWith(2); @@ -949,10 +986,10 @@ describe('useVim hook', () => { // Execute cw act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); // Exit INSERT mode @@ -963,7 +1000,7 @@ describe('useVim hook', () => { // Execute dot repeat act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimChangeWordForward).toHaveBeenCalledWith(1); @@ -977,10 +1014,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'e' }); + result.current.handleInput(createKey({ sequence: 'e' })); }); expect(testBuffer.vimChangeWordEnd).toHaveBeenCalledWith(1); @@ -992,13 +1029,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '2' }); + result.current.handleInput(createKey({ sequence: '2' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'e' }); + result.current.handleInput(createKey({ sequence: 'e' })); }); expect(testBuffer.vimChangeWordEnd).toHaveBeenCalledWith(2); @@ -1012,10 +1049,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); expect(testBuffer.vimChangeLine).toHaveBeenCalledWith(1); @@ -1030,13 +1067,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '3' }); + result.current.handleInput(createKey({ sequence: '3' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); expect(testBuffer.vimChangeLine).toHaveBeenCalledWith(3); @@ -1049,10 +1086,10 @@ describe('useVim hook', () => { // Execute cc act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); // Exit INSERT mode @@ -1063,7 +1100,7 @@ describe('useVim hook', () => { // Execute dot repeat act(() => { - result.current.handleInput({ sequence: '.' }); + result.current.handleInput(createKey({ sequence: '.' })); }); expect(testBuffer.vimChangeLine).toHaveBeenCalledWith(1); @@ -1077,10 +1114,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'b' }); + result.current.handleInput(createKey({ sequence: 'b' })); }); expect(testBuffer.vimDeleteWordBackward).toHaveBeenCalledWith(1); @@ -1091,13 +1128,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '2' }); + result.current.handleInput(createKey({ sequence: '2' })); }); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); act(() => { - result.current.handleInput({ sequence: 'b' }); + result.current.handleInput(createKey({ sequence: 'b' })); }); expect(testBuffer.vimDeleteWordBackward).toHaveBeenCalledWith(2); @@ -1110,10 +1147,10 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'b' }); + result.current.handleInput(createKey({ sequence: 'b' })); }); expect(testBuffer.vimChangeWordBackward).toHaveBeenCalledWith(1); @@ -1125,13 +1162,13 @@ describe('useVim hook', () => { const { result } = renderVimHook(testBuffer); act(() => { - result.current.handleInput({ sequence: '3' }); + result.current.handleInput(createKey({ sequence: '3' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'b' }); + result.current.handleInput(createKey({ sequence: 'b' })); }); expect(testBuffer.vimChangeWordBackward).toHaveBeenCalledWith(3); @@ -1146,22 +1183,22 @@ describe('useVim hook', () => { // Press 'd' to enter pending delete state act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); // Complete with 'w' act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); // Next 'd' should start a new pending state, not continue the previous one act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); // This should trigger dd (delete line), not an error act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); expect(testBuffer.vimDeleteLine).toHaveBeenCalledWith(1); @@ -1173,10 +1210,10 @@ describe('useVim hook', () => { // Execute cw act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); // Exit INSERT mode @@ -1184,10 +1221,10 @@ describe('useVim hook', () => { // Next 'c' should start a new pending state act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); act(() => { - result.current.handleInput({ sequence: 'c' }); + result.current.handleInput(createKey({ sequence: 'c' })); }); expect(testBuffer.vimChangeLine).toHaveBeenCalledWith(1); @@ -1199,17 +1236,17 @@ describe('useVim hook', () => { // Enter pending delete state act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); // Press escape to clear pending state act(() => { - result.current.handleInput({ name: 'escape' }); + result.current.handleInput(createKey({ name: 'escape' })); }); // Now 'w' should just move cursor, not delete act(() => { - result.current.handleInput({ sequence: 'w' }); + result.current.handleInput(createKey({ sequence: 'w' })); }); expect(testBuffer.vimDeleteWordForward).not.toHaveBeenCalled(); @@ -1223,7 +1260,9 @@ describe('useVim hook', () => { mockVimContext.vimMode = 'NORMAL'; const { result } = renderVimHook(); - const handled = result.current.handleInput({ name: 'escape' }); + const handled = result.current.handleInput( + createKey({ name: 'escape' }), + ); expect(handled).toBe(false); }); @@ -1233,12 +1272,12 @@ describe('useVim hook', () => { const { result } = renderVimHook(); act(() => { - result.current.handleInput({ sequence: 'd' }); + result.current.handleInput(createKey({ sequence: 'd' })); }); let handled: boolean | undefined; act(() => { - handled = result.current.handleInput({ name: 'escape' }); + handled = result.current.handleInput(createKey({ name: 'escape' })); }); expect(handled).toBe(true); @@ -1251,7 +1290,9 @@ describe('useVim hook', () => { mockVimContext.vimMode = 'INSERT'; const { result } = renderVimHook(); - const handled = result.current.handleInput({ name: 'r', ctrl: true }); + const handled = result.current.handleInput( + createKey({ name: 'r', ctrl: true }), + ); expect(handled).toBe(false); }); @@ -1261,7 +1302,7 @@ describe('useVim hook', () => { const emptyBuffer = createMockBuffer(''); const { result } = renderVimHook(emptyBuffer); - const handled = result.current.handleInput({ sequence: '!' }); + const handled = result.current.handleInput(createKey({ sequence: '!' })); expect(handled).toBe(false); }); @@ -1270,7 +1311,7 @@ describe('useVim hook', () => { mockVimContext.vimMode = 'INSERT'; const nonEmptyBuffer = createMockBuffer('not empty'); const { result } = renderVimHook(nonEmptyBuffer); - const key = { sequence: '!', name: '!' }; + const key = createKey({ sequence: '!', name: '!' }); act(() => { result.current.handleInput(key); @@ -1287,7 +1328,7 @@ describe('useVim hook', () => { describe('Reducer-based integration tests', () => { describe('de (delete word end)', () => { it('should delete from cursor to end of current word', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 1, // cursor on 'e' in "hello" @@ -1296,7 +1337,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_end', @@ -1310,7 +1351,7 @@ describe('useVim hook', () => { }); it('should delete multiple word ends with count', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test more'], cursorRow: 0, cursorCol: 1, // cursor on 'e' in "hello" @@ -1319,7 +1360,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_end', @@ -1335,7 +1376,7 @@ describe('useVim hook', () => { describe('db (delete word backward)', () => { it('should delete from cursor to start of previous word', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 11, // cursor on 't' in "test" @@ -1344,7 +1385,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_backward', @@ -1358,7 +1399,7 @@ describe('useVim hook', () => { }); it('should delete multiple words backward with count', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test more'], cursorRow: 0, cursorCol: 17, // cursor on 'm' in "more" @@ -1367,7 +1408,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_word_backward', @@ -1383,7 +1424,7 @@ describe('useVim hook', () => { describe('cw (change word forward)', () => { it('should delete from cursor to start of next word', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 0, // cursor on 'h' in "hello" @@ -1392,7 +1433,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_word_forward', @@ -1406,7 +1447,7 @@ describe('useVim hook', () => { }); it('should change multiple words with count', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test more'], cursorRow: 0, cursorCol: 0, @@ -1415,7 +1456,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_word_forward', @@ -1431,7 +1472,7 @@ describe('useVim hook', () => { describe('ce (change word end)', () => { it('should change from cursor to end of current word', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 1, // cursor on 'e' in "hello" @@ -1440,7 +1481,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_word_end', @@ -1454,7 +1495,7 @@ describe('useVim hook', () => { }); it('should change multiple word ends with count', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 1, // cursor on 'e' in "hello" @@ -1463,7 +1504,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_word_end', @@ -1479,7 +1520,7 @@ describe('useVim hook', () => { describe('cb (change word backward)', () => { it('should change from cursor to start of previous word', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 11, // cursor on 't' in "test" @@ -1488,7 +1529,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_word_backward', @@ -1504,7 +1545,7 @@ describe('useVim hook', () => { describe('cc (change line)', () => { it('should clear the line and place cursor at the start', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: [' hello world'], cursorRow: 0, cursorCol: 5, // cursor on 'o' @@ -1513,7 +1554,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_line', @@ -1528,7 +1569,7 @@ describe('useVim hook', () => { describe('dd (delete line)', () => { it('should delete the current line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['line1', 'line2', 'line3'], cursorRow: 1, cursorCol: 2, @@ -1537,7 +1578,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_line', @@ -1550,7 +1591,7 @@ describe('useVim hook', () => { }); it('should delete multiple lines with count', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['line1', 'line2', 'line3', 'line4'], cursorRow: 1, cursorCol: 2, @@ -1559,7 +1600,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_line', @@ -1573,7 +1614,7 @@ describe('useVim hook', () => { }); it('should handle deleting last line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['only line'], cursorRow: 0, cursorCol: 3, @@ -1582,7 +1623,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_line', @@ -1598,7 +1639,7 @@ describe('useVim hook', () => { describe('D (delete to end of line)', () => { it('should delete from cursor to end of line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 6, // cursor on 'w' in "world" @@ -1607,7 +1648,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_to_end_of_line', @@ -1620,7 +1661,7 @@ describe('useVim hook', () => { }); it('should handle D at end of line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world'], cursorRow: 0, cursorCol: 11, // cursor at end @@ -1629,7 +1670,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_delete_to_end_of_line', @@ -1644,7 +1685,7 @@ describe('useVim hook', () => { describe('C (change to end of line)', () => { it('should change from cursor to end of line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world test'], cursorRow: 0, cursorCol: 6, // cursor on 'w' in "world" @@ -1653,7 +1694,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_to_end_of_line', @@ -1666,7 +1707,7 @@ describe('useVim hook', () => { }); it('should handle C at beginning of line', () => { - const initialState = { + const initialState = createMockTextBufferState({ lines: ['hello world'], cursorRow: 0, cursorCol: 0, @@ -1675,7 +1716,7 @@ describe('useVim hook', () => { redoStack: [], clipboard: null, selectionAnchor: null, - }; + }); const result = textBufferReducer(initialState, { type: 'vim_change_to_end_of_line', diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 0ea51a3277..2224f2f52c 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -20,16 +20,6 @@ "src/utils/cleanup.test.ts", "src/utils/handleAutoUpdate.test.ts", "src/utils/startupWarnings.test.ts", - "src/ui/hooks/slashCommandProcessor.test.ts", - "src/ui/hooks/useAtCompletion.test.ts", - "src/ui/hooks/useConsoleMessages.test.ts", - "src/ui/hooks/useCommandCompletion.test.ts", - "src/ui/hooks/useFocus.test.ts", - "src/ui/hooks/useFolderTrust.test.ts", - "src/ui/hooks/useGeminiStream.test.tsx", - "src/ui/hooks/useKeypress.test.ts", - "src/ui/hooks/usePhraseCycler.test.ts", - "src/ui/hooks/vim.test.ts", "src/ui/utils/computeStats.test.ts" ], "references": [{ "path": "../core" }]