test(cli): resolve act() warnings, fix terminalCapabilityManager mock, and update snapshots

This commit is contained in:
Keith Guerin
2026-03-24 20:50:59 -07:00
parent 73c244861c
commit 5062f459c5
7 changed files with 344 additions and 59 deletions
@@ -4,51 +4,34 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render } from '../../test-utils/render.js';
import { act } from 'react';
import { renderWithProviders } from '../../test-utils/render.js';
import { AuthInProgress } from './AuthInProgress.js';
import { useKeypress, type Key } from '../hooks/useKeypress.js';
import { debugLogger } from '@google/gemini-cli-core';
// Mock dependencies
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...actual,
debugLogger: {
log: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
};
});
import {
describe,
it,
expect,
vi,
beforeEach,
afterEach,
type Mock,
} from 'vitest';
import { act } from 'react';
import { Text } from 'ink';
import { useKeypress } from '../hooks/useKeypress.js';
vi.mock('../hooks/useKeypress.js', () => ({
useKeypress: vi.fn(),
}));
vi.mock('../components/CliSpinner.js', () => ({
CliSpinner: () => '[Spinner]',
const mockedUseKeypress = useKeypress as Mock;
vi.mock('../components/BrailleAnimation.js', () => ({
BrailleAnimation: () => <Text>[Spinner]</Text>,
}));
describe('AuthInProgress', () => {
const onTimeout = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
vi.mocked(debugLogger.error).mockImplementation((...args) => {
if (
// eslint-disable-next-line no-restricted-syntax
typeof args[0] === 'string' &&
args[0].includes('was not wrapped in act')
) {
return;
}
});
});
afterEach(() => {
@@ -56,26 +39,25 @@ describe('AuthInProgress', () => {
});
it('renders initial state with spinner', async () => {
const { lastFrame, unmount } = await render(
const onTimeout = vi.fn();
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
<AuthInProgress onTimeout={onTimeout} />,
);
await waitUntilReady();
expect(lastFrame()).toContain('[Spinner] Waiting for authentication...');
expect(lastFrame()).toContain('Press Esc or Ctrl+C to cancel');
unmount();
});
it('calls onTimeout when ESC is pressed', async () => {
const { waitUntilReady, unmount } = await render(
const onTimeout = vi.fn();
const { unmount } = await renderWithProviders(
<AuthInProgress onTimeout={onTimeout} />,
);
const keypressHandler = vi.mocked(useKeypress).mock.calls[0][0];
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
keypressHandler({ name: 'escape' } as unknown as Key);
});
// Escape key has a 50ms timeout in KeypressContext, so we need to wrap waitUntilReady in act
await act(async () => {
await waitUntilReady();
keypressHandler({ name: 'escape' });
});
expect(onTimeout).toHaveBeenCalled();
@@ -83,28 +65,32 @@ describe('AuthInProgress', () => {
});
it('calls onTimeout when Ctrl+C is pressed', async () => {
const { waitUntilReady, unmount } = await render(
const onTimeout = vi.fn();
const { unmount } = await renderWithProviders(
<AuthInProgress onTimeout={onTimeout} />,
);
const keypressHandler = vi.mocked(useKeypress).mock.calls[0][0];
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
keypressHandler({ name: 'c', ctrl: true } as unknown as Key);
keypressHandler({ ctrl: true, name: 'c' });
});
await waitUntilReady();
expect(onTimeout).toHaveBeenCalled();
unmount();
});
it('calls onTimeout and shows timeout message after 3 minutes', async () => {
const { lastFrame, waitUntilReady, unmount } = await render(
const onTimeout = vi.fn();
const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
<AuthInProgress onTimeout={onTimeout} />,
);
await waitUntilReady();
await act(async () => {
vi.advanceTimersByTime(180000);
});
// Wait for state updates to propagate
await waitUntilReady();
expect(onTimeout).toHaveBeenCalled();
@@ -113,15 +99,18 @@ describe('AuthInProgress', () => {
});
it('clears timer on unmount', async () => {
const { unmount } = await render(<AuthInProgress onTimeout={onTimeout} />);
const onTimeout = vi.fn();
const { unmount, waitUntilReady } = await renderWithProviders(
<AuthInProgress onTimeout={onTimeout} />,
);
await waitUntilReady();
await act(async () => {
unmount();
});
unmount();
await act(async () => {
vi.advanceTimersByTime(180000);
});
expect(onTimeout).not.toHaveBeenCalled();
});
});
@@ -58,7 +58,7 @@ export const BrailleAnimation: React.FC<BrailleAnimationProps> = ({
const shouldShow = settings.merged.ui?.showSpinner !== false;
useEffect(() => {
if (!shouldShow || !animate) return;
if (!shouldShow || !animate || variant === 'Static') return;
debugState.debugNumAnimatedComponents++;
@@ -70,7 +70,7 @@ export const BrailleAnimation: React.FC<BrailleAnimationProps> = ({
debugState.debugNumAnimatedComponents--;
clearInterval(timer);
};
}, [interval, shouldShow, animate]);
}, [interval, shouldShow, animate, variant]);
const getLength = () => {
const cycle = Math.floor(tick / 8);
@@ -11,6 +11,17 @@ Enter to submit · Esc to cancel
"
`;
exports[`AskUserDialog > Choice question placeholder > uses default placeholder when not provided 2`] = `
"Select your preferred language:
1. TypeScript
2. JavaScript
● 3. Enter a custom value
Enter to submit · Esc to cancel
"
`;
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 1`] = `
"Select your preferred language:
@@ -22,6 +33,17 @@ Enter to submit · Esc to cancel
"
`;
exports[`AskUserDialog > Choice question placeholder > uses placeholder for "Other" option when provided 2`] = `
"Select your preferred language:
1. TypeScript
2. JavaScript
● 3. Type another language...
Enter to submit · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 1`] = `
"Choose an option
@@ -38,6 +60,22 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: false) > shows scroll arrows correctly when useAlternateBuffer is false 2`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
Description 2
3. Option 3
Description 3
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 1`] = `
"Choose an option
@@ -54,6 +92,22 @@ Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Scroll Arrows (useAlternateBuffer: true) > shows scroll arrows correctly when useAlternateBuffer is true 2`] = `
"Choose an option
● 1. Option 1
Description 1
2. Option 2
Description 2
3. Option 3
Description 3
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > Text type questions > renders text input for type: "text" 1`] = `
"What should we name this component?
@@ -196,3 +250,19 @@ exports[`AskUserDialog > verifies "All of the above" visual state with snapshot
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
exports[`AskUserDialog > verifies "All of the above" visual state with snapshot 2`] = `
"Which features?
(Select all that apply)
1. [x] TypeScript
2. [x] ESLint
● 3. [x] All of the above
Select all options
4. [ ] Enter a custom value
Done
Finish selection
Enter to select · ↑/↓ to navigate · Esc to cancel
"
`;
@@ -27,6 +27,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Type your feedback...
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 1`] = `
"Overview
@@ -54,6 +81,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > calls onFeedback when feedback is typed and submitted 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Add tests
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: false > displays error state when file read fails 1`] = `
" Error reading plan: File not found
"
@@ -140,6 +194,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > bubbles up Ctrl+C when feedback is empty while editing 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Type your feedback...
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 1`] = `
"Overview
@@ -167,6 +248,33 @@ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > calls onFeedback when feedback is typed and submitted 2`] = `
"Overview
Add user authentication to the CLI application.
Implementation Steps
1. Create src/auth/AuthService.ts with login/logout methods
2. Add session storage in src/storage/SessionStore.ts
3. Update src/commands/index.ts to check auth status
4. Add tests in src/auth/__tests__/
Files to Modify
- src/index.ts - Add auth middleware
- src/config.ts - Add auth configuration options
1. Yes, automatically accept edits
Approves plan and allows tools to run automatically
2. Yes, manually accept edits
Approves plan but requires confirmation for each tool
● 3. Add tests
Enter to submit · Ctrl+X to edit plan · Esc to cancel
"
`;
exports[`ExitPlanModeDialog > useAlternateBuffer: true > displays error state when file read fails 1`] = `
" Error reading plan: File not found
"
@@ -78,6 +78,126 @@ exports[`InputPrompt > mouse interaction > should toggle paste expansion on doub
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 4`] = `
"
ERROR [vitest] No "isITerm2" export is defined on the "../utils/terminalUtils.js" mock. Did you
forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:
- If you need to partially mock a module, you can use "importOriginal" helper inside:\\t
- \\t
-VitestMocker.create
rror (file:///Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/no
de_modules/vitest/dist/chunks/execute.B7h3T_Hc.js:284:17)
-Object.ge
(file:///Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules
/vitest/dist/chunks/execute.B7h3T_Hc.js:330:16)
- HalfLinePaddedBoxInternal (src/ui/components/shared/HalfLinePaddedBox.tsx:82:19)
-Object.react-stack-bott
m-frame (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_
modules/react-reconciler/cjs/react-reconciler.development.js:15859:20)
-renderWithHoo
s (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/re
act-reconciler/cjs/react-reconciler.development.js:3221:22)
-updateFunctionComp
nent (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modul
es/react-reconciler/cjs/react-reconciler.development.js:6475:19)
-beginWor
(/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/react-r
econciler/cjs/react-reconciler.development.js:8009:18)
-runWithFiberIn
EV (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/r
eact-reconciler/cjs/react-reconciler.development.js:1738:13)
-performUnitOfW
rk (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/r
eact-reconciler/cjs/react-reconciler.development.js:12834:22)
-workLoopSyn
(/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/reac
t-reconciler/cjs/react-reconciler.development.js:12644:41)
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 5`] = `
"
ERROR [vitest] No "isITerm2" export is defined on the "../utils/terminalUtils.js" mock. Did you
forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:
- If you need to partially mock a module, you can use "importOriginal" helper inside:\\t
- \\t
-VitestMocker.create
rror (file:///Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/no
de_modules/vitest/dist/chunks/execute.B7h3T_Hc.js:284:17)
-Object.ge
(file:///Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules
/vitest/dist/chunks/execute.B7h3T_Hc.js:330:16)
- HalfLinePaddedBoxInternal (src/ui/components/shared/HalfLinePaddedBox.tsx:82:19)
-Object.react-stack-bott
m-frame (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_
modules/react-reconciler/cjs/react-reconciler.development.js:15859:20)
-renderWithHoo
s (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/re
act-reconciler/cjs/react-reconciler.development.js:3221:22)
-updateFunctionComp
nent (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modul
es/react-reconciler/cjs/react-reconciler.development.js:6475:19)
-beginWor
(/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/react-r
econciler/cjs/react-reconciler.development.js:8009:18)
-runWithFiberIn
EV (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/r
eact-reconciler/cjs/react-reconciler.development.js:1738:13)
-performUnitOfW
rk (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/r
eact-reconciler/cjs/react-reconciler.development.js:12834:22)
-workLoopSyn
(/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/reac
t-reconciler/cjs/react-reconciler.development.js:12644:41)
"
`;
exports[`InputPrompt > mouse interaction > should toggle paste expansion on double-click 6`] = `
"
ERROR [vitest] No "isITerm2" export is defined on the "../utils/terminalUtils.js" mock. Did you
forget to return it from "vi.mock"?
If you need to partially mock a module, you can use "importOriginal" helper inside:
- If you need to partially mock a module, you can use "importOriginal" helper inside:\\t
- \\t
-VitestMocker.create
rror (file:///Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/no
de_modules/vitest/dist/chunks/execute.B7h3T_Hc.js:284:17)
-Object.ge
(file:///Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules
/vitest/dist/chunks/execute.B7h3T_Hc.js:330:16)
- HalfLinePaddedBoxInternal (src/ui/components/shared/HalfLinePaddedBox.tsx:82:19)
-Object.react-stack-bott
m-frame (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_
modules/react-reconciler/cjs/react-reconciler.development.js:15859:20)
-renderWithHoo
s (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/re
act-reconciler/cjs/react-reconciler.development.js:3221:22)
-updateFunctionComp
nent (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modul
es/react-reconciler/cjs/react-reconciler.development.js:6475:19)
-beginWor
(/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/react-r
econciler/cjs/react-reconciler.development.js:8009:18)
-runWithFiberIn
EV (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/r
eact-reconciler/cjs/react-reconciler.development.js:1738:13)
-performUnitOfW
rk (/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/r
eact-reconciler/cjs/react-reconciler.development.js:12834:22)
-workLoopSyn
(/Users/keithguerin/Documents/gemini-cli/loader-animation-adjustment/node_modules/reac
t-reconciler/cjs/react-reconciler.development.js:12644:41)
"
`;
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
> Type your message or @path/to/file
@@ -9,13 +9,13 @@ import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
import { BrailleAnimation } from '../BrailleAnimation.js';
import type {
SubagentProgress,
SubagentActivityItem,
import {
type SubagentProgress,
type SubagentActivityItem,
safeJsonToMarkdown,
} from '@google/gemini-cli-core';
import { TOOL_STATUS } from '../../constants.js';
import { STATUS_INDICATOR_WIDTH } from './ToolShared.js';
import { safeJsonToMarkdown } from '@google/gemini-cli-core';
export interface SubagentProgressDisplayProps {
progress: SubagentProgress;
-2
View File
@@ -8,7 +8,6 @@ import { vi, beforeEach, afterEach } from 'vitest';
import { format } from 'node:util';
import { coreEvents } from '@google/gemini-cli-core';
import { themeManager } from './src/ui/themes/theme-manager.js';
import { cleanup } from './src/test-utils/render.js';
// Unset CI environment variable so that ink renders dynamically as it does in a real terminal
if (process.env.CI !== undefined) {
@@ -80,7 +79,6 @@ beforeEach(() => {
});
afterEach(() => {
cleanup();
consoleErrorSpy.mockRestore();
vi.unstubAllEnvs();