refactor(cli): integrate real config loading into async test utils (#23040)

This commit is contained in:
Tommaso Sciortino
2026-03-19 17:05:33 +00:00
committed by GitHub
parent 7de0616229
commit 23264ced9a
103 changed files with 1806 additions and 1541 deletions
@@ -505,9 +505,7 @@ export const useSlashCommandProcessor = (
const props = result.props as Record<string, unknown>;
if (
!props ||
// eslint-disable-next-line no-restricted-syntax
typeof props['name'] !== 'string' ||
// eslint-disable-next-line no-restricted-syntax
typeof props['displayName'] !== 'string' ||
!props['definition']
) {
@@ -38,6 +38,17 @@ vi.mock('./useAtCompletion', () => ({
useAtCompletion: vi.fn(),
}));
vi.mock('./usePromptCompletion', () => ({
usePromptCompletion: vi.fn(() => ({
text: '',
isLoading: false,
isActive: false,
accept: vi.fn(),
clear: vi.fn(),
markSelected: vi.fn(),
})),
}));
vi.mock('./useSlashCompletion', () => ({
useSlashCompletion: vi.fn(() => ({
completionStart: 0,
@@ -183,13 +194,13 @@ describe('useCommandCompletion', () => {
return null;
}
const renderCommandCompletionHook = (
const renderCommandCompletionHook = async (
initialText: string,
cursorOffset?: number,
shellModeActive = false,
active = true,
) => {
const renderResult = renderWithProviders(
const renderResult = await renderWithProviders(
<TestComponent
initialText={initialText}
cursorOffset={cursorOffset}
@@ -219,8 +230,8 @@ describe('useCommandCompletion', () => {
describe('Core Hook Behavior', () => {
describe('State Management', () => {
it('should initialize with default state', () => {
const { result } = renderCommandCompletionHook('');
it('should initialize with default state', async () => {
const { result } = await renderCommandCompletionHook('');
expect(result.current.suggestions).toEqual([]);
expect(result.current.activeSuggestionIndex).toBe(-1);
@@ -235,7 +246,7 @@ describe('useCommandCompletion', () => {
atSuggestions: [{ label: 'src/file.txt', value: 'src/file.txt' }],
});
const { result } = renderCommandCompletionHook('@file');
const { result } = await renderCommandCompletionHook('@file');
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(1);
@@ -256,8 +267,8 @@ describe('useCommandCompletion', () => {
});
});
it('should reset all state to default values', () => {
const { result } = renderCommandCompletionHook('@files');
it('should reset all state to default values', async () => {
const { result } = await renderCommandCompletionHook('@files');
act(() => {
result.current.setActiveSuggestionIndex(5);
@@ -274,7 +285,7 @@ describe('useCommandCompletion', () => {
it('should call useAtCompletion with the correct query for an escaped space', async () => {
const text = '@src/a\\ file.txt';
const { result } = renderCommandCompletionHook(text);
const { result } = await renderCommandCompletionHook(text);
await waitFor(() => {
expect(useAtCompletion).toHaveBeenLastCalledWith(
@@ -291,7 +302,7 @@ describe('useCommandCompletion', () => {
const text = '@file1 @file2';
const cursorOffset = 3; // @fi|le1 @file2
renderCommandCompletionHook(text, cursorOffset);
await renderCommandCompletionHook(text, cursorOffset);
await waitFor(() => {
expect(useAtCompletion).toHaveBeenLastCalledWith(
@@ -329,7 +340,7 @@ describe('useCommandCompletion', () => {
slashSuggestions: [{ label: 'clear', value: 'clear' }],
});
const { result } = renderCommandCompletionHook(
const { result } = await renderCommandCompletionHook(
'/',
undefined,
shellModeActive,
@@ -361,10 +372,10 @@ describe('useCommandCompletion', () => {
setupMocks({ slashSuggestions: mockSuggestions });
});
it('should handle navigateUp with no suggestions', () => {
it('should handle navigateUp with no suggestions', async () => {
setupMocks({ slashSuggestions: [] });
const { result } = renderCommandCompletionHook('/');
const { result } = await renderCommandCompletionHook('/');
act(() => {
result.current.navigateUp();
@@ -373,9 +384,9 @@ describe('useCommandCompletion', () => {
expect(result.current.activeSuggestionIndex).toBe(-1);
});
it('should handle navigateDown with no suggestions', () => {
it('should handle navigateDown with no suggestions', async () => {
setupMocks({ slashSuggestions: [] });
const { result } = renderCommandCompletionHook('/');
const { result } = await renderCommandCompletionHook('/');
act(() => {
result.current.navigateDown();
@@ -385,7 +396,7 @@ describe('useCommandCompletion', () => {
});
it('should navigate up through suggestions with wrap-around', async () => {
const { result } = renderCommandCompletionHook('/');
const { result } = await renderCommandCompletionHook('/');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(5);
@@ -401,7 +412,7 @@ describe('useCommandCompletion', () => {
});
it('should navigate down through suggestions with wrap-around', async () => {
const { result } = renderCommandCompletionHook('/');
const { result } = await renderCommandCompletionHook('/');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(5);
@@ -420,7 +431,7 @@ describe('useCommandCompletion', () => {
});
it('should handle navigation with multiple suggestions', async () => {
const { result } = renderCommandCompletionHook('/');
const { result } = await renderCommandCompletionHook('/');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(5);
@@ -447,7 +458,7 @@ describe('useCommandCompletion', () => {
it('should automatically select the first item when suggestions are available', async () => {
setupMocks({ slashSuggestions: mockSuggestions });
const { result } = renderCommandCompletionHook('/');
const { result } = await renderCommandCompletionHook('/');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(
@@ -466,7 +477,7 @@ describe('useCommandCompletion', () => {
slashCompletionRange: { completionStart: 1, completionEnd: 4 },
});
const { result } = renderCommandCompletionHook('/mem');
const { result } = await renderCommandCompletionHook('/mem');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -484,7 +495,7 @@ describe('useCommandCompletion', () => {
atSuggestions: [{ label: 'src/file1.txt', value: 'src/file1.txt' }],
});
const { result } = renderCommandCompletionHook('@src/fi');
const { result } = await renderCommandCompletionHook('@src/fi');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -509,7 +520,7 @@ describe('useCommandCompletion', () => {
slashCompletionRange: { completionStart: 1, completionEnd: 5 },
});
const { result } = renderCommandCompletionHook('/resu');
const { result } = await renderCommandCompletionHook('/resu');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -530,7 +541,7 @@ describe('useCommandCompletion', () => {
atSuggestions: [{ label: 'src/file1.txt', value: 'src/file1.txt' }],
});
const { result } = renderCommandCompletionHook(text, cursorOffset);
const { result } = await renderCommandCompletionHook(text, cursorOffset);
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -550,7 +561,7 @@ describe('useCommandCompletion', () => {
atSuggestions: [{ label: 'src/components/', value: 'src/components/' }],
});
const { result } = renderCommandCompletionHook('@src/comp');
const { result } = await renderCommandCompletionHook('@src/comp');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -570,7 +581,7 @@ describe('useCommandCompletion', () => {
],
});
const { result } = renderCommandCompletionHook('@src\\comp');
const { result } = await renderCommandCompletionHook('@src\\comp');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -595,7 +606,7 @@ describe('useCommandCompletion', () => {
},
});
const { result } = renderCommandCompletionHook(
const { result } = await renderCommandCompletionHook(
text,
text.length,
true, // shellModeActive
@@ -624,7 +635,7 @@ describe('useCommandCompletion', () => {
},
});
const { result } = renderCommandCompletionHook(
const { result } = await renderCommandCompletionHook(
text,
text.length,
true, // shellModeActive
@@ -642,7 +653,7 @@ describe('useCommandCompletion', () => {
const text = 'ls ';
const cursorOffset = text.length;
const { result } = renderCommandCompletionHook(
const { result } = await renderCommandCompletionHook(
text,
cursorOffset,
true, // shellModeActive
@@ -668,7 +679,7 @@ describe('useCommandCompletion', () => {
},
});
const { result } = renderCommandCompletionHook(
const { result } = await renderCommandCompletionHook(
textWithoutSpace,
textWithoutSpace.length,
true, // shellModeActive
@@ -733,7 +744,7 @@ describe('useCommandCompletion', () => {
hookResult = { ...completion, textBuffer };
return null;
}
renderWithProviders(<TestComponent />);
await renderWithProviders(<TestComponent />);
// Should not trigger prompt completion for comments
await waitFor(() => {
@@ -768,7 +779,7 @@ describe('useCommandCompletion', () => {
hookResult = { ...completion, textBuffer };
return null;
}
renderWithProviders(<TestComponent />);
await renderWithProviders(<TestComponent />);
// Should not trigger prompt completion for comments
await waitFor(() => {
@@ -803,7 +814,7 @@ describe('useCommandCompletion', () => {
hookResult = { ...completion, textBuffer };
return null;
}
renderWithProviders(<TestComponent />);
await renderWithProviders(<TestComponent />);
// This test verifies that comments are filtered out while regular text is not
await waitFor(() => {
@@ -823,7 +834,7 @@ describe('useCommandCompletion', () => {
const text = '/mycommand @src/fi';
const cursorOffset = text.length;
renderCommandCompletionHook(text, cursorOffset);
await renderCommandCompletionHook(text, cursorOffset);
await waitFor(() => {
expect(useAtCompletion).toHaveBeenLastCalledWith(
@@ -843,7 +854,7 @@ describe('useCommandCompletion', () => {
const text = '/mycom';
const cursorOffset = text.length;
const { result } = renderCommandCompletionHook(text, cursorOffset);
const { result } = await renderCommandCompletionHook(text, cursorOffset);
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(1);
@@ -859,7 +870,7 @@ describe('useCommandCompletion', () => {
const text = '/command @';
const cursorOffset = text.length;
renderCommandCompletionHook(text, cursorOffset);
await renderCommandCompletionHook(text, cursorOffset);
await waitFor(() => {
expect(useAtCompletion).toHaveBeenLastCalledWith(
@@ -879,7 +890,7 @@ describe('useCommandCompletion', () => {
const text = '/diff @src/foo.ts @src/ba';
const cursorOffset = text.length;
renderCommandCompletionHook(text, cursorOffset);
await renderCommandCompletionHook(text, cursorOffset);
await waitFor(() => {
expect(useAtCompletion).toHaveBeenLastCalledWith(
@@ -896,7 +907,7 @@ describe('useCommandCompletion', () => {
atSuggestions: [{ label: 'src/file.txt', value: 'src/file.txt' }],
});
const { result } = renderCommandCompletionHook('/cmd @src/fi');
const { result } = await renderCommandCompletionHook('/cmd @src/fi');
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
@@ -915,7 +926,7 @@ describe('useCommandCompletion', () => {
});
const text = '/help ';
renderCommandCompletionHook(text);
await renderCommandCompletionHook(text);
await waitFor(() => {
expect(useSlashCompletion).toHaveBeenLastCalledWith(
+16 -16
View File
@@ -47,13 +47,13 @@ describe('useFocus', () => {
stdin.removeAllListeners();
});
const renderFocusHook = () => {
const renderFocusHook = async () => {
let hookResult: ReturnType<typeof useFocus>;
function TestComponent() {
hookResult = useFocus();
return null;
}
const { unmount } = renderWithProviders(<TestComponent />);
const { unmount } = await renderWithProviders(<TestComponent />);
return {
result: {
get current() {
@@ -64,15 +64,15 @@ describe('useFocus', () => {
};
};
it('should initialize with focus and enable focus reporting', () => {
const { result } = renderFocusHook();
it('should initialize with focus and enable focus reporting', async () => {
const { result } = await renderFocusHook();
expect(result.current.isFocused).toBe(true);
expect(stdout.write).toHaveBeenCalledWith('\x1b[?1004h');
});
it('should set isFocused to false when a focus-out event is received', () => {
const { result } = renderFocusHook();
it('should set isFocused to false when a focus-out event is received', async () => {
const { result } = await renderFocusHook();
// Initial state is focused
expect(result.current.isFocused).toBe(true);
@@ -86,8 +86,8 @@ describe('useFocus', () => {
expect(result.current.isFocused).toBe(false);
});
it('should set isFocused to true when a focus-in event is received', () => {
const { result } = renderFocusHook();
it('should set isFocused to true when a focus-in event is received', async () => {
const { result } = await renderFocusHook();
// Simulate focus-out to set initial state to false
act(() => {
@@ -104,8 +104,8 @@ describe('useFocus', () => {
expect(result.current.isFocused).toBe(true);
});
it('should clean up and disable focus reporting on unmount', () => {
const { unmount } = renderFocusHook();
it('should clean up and disable focus reporting on unmount', async () => {
const { unmount } = await renderFocusHook();
// At this point we should have listeners from both KeypressProvider and useFocus
const listenerCountAfterMount = stdin.listenerCount('data');
@@ -119,8 +119,8 @@ describe('useFocus', () => {
expect(stdin.listenerCount('data')).toBeLessThan(listenerCountAfterMount);
});
it('should handle multiple focus events correctly', () => {
const { result } = renderFocusHook();
it('should handle multiple focus events correctly', async () => {
const { result } = await renderFocusHook();
act(() => {
stdin.emit('data', '\x1b[O');
@@ -143,8 +143,8 @@ describe('useFocus', () => {
expect(result.current.isFocused).toBe(true);
});
it('restores focus on keypress after focus is lost', () => {
const { result } = renderFocusHook();
it('restores focus on keypress after focus is lost', async () => {
const { result } = await renderFocusHook();
// Simulate focus-out event
act(() => {
@@ -159,8 +159,8 @@ describe('useFocus', () => {
expect(result.current.isFocused).toBe(true);
});
it('tracks whether any focus event has been received', () => {
const { result } = renderFocusHook();
it('tracks whether any focus event has been received', async () => {
const { result } = await renderFocusHook();
expect(result.current.hasReceivedFocusEvent).toBe(false);
@@ -375,7 +375,7 @@ describe('useGeminiStream', () => {
setValue: vi.fn(),
} as unknown as LoadedSettings;
const renderTestHook = (
const renderTestHook = async (
initialToolCalls: TrackedToolCall[] = [],
geminiClient?: any,
loadedSettings: LoadedSettings = mockLoadedSettings,
@@ -436,7 +436,7 @@ describe('useGeminiStream', () => {
];
});
const { result, rerender } = renderHookWithProviders(
const { result, rerender } = await renderHookWithProviders(
(props: typeof initialProps) =>
useGeminiStream(
props.client,
@@ -518,7 +518,7 @@ describe('useGeminiStream', () => {
});
// Helper to render hook with default parameters - reduces boilerplate
const renderHookWithDefaults = (
const renderHookWithDefaults = async (
options: {
shellModeActive?: boolean;
onCancelSubmit?: () => void;
@@ -562,7 +562,7 @@ describe('useGeminiStream', () => {
);
};
it('should not submit tool responses if not all tool calls are completed', () => {
it('should not submit tool responses if not all tool calls are completed', async () => {
const toolCalls: TrackedToolCall[] = [
{
request: {
@@ -617,7 +617,7 @@ describe('useGeminiStream', () => {
];
const { mockMarkToolsAsSubmitted, mockSendMessageStream } =
renderTestHook(toolCalls);
await renderTestHook(toolCalls);
// Effect for submitting tool responses depends on toolCalls and isResponding
// isResponding is initially false, so the effect should run.
@@ -626,7 +626,7 @@ describe('useGeminiStream', () => {
expect(mockSendMessageStream).not.toHaveBeenCalled(); // submitQuery uses this
});
it('should expose activePtyId for non-shell executing tools that report an execution ID', () => {
it('should expose activePtyId for non-shell executing tools that report an execution ID', async () => {
const remoteExecutingTool: TrackedExecutingToolCall = {
request: {
callId: 'remote-call-1',
@@ -651,7 +651,7 @@ describe('useGeminiStream', () => {
pid: 4242,
};
const { result } = renderTestHook([remoteExecutingTool]);
const { result } = await renderTestHook([remoteExecutingTool]);
expect(result.current.activePtyId).toBe(4242);
});
@@ -716,7 +716,7 @@ describe('useGeminiStream', () => {
];
});
renderHookWithProviders(() =>
await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -817,7 +817,7 @@ describe('useGeminiStream', () => {
];
});
renderHookWithProviders(() =>
await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -927,7 +927,7 @@ describe('useGeminiStream', () => {
];
});
renderHookWithProviders(() =>
await renderHookWithProviders(() =>
useGeminiStream(
client,
[],
@@ -998,7 +998,7 @@ describe('useGeminiStream', () => {
];
const client = new MockedGeminiClientClass(mockConfig);
const { result } = renderTestHook([], client);
const { result } = await renderTestHook([], client);
// Trigger the onComplete callback with STOP_EXECUTION tool
await act(async () => {
@@ -1077,7 +1077,7 @@ describe('useGeminiStream', () => {
} as LoadedSettings;
const client = new MockedGeminiClientClass(mockConfig);
const { result } = renderTestHook([], client, lowVerbositySettings);
const { result } = await renderTestHook([], client, lowVerbositySettings);
await act(async () => {
if (capturedOnComplete) {
@@ -1190,7 +1190,7 @@ describe('useGeminiStream', () => {
];
});
renderHookWithProviders(() =>
await renderHookWithProviders(() =>
useGeminiStream(
client,
[],
@@ -1307,7 +1307,7 @@ describe('useGeminiStream', () => {
];
});
const { result, rerender } = renderHookWithProviders(() =>
const { result, rerender } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -1408,7 +1408,7 @@ describe('useGeminiStream', () => {
})();
mockSendMessageStream.mockReturnValue(mockStream);
const { result } = renderTestHook();
const { result } = await renderTestHook();
// Start a query
await act(async () => {
@@ -1445,7 +1445,7 @@ describe('useGeminiStream', () => {
})();
mockSendMessageStream.mockReturnValue(mockStream);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
mockConfig.getGeminiClient(),
[],
@@ -1486,7 +1486,7 @@ describe('useGeminiStream', () => {
})();
mockSendMessageStream.mockReturnValue(mockStream);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
mockConfig.getGeminiClient(),
[],
@@ -1519,8 +1519,8 @@ describe('useGeminiStream', () => {
expect(setShellInputFocusedSpy).toHaveBeenCalledWith(false);
});
it('should not do anything if escape is pressed when not responding', () => {
const { result } = renderTestHook();
it('should not do anything if escape is pressed when not responding', async () => {
const { result } = await renderTestHook();
expect(result.current.streamingState).toBe(StreamingState.Idle);
@@ -1548,7 +1548,7 @@ describe('useGeminiStream', () => {
})();
mockSendMessageStream.mockReturnValue(mockStream);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -1600,7 +1600,7 @@ describe('useGeminiStream', () => {
} as TrackedExecutingToolCall,
];
const { result } = renderTestHook(toolCalls);
const { result } = await renderTestHook(toolCalls);
// State is `Responding` because a tool is running
expect(result.current.streamingState).toBe(StreamingState.Responding);
@@ -1648,7 +1648,7 @@ describe('useGeminiStream', () => {
} as TrackedWaitingToolCall,
];
const { result } = renderTestHook(toolCalls);
const { result } = await renderTestHook(toolCalls);
// State is `WaitingForConfirmation` because a tool is awaiting approval
expect(result.current.streamingState).toBe(
@@ -1677,7 +1677,7 @@ describe('useGeminiStream', () => {
describe('Retry Handling', () => {
it('should update retryStatus when CoreEvent.RetryAttempt is emitted', async () => {
const { result } = renderHookWithDefaults();
const { result } = await renderHookWithDefaults();
const retryPayload = {
model: 'gemini-2.5-pro',
@@ -1694,7 +1694,7 @@ describe('useGeminiStream', () => {
});
it('should reset retryStatus when isResponding becomes false', async () => {
const { result } = renderTestHook();
const { result } = await renderTestHook();
const retryPayload = {
model: 'gemini-2.5-pro',
@@ -1744,7 +1744,7 @@ describe('useGeminiStream', () => {
};
mockHandleSlashCommand.mockResolvedValue(clientToolRequest);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('/memory add "test fact"');
@@ -1771,7 +1771,7 @@ describe('useGeminiStream', () => {
};
mockHandleSlashCommand.mockResolvedValue(uiOnlyCommandResult);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('/help');
@@ -1792,7 +1792,7 @@ describe('useGeminiStream', () => {
mockHandleSlashCommand.mockResolvedValue(customCommandResult);
const { result, mockSendMessageStream: localMockSendMessageStream } =
renderTestHook();
await renderTestHook();
await act(async () => {
await result.current.submitQuery('/my-custom-command');
@@ -1830,7 +1830,7 @@ describe('useGeminiStream', () => {
mockHandleSlashCommand.mockResolvedValue(emptyPromptResult);
const { result, mockSendMessageStream: localMockSendMessageStream } =
renderTestHook();
await renderTestHook();
await act(async () => {
await result.current.submitQuery('/emptycmd');
@@ -1851,7 +1851,7 @@ describe('useGeminiStream', () => {
it('should not call handleSlashCommand for line comments', async () => {
const { result, mockSendMessageStream: localMockSendMessageStream } =
renderTestHook();
await renderTestHook();
await act(async () => {
await result.current.submitQuery('// This is a line comment');
@@ -1872,7 +1872,7 @@ describe('useGeminiStream', () => {
it('should not call handleSlashCommand for block comments', async () => {
const { result, mockSendMessageStream: localMockSendMessageStream } =
renderTestHook();
await renderTestHook();
await act(async () => {
await result.current.submitQuery('/* This is a block comment */');
@@ -1892,7 +1892,7 @@ describe('useGeminiStream', () => {
});
it('should not call handleSlashCommand is shell mode is active', async () => {
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -1972,7 +1972,7 @@ describe('useGeminiStream', () => {
];
});
renderHookWithProviders(() =>
await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -2031,7 +2031,7 @@ describe('useGeminiStream', () => {
getModel: vi.fn(() => 'gemini-2.5-pro'),
} as unknown as Config;
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(testConfig),
[],
@@ -2078,7 +2078,7 @@ describe('useGeminiStream', () => {
createMockToolCall('read_file', 'call2', 'info'),
];
const { result } = renderTestHook(awaitingApprovalToolCalls);
const { result } = await renderTestHook(awaitingApprovalToolCalls);
await act(async () => {
await result.current.handleApprovalModeChange(ApprovalMode.YOLO);
@@ -2108,7 +2108,7 @@ describe('useGeminiStream', () => {
createMockToolCall('read_file', 'call3', 'info'),
];
const { result } = renderTestHook(awaitingApprovalToolCalls);
const { result } = await renderTestHook(awaitingApprovalToolCalls);
await act(async () => {
await result.current.handleApprovalModeChange(ApprovalMode.AUTO_EDIT);
@@ -2132,7 +2132,7 @@ describe('useGeminiStream', () => {
createMockToolCall('replace', 'call1', 'edit'),
];
const { result } = renderTestHook(awaitingApprovalToolCalls);
const { result } = await renderTestHook(awaitingApprovalToolCalls);
await act(async () => {
await result.current.handleApprovalModeChange(ApprovalMode.DEFAULT);
@@ -2154,7 +2154,7 @@ describe('useGeminiStream', () => {
createMockToolCall('write_file', 'call2', 'edit'),
];
const { result } = renderTestHook(awaitingApprovalToolCalls);
const { result } = await renderTestHook(awaitingApprovalToolCalls);
await act(async () => {
await result.current.handleApprovalModeChange(ApprovalMode.YOLO);
@@ -2196,7 +2196,7 @@ describe('useGeminiStream', () => {
} as unknown as TrackedWaitingToolCall,
];
const { result } = renderTestHook(awaitingApprovalToolCalls);
const { result } = await renderTestHook(awaitingApprovalToolCalls);
// Should not throw an error
await act(async () => {
@@ -2239,7 +2239,7 @@ describe('useGeminiStream', () => {
} as TrackedExecutingToolCall,
];
const { result } = renderTestHook(mixedStatusToolCalls);
const { result } = await renderTestHook(mixedStatusToolCalls);
await act(async () => {
await result.current.handleApprovalModeChange(ApprovalMode.YOLO);
@@ -2260,7 +2260,7 @@ describe('useGeminiStream', () => {
(mockConfig.getApprovalMode as Mock).mockReturnValue(ApprovalMode.PLAN);
// Render the hook, which will initialize the previousApprovalModeRef with PLAN
const { result, client } = renderTestHook([]);
const { result, client } = await renderTestHook([]);
// Update mockConfig to return DEFAULT mode (new mode)
(mockConfig.getApprovalMode as Mock).mockReturnValue(
@@ -2300,7 +2300,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -2374,7 +2374,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithDefaults();
const { result } = await renderHookWithDefaults();
await act(async () => {
await result.current.submitQuery('Test overflow');
@@ -2405,7 +2405,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -2454,7 +2454,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithDefaults();
const { result } = await renderHookWithDefaults();
// Submit a query
await act(async () => {
@@ -2541,7 +2541,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithDefaults();
const { result } = await renderHookWithDefaults();
await act(async () => {
await result.current.submitQuery(`Test ${reason}`);
@@ -2613,7 +2613,7 @@ describe('useGeminiStream', () => {
];
});
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -2684,7 +2684,7 @@ describe('useGeminiStream', () => {
shouldProceed: true,
});
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
mockConfig.getGeminiClient(),
[],
@@ -2777,7 +2777,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery(userQuery);
@@ -2849,7 +2849,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -2904,7 +2904,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('Test query');
@@ -2949,7 +2949,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -3021,7 +3021,7 @@ describe('useGeminiStream', () => {
});
});
it('should memoize pendingHistoryItems', () => {
it('should memoize pendingHistoryItems', async () => {
mockUseToolScheduler.mockReturnValue([
[],
mockScheduleToolCalls,
@@ -3031,7 +3031,7 @@ describe('useGeminiStream', () => {
0,
]);
const { result, rerender } = renderHookWithProviders(() =>
const { result, rerender } = await renderHookWithProviders(() =>
useGeminiStream(
mockConfig.getGeminiClient(),
[],
@@ -3102,7 +3102,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -3159,7 +3159,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -3227,7 +3227,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useGeminiStream(
new MockedGeminiClientClass(mockConfig),
[],
@@ -3287,7 +3287,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
@@ -3334,7 +3334,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
@@ -3399,7 +3399,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
@@ -3434,7 +3434,7 @@ describe('useGeminiStream', () => {
});
it('should handle multiple loop detection events properly', async () => {
const { result } = renderTestHook();
const { result } = await renderTestHook();
// First loop detection - set up fresh mock for first call
mockSendMessageStream.mockReturnValueOnce(
@@ -3544,7 +3544,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
@@ -3581,7 +3581,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
// Start first query without awaiting (fire-and-forget, like existing tests)
await act(async () => {
@@ -3637,7 +3637,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test query');
@@ -3692,7 +3692,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test stop');
@@ -3720,7 +3720,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test stop');
@@ -3751,7 +3751,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test block');
@@ -3778,7 +3778,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('test block');
@@ -3807,7 +3807,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('user query');
@@ -3855,7 +3855,7 @@ describe('useGeminiStream', () => {
})(),
);
const { result } = renderTestHook();
const { result } = await renderTestHook();
await act(async () => {
await result.current.submitQuery('user query');
@@ -3881,7 +3881,7 @@ describe('useGeminiStream', () => {
});
it('should trace UserPrompt telemetry on submitQuery', async () => {
const { result } = renderTestHook();
const { result } = await renderTestHook();
mockSendMessageStream.mockReturnValue(
(async function* () {
+27 -24
View File
@@ -43,7 +43,7 @@ describe(`useKeypress`, () => {
const onKeypress = vi.fn();
let originalNodeVersion: string;
const renderKeypressHook = (isActive = true) =>
const renderKeypressHook = async (isActive = true) =>
renderHookWithProviders(() => useKeypress(onKeypress, { isActive }));
beforeEach(() => {
@@ -66,8 +66,8 @@ describe(`useKeypress`, () => {
});
});
it('should not listen if isActive is false', () => {
renderKeypressHook(false);
it('should not listen if isActive is false', async () => {
await renderKeypressHook(false);
act(() => stdin.write('a'));
expect(onKeypress).not.toHaveBeenCalled();
});
@@ -79,28 +79,31 @@ describe(`useKeypress`, () => {
{ key: { name: 'up', sequence: '\x1b[A' } },
{ key: { name: 'down', sequence: '\x1b[B' } },
{ key: { name: 'tab', sequence: '\x1b[Z', shift: true } },
])('should listen for keypress when active for key $key.name', ({ key }) => {
renderKeypressHook(true);
act(() => stdin.write(key.sequence));
expect(onKeypress).toHaveBeenCalledWith(expect.objectContaining(key));
});
])(
'should listen for keypress when active for key $key.name',
async ({ key }) => {
await renderKeypressHook(true);
act(() => stdin.write(key.sequence));
expect(onKeypress).toHaveBeenCalledWith(expect.objectContaining(key));
},
);
it('should set and release raw mode', () => {
const { unmount } = renderKeypressHook(true);
it('should set and release raw mode', async () => {
const { unmount } = await renderKeypressHook(true);
expect(mockSetRawMode).toHaveBeenCalledWith(true);
unmount();
expect(mockSetRawMode).toHaveBeenCalledWith(false);
});
it('should stop listening after being unmounted', () => {
const { unmount } = renderKeypressHook(true);
it('should stop listening after being unmounted', async () => {
const { unmount } = await renderKeypressHook(true);
unmount();
act(() => stdin.write('a'));
expect(onKeypress).not.toHaveBeenCalled();
});
it('should correctly identify alt+enter (meta key)', () => {
renderKeypressHook(true);
it('should correctly identify alt+enter (meta key)', async () => {
await renderKeypressHook(true);
const key = { name: 'enter', sequence: '\x1B\r' };
act(() => stdin.write(key.sequence));
expect(onKeypress).toHaveBeenCalledWith(
@@ -128,8 +131,8 @@ describe(`useKeypress`, () => {
setup();
});
it('should process a paste as a single event', () => {
renderKeypressHook(true);
it('should process a paste as a single event', async () => {
await renderKeypressHook(true);
const pasteText = 'hello world';
act(() => stdin.write(PASTE_START + pasteText + PASTE_END));
@@ -145,8 +148,8 @@ describe(`useKeypress`, () => {
});
});
it('should handle keypress interspersed with pastes', () => {
renderKeypressHook(true);
it('should handle keypress interspersed with pastes', async () => {
await renderKeypressHook(true);
const keyA = { name: 'a', sequence: 'a' };
act(() => stdin.write('a'));
@@ -169,8 +172,8 @@ describe(`useKeypress`, () => {
expect(onKeypress).toHaveBeenCalledTimes(3);
});
it('should handle lone pastes', () => {
renderKeypressHook(true);
it('should handle lone pastes', async () => {
await renderKeypressHook(true);
const pasteText = 'pasted';
act(() => {
@@ -184,7 +187,7 @@ describe(`useKeypress`, () => {
});
it('should handle paste false alarm', async () => {
renderKeypressHook(true);
await renderKeypressHook(true);
act(() => {
stdin.write(PASTE_START.slice(0, 5));
@@ -200,8 +203,8 @@ describe(`useKeypress`, () => {
expect(onKeypress).toHaveBeenCalledTimes(2);
});
it('should handle back to back pastes', () => {
renderKeypressHook(true);
it('should handle back to back pastes', async () => {
await renderKeypressHook(true);
const pasteText1 = 'herp';
const pasteText2 = 'derp';
@@ -226,7 +229,7 @@ describe(`useKeypress`, () => {
});
it('should handle pastes split across writes', async () => {
renderKeypressHook(true);
await renderKeypressHook(true);
const keyA = { name: 'a', sequence: 'a' };
act(() => stdin.write('a'));
@@ -30,10 +30,10 @@ describe('useReverseSearchCompletion', () => {
describe('Core Hook Behavior', () => {
describe('State Management', () => {
it('should initialize with default state', () => {
it('should initialize with default state', async () => {
const mockShellHistory = ['echo hello'];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest(''),
mockShellHistory,
@@ -48,9 +48,9 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.isLoadingSuggestions).toBe(false);
});
it('should reset state when reverseSearchActive becomes false', () => {
it('should reset state when reverseSearchActive becomes false', async () => {
const mockShellHistory = ['echo hello'];
const { result, rerender } = renderHookWithProviders(
const { result, rerender } = await renderHookWithProviders(
({ text, active }) => {
const textBuffer = useTextBufferForTest(text);
return useReverseSearchCompletion(
@@ -72,10 +72,10 @@ describe('useReverseSearchCompletion', () => {
});
describe('Navigation', () => {
it('should handle navigateUp with no suggestions', () => {
it('should handle navigateUp with no suggestions', async () => {
const mockShellHistory = ['echo hello'];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest('grep'),
mockShellHistory,
@@ -90,9 +90,9 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.activeSuggestionIndex).toBe(-1);
});
it('should handle navigateDown with no suggestions', () => {
it('should handle navigateDown with no suggestions', async () => {
const mockShellHistory = ['echo hello'];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest('grep'),
mockShellHistory,
@@ -107,7 +107,7 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.activeSuggestionIndex).toBe(-1);
});
it('should navigate up through suggestions with wrap-around', () => {
it('should navigate up through suggestions with wrap-around', async () => {
const mockShellHistory = [
'ls -l',
'ls -la',
@@ -117,7 +117,7 @@ describe('useReverseSearchCompletion', () => {
'echo Hi',
];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest('echo'),
mockShellHistory,
@@ -135,7 +135,7 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.activeSuggestionIndex).toBe(1);
});
it('should navigate down through suggestions with wrap-around', () => {
it('should navigate down through suggestions with wrap-around', async () => {
const mockShellHistory = [
'ls -l',
'ls -la',
@@ -144,7 +144,7 @@ describe('useReverseSearchCompletion', () => {
'echo "Hello, World!"',
'echo Hi',
];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest('ls'),
mockShellHistory,
@@ -162,7 +162,7 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.activeSuggestionIndex).toBe(1);
});
it('should handle navigation with multiple suggestions', () => {
it('should handle navigation with multiple suggestions', async () => {
const mockShellHistory = [
'ls -l',
'ls -la',
@@ -172,7 +172,7 @@ describe('useReverseSearchCompletion', () => {
'echo "Hi all"',
];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest('l'),
mockShellHistory,
@@ -209,13 +209,13 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.activeSuggestionIndex).toBe(4);
});
it('should handle navigation with large suggestion lists and scrolling', () => {
it('should handle navigation with large suggestion lists and scrolling', async () => {
const largeMockCommands = Array.from(
{ length: 15 },
(_, i) => `echo ${i}`,
);
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(
useTextBufferForTest('echo'),
largeMockCommands,
@@ -239,9 +239,9 @@ describe('useReverseSearchCompletion', () => {
});
describe('Filtering', () => {
it('filters history by buffer.text and sets showSuggestions', () => {
it('filters history by buffer.text and sets showSuggestions', async () => {
const history = ['foo', 'barfoo', 'baz'];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(useTextBufferForTest('foo'), history, true),
);
@@ -253,9 +253,9 @@ describe('useReverseSearchCompletion', () => {
expect(result.current.showSuggestions).toBe(true);
});
it('hides suggestions when there are no matches', () => {
it('hides suggestions when there are no matches', async () => {
const history = ['alpha', 'beta'];
const { result } = renderHookWithProviders(() =>
const { result } = await renderHookWithProviders(() =>
useReverseSearchCompletion(useTextBufferForTest('γ'), history, true),
);
+38 -23
View File
@@ -48,10 +48,13 @@ describe('useSnowfall', () => {
vi.useRealTimers();
});
it('initially enables animation during holiday season with Holiday theme', () => {
const { result } = renderHookWithProviders(() => useSnowfall(mockArt), {
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
});
it('initially enables animation during holiday season with Holiday theme', async () => {
const { result } = await renderHookWithProviders(
() => useSnowfall(mockArt),
{
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
},
);
// Should contain holiday trees
expect(result.current).toContain('|_|');
@@ -59,10 +62,13 @@ describe('useSnowfall', () => {
expect(debugState.debugNumAnimatedComponents).toBeGreaterThan(0);
});
it('stops animation after 15 seconds', () => {
const { result } = renderHookWithProviders(() => useSnowfall(mockArt), {
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
});
it('stops animation after 15 seconds', async () => {
const { result } = await renderHookWithProviders(
() => useSnowfall(mockArt),
{
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
},
);
expect(debugState.debugNumAnimatedComponents).toBeGreaterThan(0);
@@ -76,35 +82,44 @@ describe('useSnowfall', () => {
expect(result.current).toBe(mockArt);
});
it('does not enable animation if not holiday season', () => {
it('does not enable animation if not holiday season', async () => {
vi.setSystemTime(new Date('2025-06-15'));
const { result } = renderHookWithProviders(() => useSnowfall(mockArt), {
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
});
const { result } = await renderHookWithProviders(
() => useSnowfall(mockArt),
{
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
},
);
expect(result.current).toBe(mockArt);
expect(debugState.debugNumAnimatedComponents).toBe(0);
});
it('does not enable animation if theme is not Holiday', () => {
it('does not enable animation if theme is not Holiday', async () => {
vi.mocked(themeManager.getActiveTheme).mockReturnValue({
name: 'Default',
} as Theme);
const { result } = renderHookWithProviders(() => useSnowfall(mockArt), {
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
});
const { result } = await renderHookWithProviders(
() => useSnowfall(mockArt),
{
uiState: { history: [], historyRemountKey: 0 } as Partial<UIState>,
},
);
expect(result.current).toBe(mockArt);
expect(debugState.debugNumAnimatedComponents).toBe(0);
});
it('does not enable animation if chat has started', () => {
const { result } = renderHookWithProviders(() => useSnowfall(mockArt), {
uiState: {
history: [{ type: 'user', text: 'hello' }],
historyRemountKey: 0,
} as Partial<UIState>,
});
it('does not enable animation if chat has started', async () => {
const { result } = await renderHookWithProviders(
() => useSnowfall(mockArt),
{
uiState: {
history: [{ type: 'user', text: 'hello' }],
historyRemountKey: 0,
} as Partial<UIState>,
},
);
expect(result.current).toBe(mockArt);
expect(debugState.debugNumAnimatedComponents).toBe(0);
+6 -6
View File
@@ -16,8 +16,8 @@ describe('useTips()', () => {
vi.clearAllMocks();
});
it('should return false and call set(1) if state is undefined', () => {
const { result } = renderHookWithProviders(() => useTips());
it('should return false and call set(1) if state is undefined', async () => {
const { result } = await renderHookWithProviders(() => useTips());
expect(result.current.showTips).toBe(true);
@@ -25,20 +25,20 @@ describe('useTips()', () => {
expect(persistentStateMock.get('tipsShown')).toBe(1);
});
it('should return false and call set(6) if state is 5', () => {
it('should return false and call set(6) if state is 5', async () => {
persistentStateMock.setData({ tipsShown: 5 });
const { result } = renderHookWithProviders(() => useTips());
const { result } = await renderHookWithProviders(() => useTips());
expect(result.current.showTips).toBe(true);
expect(persistentStateMock.get('tipsShown')).toBe(6);
});
it('should return true if state is 10', () => {
it('should return true if state is 10', async () => {
persistentStateMock.setData({ tipsShown: 10 });
const { result } = renderHookWithProviders(() => useTips());
const { result } = await renderHookWithProviders(() => useTips());
expect(result.current.showTips).toBe(false);
expect(persistentStateMock.set).not.toHaveBeenCalled();