diff --git a/packages/cli/src/config/extensions/consent.test.ts b/packages/cli/src/config/extensions/consent.test.ts
index 76d7227ab4..8de884cdd5 100644
--- a/packages/cli/src/config/extensions/consent.test.ts
+++ b/packages/cli/src/config/extensions/consent.test.ts
@@ -59,8 +59,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
});
async function expectConsentSnapshot(consentString: string) {
- const renderResult = render(React.createElement(Text, null, consentString));
- await renderResult.waitUntilReady();
+ const renderResult = await render(
+ React.createElement(Text, null, consentString),
+ );
await expect(renderResult).toMatchSvgSnapshot();
}
diff --git a/packages/cli/src/test-utils/render.test.tsx b/packages/cli/src/test-utils/render.test.tsx
index 7172a99119..3c3f4102a4 100644
--- a/packages/cli/src/test-utils/render.test.tsx
+++ b/packages/cli/src/test-utils/render.test.tsx
@@ -12,24 +12,18 @@ import { waitFor } from './async.js';
describe('render', () => {
it('should render a component', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
- Hello World,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render(Hello World);
expect(lastFrame()).toBe('Hello World\n');
unmount();
});
it('should support rerender', async () => {
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, waitUntilReady, unmount } = await render(
Hello,
);
- await waitUntilReady();
expect(lastFrame()).toBe('Hello\n');
- await act(async () => {
- rerender(World);
- });
+ await act(async () => rerender(World));
await waitUntilReady();
expect(lastFrame()).toBe('World\n');
unmount();
@@ -42,10 +36,8 @@ describe('render', () => {
return Hello;
}
- const { unmount, waitUntilReady } = render();
- await waitUntilReady();
+ const { unmount } = await render();
unmount();
-
expect(cleanupMock).toHaveBeenCalled();
});
});
@@ -54,36 +46,27 @@ describe('renderHook', () => {
it('should rerender with previous props when called without arguments', async () => {
const useTestHook = ({ value }: { value: number }) => {
const [count, setCount] = useState(0);
- useEffect(() => {
- setCount((c) => c + 1);
- }, [value]);
+ useEffect(() => setCount((c) => c + 1), [value]);
return { count, value };
};
- const { result, rerender, waitUntilReady, unmount } = renderHook(
+ const { result, rerender, waitUntilReady, unmount } = await renderHook(
useTestHook,
- {
- initialProps: { value: 1 },
- },
+ { initialProps: { value: 1 } },
);
- await waitUntilReady();
expect(result.current.value).toBe(1);
await waitFor(() => expect(result.current.count).toBe(1));
// Rerender with new props
- await act(async () => {
- rerender({ value: 2 });
- });
+ await act(async () => rerender({ value: 2 }));
await waitUntilReady();
expect(result.current.value).toBe(2);
await waitFor(() => expect(result.current.count).toBe(2));
// Rerender without arguments should use previous props (value: 2)
// This would previously crash or pass undefined if not fixed
- await act(async () => {
- rerender();
- });
+ await act(async () => rerender());
await waitUntilReady();
expect(result.current.value).toBe(2);
// Count should not increase because value didn't change
@@ -98,14 +81,11 @@ describe('renderHook', () => {
};
const { result, rerender, waitUntilReady, unmount } =
- renderHook(useTestHook);
- await waitUntilReady();
+ await renderHook(useTestHook);
expect(result.current.count).toBe(0);
- await act(async () => {
- rerender();
- });
+ await act(async () => rerender());
await waitUntilReady();
expect(result.current.count).toBe(0);
unmount();
@@ -113,19 +93,14 @@ describe('renderHook', () => {
it('should update props if undefined is passed explicitly', async () => {
const useTestHook = (val: string | undefined) => val;
- const { result, rerender, waitUntilReady, unmount } = renderHook(
+ const { result, rerender, waitUntilReady, unmount } = await renderHook(
useTestHook,
- {
- initialProps: 'initial' as string | undefined,
- },
+ { initialProps: 'initial' },
);
- await waitUntilReady();
expect(result.current).toBe('initial');
- await act(async () => {
- rerender(undefined);
- });
+ await act(async () => rerender(undefined));
await waitUntilReady();
expect(result.current).toBeUndefined();
unmount();
diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx
index 7d298b120d..ea889181c6 100644
--- a/packages/cli/src/test-utils/render.tsx
+++ b/packages/cli/src/test-utils/render.tsx
@@ -257,13 +257,9 @@ class XtermStdout extends EventEmitter {
return currentFrame !== '';
}
- // If both are empty, it's a match.
- // We consider undefined lastRenderOutput as effectively empty for this check
- // to support hook testing where Ink may skip rendering completely.
- if (
- (this.lastRenderOutput === undefined || expectedFrame === '') &&
- currentFrame === ''
- ) {
+ // If Ink expects nothing (no new static content and no dynamic output),
+ // we consider it a match because the terminal buffer will just hold the historical static content.
+ if (expectedFrame === '') {
return true;
}
@@ -271,8 +267,8 @@ class XtermStdout extends EventEmitter {
return false;
}
- // If Ink expects nothing but terminal has content, or vice-versa, it's NOT a match.
- if (expectedFrame === '' || currentFrame === '') {
+ // If the terminal is empty but Ink expects something, it's not a match.
+ if (currentFrame === '') {
return false;
}
@@ -382,13 +378,11 @@ export type RenderInstance = {
const instances: InkInstance[] = [];
-// Wrapper around ink's render that ensures act() is called and uses Xterm for output
-export const render = (
+export const render = async (
tree: React.ReactElement,
terminalWidth?: number,
-): Omit<
- RenderInstance,
- 'capturedOverflowState' | 'capturedOverflowActions'
+): Promise<
+ Omit
> => {
const cols = terminalWidth ?? 100;
// We use 1000 rows to avoid windows with incorrect snapshots if a correct
@@ -437,6 +431,8 @@ export const render = (
instances.push(instance);
+ await stdout.waitUntilReady();
+
return {
rerender: (newTree: React.ReactElement) => {
act(() => {
@@ -751,7 +747,10 @@ export const renderWithProviders = async (
);
- const renderResult = render(wrapWithProviders(component), terminalWidth);
+ const renderResult = await render(
+ wrapWithProviders(component),
+ terminalWidth,
+ );
return {
...renderResult,
@@ -765,19 +764,19 @@ export const renderWithProviders = async (
};
};
-export function renderHook(
+export async function renderHook(
renderCallback: (props: Props) => Result,
options?: {
initialProps?: Props;
wrapper?: React.ComponentType<{ children: React.ReactNode }>;
},
-): {
+): Promise<{
result: { current: Result };
rerender: (props?: Props) => void;
unmount: () => void;
waitUntilReady: () => Promise;
generateSvg: () => string;
-} {
+}> {
const result = { current: undefined as unknown as Result };
let currentProps = options?.initialProps as Props;
@@ -800,17 +799,15 @@ export function renderHook(
let waitUntilReady: () => Promise = async () => {};
let generateSvg: () => string = () => '';
- act(() => {
- const renderResult = render(
-
-
- ,
- );
- inkRerender = renderResult.rerender;
- unmount = renderResult.unmount;
- waitUntilReady = renderResult.waitUntilReady;
- generateSvg = renderResult.generateSvg;
- });
+ const renderResult = await render(
+
+
+ ,
+ );
+ inkRerender = renderResult.rerender;
+ unmount = renderResult.unmount;
+ waitUntilReady = renderResult.waitUntilReady;
+ generateSvg = renderResult.generateSvg;
function rerender(props?: Props) {
if (arguments.length > 0) {
@@ -864,7 +861,13 @@ export async function renderHookWithProviders(
const Wrapper = options.wrapper || (({ children }) => <>{children}>);
- let renderResult: ReturnType;
+ let renderResult: RenderInstance & {
+ simulateClick: (
+ col: number,
+ row: number,
+ button?: 0 | 1 | 2,
+ ) => Promise;
+ };
await act(async () => {
renderResult = await renderWithProviders(
diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx
index 7f5e55c022..950363f6a8 100644
--- a/packages/cli/src/ui/App.test.tsx
+++ b/packages/cli/src/ui/App.test.tsx
@@ -94,14 +94,10 @@ describe('App', () => {
};
it('should render main content and composer when not quitting', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: mockUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: mockUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
+ });
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
@@ -115,14 +111,10 @@ describe('App', () => {
quittingMessages: [{ id: 1, type: 'user', text: 'test' }],
} as UIState;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: quittingUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: quittingUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
+ });
expect(lastFrame()).toContain('Quitting...');
unmount();
@@ -136,14 +128,10 @@ describe('App', () => {
pendingHistoryItems: [{ type: 'user', text: 'pending item' }],
} as UIState;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: quittingUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: quittingUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toContain('HistoryItemDisplay');
expect(lastFrame()).toContain('Quitting...');
@@ -156,14 +144,10 @@ describe('App', () => {
dialogsVisible: true,
} as UIState;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: dialogUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: dialogUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
@@ -183,14 +167,10 @@ describe('App', () => {
[stateKey]: true,
} as UIState;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toContain(`Press Ctrl+${key} again to exit.`);
unmount();
@@ -200,14 +180,10 @@ describe('App', () => {
it('should render ScreenReaderAppLayout when screen reader is enabled', async () => {
(useIsScreenReaderEnabled as Mock).mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: mockUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: mockUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toContain('Notifications');
expect(lastFrame()).toContain('Footer');
@@ -219,14 +195,10 @@ describe('App', () => {
it('should render DefaultAppLayout when screen reader is not enabled', async () => {
(useIsScreenReaderEnabled as Mock).mockReturnValue(false);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: mockUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: mockUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
@@ -274,15 +246,11 @@ describe('App', () => {
vi.spyOn(configWithExperiment, 'isTrustedFolder').mockReturnValue(true);
vi.spyOn(configWithExperiment, 'getIdeMode').mockReturnValue(false);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: stateWithConfirmingTool,
- config: configWithExperiment,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: stateWithConfirmingTool,
+ config: configWithExperiment,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toContain('Tips for getting started');
expect(lastFrame()).toContain('Notifications');
@@ -295,28 +263,20 @@ describe('App', () => {
describe('Snapshots', () => {
it('renders default layout correctly', async () => {
(useIsScreenReaderEnabled as Mock).mockReturnValue(false);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: mockUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: mockUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders screen reader layout correctly', async () => {
(useIsScreenReaderEnabled as Mock).mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: mockUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: mockUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -326,14 +286,10 @@ describe('App', () => {
...mockUIState,
dialogsVisible: true,
} as UIState;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: dialogUIState,
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: dialogUIState,
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/AppContainer.test.tsx b/packages/cli/src/ui/AppContainer.test.tsx
index 650804025b..313573a573 100644
--- a/packages/cli/src/ui/AppContainer.test.tsx
+++ b/packages/cli/src/ui/AppContainer.test.tsx
@@ -16,7 +16,7 @@ import {
} from 'vitest';
import { render, cleanup, persistentStateMock } from '../test-utils/render.js';
import { waitFor } from '../test-utils/async.js';
-import { act, useContext, type ReactElement } from 'react';
+import { act, useContext } from 'react';
import { AppContainer } from './AppContainer.js';
import { SettingsContext } from './contexts/SettingsContext.js';
import { type TrackedToolCall } from './hooks/useToolScheduler.js';
@@ -250,6 +250,15 @@ describe('AppContainer State Management', () => {
let mockInitResult: InitializationResult;
let mockExtensionManager: MockedObject;
+ type AppContainerProps = {
+ settings?: LoadedSettings;
+ config?: Config;
+ version?: string;
+ initResult?: InitializationResult;
+ startupWarnings?: StartupWarning[];
+ resumedSessionData?: ResumedSessionData;
+ };
+
// Helper to generate the AppContainer JSX for render and rerender
const getAppContainer = ({
settings = mockSettings,
@@ -258,14 +267,7 @@ describe('AppContainer State Management', () => {
initResult = mockInitResult,
startupWarnings,
resumedSessionData,
- }: {
- settings?: LoadedSettings;
- config?: Config;
- version?: string;
- initResult?: InitializationResult;
- startupWarnings?: StartupWarning[];
- resumedSessionData?: ResumedSessionData;
- } = {}) => (
+ }: AppContainerProps = {}) => (
@@ -282,7 +284,7 @@ describe('AppContainer State Management', () => {
);
// Helper to render the AppContainer
- const renderAppContainer = (props?: Parameters[0]) =>
+ const renderAppContainer = async (props?: AppContainerProps) =>
render(getAppContainer(props));
// Create typed mocks for all hooks
@@ -514,13 +516,9 @@ describe('AppContainer State Management', () => {
describe('Basic Rendering', () => {
it('renders without crashing with minimal props', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () => renderAppContainer());
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
it('renders with startup warnings', async () => {
@@ -537,44 +535,32 @@ describe('AppContainer State Management', () => {
},
];
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({ startupWarnings });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () =>
+ renderAppContainer({ startupWarnings }),
+ );
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
it('shows full UI details by default', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => {
- expect(capturedUIState.cleanUiDetailsVisible).toBe(true);
- });
- unmount!();
+ expect(capturedUIState.cleanUiDetailsVisible).toBe(true);
+ unmount();
});
it('starts in minimal UI mode when Focus UI preference is persisted', async () => {
persistentStateMock.get.mockReturnValueOnce(true);
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
settings: mockSettings,
- });
- unmount = result.unmount;
- });
+ }),
+ );
- await waitFor(() => {
- expect(capturedUIState.cleanUiDetailsVisible).toBe(false);
- });
+ expect(capturedUIState.cleanUiDetailsVisible).toBe(false);
expect(persistentStateMock.get).toHaveBeenCalledWith('focusUiEnabled');
- unmount!();
+ unmount();
});
});
@@ -609,15 +595,9 @@ describe('AppContainer State Management', () => {
],
});
- let unmount: (() => void) | undefined;
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() =>
- expect(terminalNotificationsMocks.notifyViaTerminal).toHaveBeenCalled(),
- );
+ expect(terminalNotificationsMocks.notifyViaTerminal).toHaveBeenCalled();
expect(
terminalNotificationsMocks.buildRunEventNotificationContent,
).toHaveBeenCalledWith(
@@ -626,9 +606,7 @@ describe('AppContainer State Management', () => {
}),
);
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('does not send attention notification when terminal is focused', async () => {
@@ -661,19 +639,13 @@ describe('AppContainer State Management', () => {
],
});
- let unmount: (() => void) | undefined;
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
expect(
terminalNotificationsMocks.notifyViaTerminal,
).not.toHaveBeenCalled();
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('sends attention notification when focus reporting is unavailable', async () => {
@@ -706,19 +678,11 @@ describe('AppContainer State Management', () => {
],
});
- let unmount: (() => void) | undefined;
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() =>
- expect(terminalNotificationsMocks.notifyViaTerminal).toHaveBeenCalled(),
- );
+ expect(terminalNotificationsMocks.notifyViaTerminal).toHaveBeenCalled();
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('sends a macOS notification when a response completes while unfocused', async () => {
@@ -732,35 +696,24 @@ describe('AppContainer State Management', () => {
streamingState: currentStreamingState,
}));
- let unmount: (() => void) | undefined;
- let rerender: ((tree: ReactElement) => void) | undefined;
-
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- rerender = rendered.rerender;
- });
+ const { unmount, rerender } = await act(async () => renderAppContainer());
currentStreamingState = 'idle';
await act(async () => {
- rerender?.(getAppContainer());
+ rerender(getAppContainer());
});
- await waitFor(() =>
- expect(
- terminalNotificationsMocks.buildRunEventNotificationContent,
- ).toHaveBeenCalledWith(
- expect.objectContaining({
- type: 'session_complete',
- detail: 'Gemini CLI finished responding.',
- }),
- ),
+ expect(
+ terminalNotificationsMocks.buildRunEventNotificationContent,
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: 'session_complete',
+ detail: 'Gemini CLI finished responding.',
+ }),
);
expect(terminalNotificationsMocks.notifyViaTerminal).toHaveBeenCalled();
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('sends completion notification when focus reporting is unavailable', async () => {
@@ -774,34 +727,23 @@ describe('AppContainer State Management', () => {
streamingState: currentStreamingState,
}));
- let unmount: (() => void) | undefined;
- let rerender: ((tree: ReactElement) => void) | undefined;
-
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- rerender = rendered.rerender;
- });
+ const { unmount, rerender } = await act(async () => renderAppContainer());
currentStreamingState = 'idle';
await act(async () => {
- rerender?.(getAppContainer());
+ rerender(getAppContainer());
});
- await waitFor(() =>
- expect(
- terminalNotificationsMocks.buildRunEventNotificationContent,
- ).toHaveBeenCalledWith(
- expect.objectContaining({
- type: 'session_complete',
- detail: 'Gemini CLI finished responding.',
- }),
- ),
+ expect(
+ terminalNotificationsMocks.buildRunEventNotificationContent,
+ ).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: 'session_complete',
+ detail: 'Gemini CLI finished responding.',
+ }),
);
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('does not send completion notification when another action-required dialog is pending', async () => {
@@ -819,27 +761,18 @@ describe('AppContainer State Management', () => {
streamingState: currentStreamingState,
}));
- let unmount: (() => void) | undefined;
- let rerender: ((tree: ReactElement) => void) | undefined;
-
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- rerender = rendered.rerender;
- });
+ const { unmount, rerender } = await act(async () => renderAppContainer());
currentStreamingState = 'idle';
await act(async () => {
- rerender?.(getAppContainer());
+ rerender(getAppContainer());
});
expect(
terminalNotificationsMocks.notifyViaTerminal,
).not.toHaveBeenCalled();
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('can send repeated attention notifications for the same key after pending state clears', async () => {
@@ -875,24 +808,15 @@ describe('AppContainer State Management', () => {
pendingHistoryItems,
}));
- let unmount: (() => void) | undefined;
- let rerender: ((tree: ReactElement) => void) | undefined;
+ const { unmount, rerender } = await act(async () => renderAppContainer());
- await act(async () => {
- const rendered = renderAppContainer();
- unmount = rendered.unmount;
- rerender = rendered.rerender;
- });
-
- await waitFor(() =>
- expect(
- terminalNotificationsMocks.notifyViaTerminal,
- ).toHaveBeenCalledTimes(1),
- );
+ expect(
+ terminalNotificationsMocks.notifyViaTerminal,
+ ).toHaveBeenCalledTimes(1);
pendingHistoryItems = [];
await act(async () => {
- rerender?.(getAppContainer());
+ rerender(getAppContainer());
});
pendingHistoryItems = [
@@ -917,18 +841,14 @@ describe('AppContainer State Management', () => {
},
];
await act(async () => {
- rerender?.(getAppContainer());
+ rerender(getAppContainer());
});
- await waitFor(() =>
- expect(
- terminalNotificationsMocks.notifyViaTerminal,
- ).toHaveBeenCalledTimes(2),
- );
+ expect(
+ terminalNotificationsMocks.notifyViaTerminal,
+ ).toHaveBeenCalledTimes(2);
- await act(async () => {
- unmount?.();
- });
+ unmount();
});
it('initializes with theme error from initialization result', async () => {
@@ -937,68 +857,53 @@ describe('AppContainer State Management', () => {
themeError: 'Failed to load theme',
};
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
initResult: initResultWithError,
- });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ }),
+ );
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
- it('handles debug mode state', () => {
+ it('handles debug mode state', async () => {
const debugConfig = makeFakeConfig();
vi.spyOn(debugConfig, 'getDebugMode').mockReturnValue(true);
- expect(() => {
- renderAppContainer({ config: debugConfig });
- }).not.toThrow();
+ const { unmount } = await act(async () =>
+ renderAppContainer({ config: debugConfig }),
+ );
+ unmount();
});
});
describe('Context Providers', () => {
it('provides AppContext with correct values', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({ version: '2.0.0' });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () =>
+ renderAppContainer({ version: '2.0.0' }),
+ );
+ expect(capturedUIState).toBeTruthy();
// Should render and unmount cleanly
- expect(() => unmount!()).not.toThrow();
+ unmount();
});
it('provides UIStateContext with state management', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () => renderAppContainer());
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
it('provides UIActionsContext with action handlers', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () => renderAppContainer());
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
it('provides ConfigContext with config object', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () => renderAppContainer());
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
});
@@ -1011,13 +916,11 @@ describe('AppContainer State Management', () => {
showMemoryUsage: false,
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({ settings: settingsAllHidden });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () =>
+ renderAppContainer({ settings: settingsAllHidden }),
+ );
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
it('handles settings with memory usage enabled', async () => {
@@ -1025,13 +928,11 @@ describe('AppContainer State Management', () => {
showMemoryUsage: true,
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({ settings: settingsWithMemory });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () =>
+ renderAppContainer({ settings: settingsWithMemory }),
+ );
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
});
@@ -1039,13 +940,11 @@ describe('AppContainer State Management', () => {
it.each(['1.0.0', '2.1.3-beta', '3.0.0-nightly'])(
'handles version format: %s',
async (version) => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({ version });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () =>
+ renderAppContainer({ version }),
+ );
+ expect(capturedUIState).toBeTruthy();
+ unmount();
},
);
});
@@ -1058,30 +957,30 @@ describe('AppContainer State Management', () => {
});
// Should still render without crashing - errors should be handled internally
- const { unmount } = renderAppContainer({ config: errorConfig });
+ const { unmount } = await act(async () =>
+ renderAppContainer({ config: errorConfig }),
+ );
unmount();
});
it('handles undefined settings gracefully', async () => {
const undefinedSettings = createMockSettings();
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({ settings: undefinedSettings });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
- unmount!();
+ const { unmount } = await act(async () =>
+ renderAppContainer({ settings: undefinedSettings }),
+ );
+ expect(capturedUIState).toBeTruthy();
+ unmount();
});
});
describe('Provider Hierarchy', () => {
- it('establishes correct provider nesting order', () => {
+ it('establishes correct provider nesting order', async () => {
// This tests that all the context providers are properly nested
// and that the component tree can be built without circular dependencies
- const { unmount } = renderAppContainer();
+ const { unmount } = await act(async () => renderAppContainer());
- expect(() => unmount()).not.toThrow();
+ unmount();
});
});
@@ -1113,40 +1012,32 @@ describe('AppContainer State Management', () => {
filePath: '/tmp/test-session.json',
};
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
config: mockConfig,
settings: mockSettings,
version: '1.0.0',
initResult: mockInitResult,
resumedSessionData: mockResumedSessionData,
- });
- unmount = result.unmount;
- });
- await act(async () => {
- unmount();
- });
+ }),
+ );
+ unmount();
});
it('renders without resumed session data', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
config: mockConfig,
settings: mockSettings,
version: '1.0.0',
initResult: mockInitResult,
resumedSessionData: undefined,
- });
- unmount = result.unmount;
- });
- await act(async () => {
- unmount();
- });
+ }),
+ );
+ unmount();
});
- it('initializes chat recording service when config has it', () => {
+ it('initializes chat recording service when config has it', async () => {
const mockChatRecordingService = {
initialize: vi.fn(),
recordMessage: vi.fn(),
@@ -1166,18 +1057,19 @@ describe('AppContainer State Management', () => {
mockGeminiClient as unknown as ReturnType,
);
- expect(() => {
+ const { unmount } = await act(async () =>
renderAppContainer({
config: configWithRecording,
settings: mockSettings,
version: '1.0.0',
initResult: mockInitResult,
- });
- }).not.toThrow();
+ }),
+ );
+ unmount();
});
});
describe('Session Recording Integration', () => {
- it('provides chat recording service configuration', () => {
+ it('provides chat recording service configuration', async () => {
const mockChatRecordingService = {
initialize: vi.fn(),
recordMessage: vi.fn(),
@@ -1203,23 +1095,24 @@ describe('AppContainer State Management', () => {
'test-session-123',
);
- expect(() => {
+ const { unmount } = await act(async () =>
renderAppContainer({
config: configWithRecording,
settings: mockSettings,
version: '1.0.0',
initResult: mockInitResult,
- });
- }).not.toThrow();
+ }),
+ );
// Verify the recording service structure is correct
expect(configWithRecording.getGeminiClient).toBeDefined();
expect(mockGeminiClient.getChatRecordingService).toBeDefined();
expect(mockChatRecordingService.initialize).toBeDefined();
expect(mockChatRecordingService.recordMessage).toBeDefined();
+ unmount();
});
- it('handles session recording when messages are added', () => {
+ it('handles session recording when messages are added', async () => {
const mockRecordMessage = vi.fn();
const mockRecordMessageTokens = vi.fn();
@@ -1242,22 +1135,25 @@ describe('AppContainer State Management', () => {
mockGeminiClient as unknown as ReturnType,
);
- renderAppContainer({
- config: configWithRecording,
- settings: mockSettings,
- version: '1.0.0',
- initResult: mockInitResult,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ config: configWithRecording,
+ settings: mockSettings,
+ version: '1.0.0',
+ initResult: mockInitResult,
+ }),
+ );
// The actual recording happens through the useHistory hook
// which would be triggered by user interactions
expect(mockChatRecordingService.initialize).toBeDefined();
expect(mockChatRecordingService.recordMessage).toBeDefined();
+ unmount();
});
});
describe('Session Resume Flow', () => {
- it('accepts resumed session data', () => {
+ it('accepts resumed session data', async () => {
const mockResumeChat = vi.fn();
const mockGeminiClient = {
isInitialized: vi.fn(() => true),
@@ -1303,22 +1199,23 @@ describe('AppContainer State Management', () => {
filePath: '/tmp/resumed-session.json',
};
- expect(() => {
+ const { unmount } = await act(async () =>
renderAppContainer({
config: configWithClient,
settings: mockSettings,
version: '1.0.0',
initResult: mockInitResult,
resumedSessionData: resumedData,
- });
- }).not.toThrow();
+ }),
+ );
// Verify the resume functionality structure is in place
expect(mockGeminiClient.resumeChat).toBeDefined();
expect(resumedData.conversation.messages).toHaveLength(2);
+ unmount();
});
- it('does not attempt resume when client is not initialized', () => {
+ it('does not attempt resume when client is not initialized', async () => {
const mockResumeChat = vi.fn();
const mockGeminiClient = {
isInitialized: vi.fn(() => false), // Not initialized
@@ -1343,21 +1240,24 @@ describe('AppContainer State Management', () => {
filePath: '/tmp/session.json',
};
- renderAppContainer({
- config: configWithClient,
- settings: mockSettings,
- version: '1.0.0',
- initResult: mockInitResult,
- resumedSessionData: resumedData,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ config: configWithClient,
+ settings: mockSettings,
+ version: '1.0.0',
+ initResult: mockInitResult,
+ resumedSessionData: resumedData,
+ }),
+ );
// Should not call resumeChat when client is not initialized
expect(mockResumeChat).not.toHaveBeenCalled();
+ unmount();
});
});
describe('Token Counting from Session Stats', () => {
- it('tracks token counts from session messages', () => {
+ it('tracks token counts from session messages', async () => {
// Session stats are provided through the SessionStatsProvider context
// in the real app, not through the config directly
const mockChatRecordingService = {
@@ -1385,33 +1285,30 @@ describe('AppContainer State Management', () => {
mockGeminiClient as unknown as ReturnType,
);
- renderAppContainer({
- config: configWithRecording,
- settings: mockSettings,
- version: '1.0.0',
- initResult: mockInitResult,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ config: configWithRecording,
+ settings: mockSettings,
+ version: '1.0.0',
+ initResult: mockInitResult,
+ }),
+ );
// In the actual app, these stats would be displayed in components
// and updated as messages are processed through the recording service
expect(mockChatRecordingService.recordMessageTokens).toBeDefined();
expect(mockChatRecordingService.getCurrentConversation).toBeDefined();
+ unmount();
});
});
describe('Quota and Fallback Integration', () => {
it('passes a null proQuotaRequest to UIStateContext by default', async () => {
// The default mock from beforeEach already sets proQuotaRequest to null
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => {
- // Assert that the context value is as expected
- expect(capturedUIState.quota.proQuotaRequest).toBeNull();
- });
- unmount!();
+ const { unmount } = await act(async () => renderAppContainer());
+ // Assert that the context value is as expected
+ expect(capturedUIState.quota.proQuotaRequest).toBeNull();
+ unmount();
});
it('passes a valid proQuotaRequest to UIStateContext when provided by the hook', async () => {
@@ -1427,16 +1324,10 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => {
- // Assert: The mock request is correctly passed through the context
- expect(capturedUIState.quota.proQuotaRequest).toEqual(mockRequest);
- });
- unmount!();
+ const { unmount } = await act(async () => renderAppContainer());
+ // Assert: The mock request is correctly passed through the context
+ expect(capturedUIState.quota.proQuotaRequest).toEqual(mockRequest);
+ unmount();
});
it('passes the handleProQuotaChoice function to UIActionsContext', async () => {
@@ -1448,22 +1339,16 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => {
- // Assert: The action in the context is the mock handler we provided
- expect(capturedUIActions.handleProQuotaChoice).toBe(mockHandler);
- });
+ const { unmount } = await act(async () => renderAppContainer());
+ // Assert: The action in the context is the mock handler we provided
+ expect(capturedUIActions.handleProQuotaChoice).toBe(mockHandler);
// You can even verify that the plumbed function is callable
act(() => {
capturedUIActions.handleProQuotaChoice('retry_later');
});
expect(mockHandler).toHaveBeenCalledWith('retry_later');
- unmount!();
+ unmount();
});
});
@@ -1479,7 +1364,7 @@ describe('AppContainer State Management', () => {
expect(stdout).toBe(mocks.mockStdout);
});
- it('should update terminal title with Working… when showStatusInTitle is false', () => {
+ it('should update terminal title with Working… when showStatusInTitle is false', async () => {
// Arrange: Set up mock settings with showStatusInTitle disabled
const mockSettingsWithShowStatusFalse = createMockSettings({
ui: {
@@ -1496,9 +1381,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithShowStatusFalse,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithShowStatusFalse,
+ }),
+ );
// Assert: Check that title was updated with "Working…"
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1512,7 +1399,7 @@ describe('AppContainer State Management', () => {
unmount();
});
- it('should use legacy terminal title when dynamicWindowTitle is false', () => {
+ it('should use legacy terminal title when dynamicWindowTitle is false', async () => {
// Arrange: Set up mock settings with dynamicWindowTitle disabled
const mockSettingsWithDynamicTitleFalse = createMockSettings({
ui: {
@@ -1529,9 +1416,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithDynamicTitleFalse,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithDynamicTitleFalse,
+ }),
+ );
// Assert: Check that legacy title was used
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1545,7 +1434,7 @@ describe('AppContainer State Management', () => {
unmount();
});
- it('should not update terminal title when hideWindowTitle is true', () => {
+ it('should not update terminal title when hideWindowTitle is true', async () => {
// Arrange: Set up mock settings with hideWindowTitle enabled
const mockSettingsWithHideTitleTrue = createMockSettings({
ui: {
@@ -1555,9 +1444,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithHideTitleTrue,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithHideTitleTrue,
+ }),
+ );
// Assert: Check that no title-related writes occurred
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1568,7 +1459,7 @@ describe('AppContainer State Management', () => {
unmount();
});
- it('should update terminal title with thought subject when in active state', () => {
+ it('should update terminal title with thought subject when in active state', async () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
const mockSettingsWithTitleEnabled = createMockSettings({
ui: {
@@ -1586,9 +1477,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Assert: Check that title was updated with thought subject and suffix
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1602,7 +1495,7 @@ describe('AppContainer State Management', () => {
unmount();
});
- it('should update terminal title with default text when in Idle state and no thought subject', () => {
+ it('should update terminal title with default text when in Idle state and no thought subject', async () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
const mockSettingsWithTitleEnabled = createMockSettings({
ui: {
@@ -1615,9 +1508,11 @@ describe('AppContainer State Management', () => {
mockedUseGeminiStream.mockReturnValue(DEFAULT_GEMINI_STREAM_MOCK);
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Assert: Check that title was updated with default Idle text
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1649,13 +1544,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
settings: mockSettingsWithTitleEnabled,
- });
- unmount = result.unmount;
- });
+ }),
+ );
// Assert: Check that title was updated with confirmation text
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1666,7 +1559,7 @@ describe('AppContainer State Management', () => {
expect(titleWrites[0][0]).toBe(
`\x1b]0;${'✋ Action Required (workspace)'.padEnd(80, ' ')}\x07`,
);
- unmount!();
+ unmount();
});
describe('Shell Focus Action Required', () => {
@@ -1712,9 +1605,11 @@ describe('AppContainer State Management', () => {
vi.spyOn(mockConfig, 'isInteractiveShellEnabled').mockReturnValue(true);
// Act: Render the container (embeddedShellFocused is false by default in state)
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Initially it should show the working status
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1773,9 +1668,11 @@ describe('AppContainer State Management', () => {
vi.spyOn(mockConfig, 'isInteractive').mockReturnValue(true);
vi.spyOn(mockConfig, 'isInteractiveShellEnabled').mockReturnValue(true);
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Fast-forward time by 65 seconds - should still NOT be Action Required
await act(async () => {
@@ -1830,9 +1727,11 @@ describe('AppContainer State Management', () => {
vi.spyOn(mockConfig, 'isInteractive').mockReturnValue(true);
vi.spyOn(mockConfig, 'isInteractiveShellEnabled').mockReturnValue(true);
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Fast-forward time by 65 seconds
await act(async () => {
@@ -1875,9 +1774,11 @@ describe('AppContainer State Management', () => {
vi.spyOn(mockConfig, 'isInteractiveShellEnabled').mockReturnValue(true);
// Act: Render the container
- const { unmount, rerender } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount, rerender } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Fast-forward time by 20 seconds
await act(async () => {
@@ -1931,7 +1832,7 @@ describe('AppContainer State Management', () => {
});
});
- it('should pad title to exactly 80 characters', () => {
+ it('should pad title to exactly 80 characters', async () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
const mockSettingsWithTitleEnabled = createMockSettings({
ui: {
@@ -1949,9 +1850,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Assert: Check that title is padded to exactly 80 characters
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1966,7 +1869,7 @@ describe('AppContainer State Management', () => {
unmount();
});
- it('should use correct ANSI escape code format', () => {
+ it('should use correct ANSI escape code format', async () => {
// Arrange: Set up mock settings with showStatusInTitle enabled
const mockSettingsWithTitleEnabled = createMockSettings({
ui: {
@@ -1984,9 +1887,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleEnabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleEnabled,
+ }),
+ );
// Assert: Check that the correct ANSI escape sequence is used
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -1999,7 +1904,7 @@ describe('AppContainer State Management', () => {
unmount();
});
- it('should use CLI_TITLE environment variable when set', () => {
+ it('should use CLI_TITLE environment variable when set', async () => {
// Arrange: Set up mock settings with showStatusInTitle disabled (so it shows suffix)
const mockSettingsWithTitleDisabled = createMockSettings({
ui: {
@@ -2018,9 +1923,11 @@ describe('AppContainer State Management', () => {
});
// Act: Render the container
- const { unmount } = renderAppContainer({
- settings: mockSettingsWithTitleDisabled,
- });
+ const { unmount } = await act(async () =>
+ renderAppContainer({
+ settings: mockSettingsWithTitleDisabled,
+ }),
+ );
// Assert: Check that title was updated with CLI_TITLE value
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) =>
@@ -2046,7 +1953,7 @@ describe('AppContainer State Management', () => {
});
it('should set and clear the queue error message after a timeout', async () => {
- const { rerender, unmount } = renderAppContainer();
+ const { rerender, unmount } = await act(async () => renderAppContainer());
await act(async () => {
vi.advanceTimersByTime(0);
});
@@ -2068,7 +1975,7 @@ describe('AppContainer State Management', () => {
});
it('should reset the timer if a new error message is set', async () => {
- const { rerender, unmount } = renderAppContainer();
+ const { rerender, unmount } = await act(async () => renderAppContainer());
await act(async () => {
vi.advanceTimersByTime(0);
});
@@ -2110,11 +2017,11 @@ describe('AppContainer State Management', () => {
let mockCancelOngoingRequest: Mock;
let rerender: () => void;
let unmount: () => void;
- let stdin: ReturnType['stdin'];
+ let stdin: Awaited>['stdin'];
// Helper function to reduce boilerplate in tests
const setupKeypressTest = async () => {
- const renderResult = renderAppContainer();
+ const renderResult = await act(async () => renderAppContainer());
stdin = renderResult.stdin;
await act(async () => {
vi.advanceTimersByTime(0);
@@ -2328,7 +2235,7 @@ describe('AppContainer State Management', () => {
activePtyId: 1,
});
- const renderResult = render(getAppContainer());
+ const renderResult = await act(async () => render(getAppContainer()));
await act(async () => {
vi.advanceTimersByTime(0);
});
@@ -2446,7 +2353,7 @@ describe('AppContainer State Management', () => {
let unmount: () => void;
const setupShortcutsVisibilityTest = async () => {
- const renderResult = renderAppContainer();
+ const renderResult = await act(async () => renderAppContainer());
await act(async () => {
vi.advanceTimersByTime(0);
});
@@ -2522,9 +2429,7 @@ describe('AppContainer State Management', () => {
await act(async () => {
rerender();
});
- await waitFor(() => {
- expect(capturedUIState.shortcutsHelpVisible).toBe(false);
- });
+ expect(capturedUIState.shortcutsHelpVisible).toBe(false);
unmount();
});
@@ -2553,9 +2458,7 @@ describe('AppContainer State Management', () => {
await act(async () => {
rerender();
});
- await waitFor(() => {
- expect(capturedUIState.shortcutsHelpVisible).toBe(false);
- });
+ expect(capturedUIState.shortcutsHelpVisible).toBe(false);
unmount();
});
@@ -2564,7 +2467,7 @@ describe('AppContainer State Management', () => {
describe('Copy Mode (CTRL+S)', () => {
let rerender: () => void;
let unmount: () => void;
- let stdin: ReturnType['stdin'];
+ let stdin: Awaited>['stdin'];
const setupCopyModeTest = async (
isAlternateMode = false,
@@ -2602,7 +2505,7 @@ describe('AppContainer State Management', () => {
);
- const renderResult = render(getTree(testSettings));
+ const renderResult = await act(async () => render(getTree(testSettings)));
stdin = renderResult.stdin;
await act(async () => {
vi.advanceTimersByTime(0);
@@ -2792,15 +2695,10 @@ describe('AppContainer State Management', () => {
closeModelDialog: vi.fn(),
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
expect(capturedUIState.isModelDialogOpen).toBe(true);
- unmount!();
+ unmount();
});
it('should provide model dialog actions in the UIActionsContext', async () => {
@@ -2812,45 +2710,29 @@ describe('AppContainer State Management', () => {
closeModelDialog: mockCloseModelDialog,
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
// Verify that the actions are correctly passed through context
act(() => {
capturedUIActions.closeModelDialog();
});
expect(mockCloseModelDialog).toHaveBeenCalled();
- unmount!();
+ unmount();
});
});
describe('Agent Configuration Dialog Integration', () => {
it('should initialize with dialog closed and no agent selected', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
-
+ const { unmount } = await act(async () => renderAppContainer());
expect(capturedUIState.isAgentConfigDialogOpen).toBe(false);
expect(capturedUIState.selectedAgentName).toBeUndefined();
expect(capturedUIState.selectedAgentDisplayName).toBeUndefined();
expect(capturedUIState.selectedAgentDefinition).toBeUndefined();
- unmount!();
+ unmount();
});
it('should update state when openAgentConfigDialog is called', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
const agentDefinition = { name: 'test-agent' };
act(() => {
@@ -2865,16 +2747,11 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.selectedAgentName).toBe('test-agent');
expect(capturedUIState.selectedAgentDisplayName).toBe('Test Agent');
expect(capturedUIState.selectedAgentDefinition).toEqual(agentDefinition);
- unmount!();
+ unmount();
});
it('should clear state when closeAgentConfigDialog is called', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
const agentDefinition = { name: 'test-agent' };
act(() => {
@@ -2895,31 +2772,26 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.selectedAgentName).toBeUndefined();
expect(capturedUIState.selectedAgentDisplayName).toBeUndefined();
expect(capturedUIState.selectedAgentDefinition).toBeUndefined();
- unmount!();
+ unmount();
});
});
describe('CoreEvents Integration', () => {
it('subscribes to UserFeedback and drains backlog on mount', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
expect(mockCoreEvents.on).toHaveBeenCalledWith(
CoreEvent.UserFeedback,
expect.any(Function),
);
expect(mockCoreEvents.drainBacklogs).toHaveBeenCalledTimes(1);
- unmount!();
+ unmount();
});
it('unsubscribes from UserFeedback on unmount', async () => {
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => expect(capturedUIState).toBeTruthy());
@@ -2935,7 +2807,7 @@ describe('AppContainer State Management', () => {
it('adds history item when UserFeedback event is received', async () => {
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => expect(capturedUIState).toBeTruthy());
@@ -2971,7 +2843,7 @@ describe('AppContainer State Management', () => {
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => {
@@ -3004,7 +2876,7 @@ describe('AppContainer State Management', () => {
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => expect(capturedUIState).toBeTruthy());
@@ -3016,7 +2888,7 @@ describe('AppContainer State Management', () => {
it('handles consent request events', async () => {
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => expect(capturedUIState).toBeTruthy());
@@ -3053,7 +2925,7 @@ describe('AppContainer State Management', () => {
it('unsubscribes from ConsentRequest on unmount', async () => {
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => expect(capturedUIState).toBeTruthy());
@@ -3076,7 +2948,7 @@ describe('AppContainer State Management', () => {
});
let unmount: () => void;
await act(async () => {
- const result = renderAppContainer();
+ const result = await renderAppContainer();
unmount = result.unmount;
});
await waitFor(() => {
@@ -3104,12 +2976,7 @@ describe('AppContainer State Management', () => {
});
it('preserves buffer when cancelling, even if empty (user is in control)', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
const { onCancelSubmit } = extractUseGeminiStreamArgs(
mockedUseGeminiStream.mock.lastCall!,
@@ -3122,7 +2989,7 @@ describe('AppContainer State Management', () => {
// Should NOT modify buffer when cancelling - user is in control
expect(mockSetText).not.toHaveBeenCalled();
- unmount!();
+ unmount();
});
it('preserves prompt text when cancelling streaming, even if same as last message (regression test for issue #13387)', async () => {
@@ -3140,12 +3007,7 @@ describe('AppContainer State Management', () => {
initializeFromLogger: vi.fn(),
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
const { onCancelSubmit } = extractUseGeminiStreamArgs(
mockedUseGeminiStream.mock.lastCall!,
@@ -3159,7 +3021,7 @@ describe('AppContainer State Management', () => {
// Should NOT call setText - prompt should be preserved regardless of content
expect(mockSetText).not.toHaveBeenCalled();
- unmount!();
+ unmount();
});
it('restores the prompt when onCancelSubmit is called with shouldRestorePrompt=true (or undefined)', async () => {
@@ -3170,14 +3032,8 @@ describe('AppContainer State Management', () => {
initializeFromLogger: vi.fn(),
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() =>
- expect(capturedUIState.userMessages).toContain('previous message'),
- );
+ const { unmount } = await act(async () => renderAppContainer());
+ expect(capturedUIState.userMessages).toContain('previous message');
const { onCancelSubmit } = extractUseGeminiStreamArgs(
mockedUseGeminiStream.mock.lastCall!,
@@ -3187,11 +3043,9 @@ describe('AppContainer State Management', () => {
onCancelSubmit(true);
});
- await waitFor(() => {
- expect(mockSetText).toHaveBeenCalledWith('previous message');
- });
+ expect(mockSetText).toHaveBeenCalledWith('previous message');
- unmount!();
+ unmount();
});
it('input history is independent from conversation history (survives /clear)', async () => {
@@ -3204,18 +3058,10 @@ describe('AppContainer State Management', () => {
initializeFromLogger: vi.fn(),
});
- let rerender: (tree: ReactElement) => void;
- let unmount;
- await act(async () => {
- const result = renderAppContainer();
- rerender = result.rerender;
- unmount = result.unmount;
- });
+ const { rerender, unmount } = await act(async () => renderAppContainer());
// Verify userMessages is populated from inputHistory
- await waitFor(() =>
- expect(capturedUIState.userMessages).toContain('first prompt'),
- );
+ expect(capturedUIState.userMessages).toContain('first prompt');
expect(capturedUIState.userMessages).toContain('second prompt');
// Clear the conversation history (simulating /clear command)
@@ -3238,7 +3084,7 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.userMessages).toContain('first prompt');
expect(capturedUIState.userMessages).toContain('second prompt');
- unmount!();
+ unmount();
});
});
@@ -3253,14 +3099,10 @@ describe('AppContainer State Management', () => {
// Clear previous calls
mocks.mockStdout.write.mockClear();
- let compUnmount: () => void = () => {};
- await act(async () => {
- const { unmount } = renderAppContainer();
- compUnmount = unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
// Allow async effects to run
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ expect(capturedUIState).toBeTruthy();
// Wait for fetchBannerTexts to complete
await act(async () => {
@@ -3273,7 +3115,7 @@ describe('AppContainer State Management', () => {
);
expect(clearTerminalCalls).toHaveLength(0);
- compUnmount();
+ unmount();
});
});
@@ -3284,14 +3126,13 @@ describe('AppContainer State Management', () => {
);
vi.mocked(checkPermissions).mockResolvedValue([]);
- let unmount: () => void;
- await act(async () => {
- unmount = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
- }).unmount;
- });
+ }),
+ );
- await waitFor(() => expect(capturedUIActions).toBeTruthy());
+ expect(capturedUIActions).toBeTruthy();
// Expand first
act(() => capturedUIActions.setConstrainHeight(false));
@@ -3309,7 +3150,7 @@ describe('AppContainer State Management', () => {
expect(mocks.mockStdout.write).toHaveBeenCalledWith(
ansiEscapes.clearTerminal,
);
- unmount!();
+ unmount();
});
it('resets expansion state on submission when in alternate buffer without clearing terminal', async () => {
@@ -3320,14 +3161,13 @@ describe('AppContainer State Management', () => {
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(true);
- let unmount: () => void;
- await act(async () => {
- unmount = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- }).unmount;
- });
+ }),
+ );
- await waitFor(() => expect(capturedUIActions).toBeTruthy());
+ expect(capturedUIActions).toBeTruthy();
// Expand first
act(() => capturedUIActions.setConstrainHeight(false));
@@ -3345,7 +3185,7 @@ describe('AppContainer State Management', () => {
expect(mocks.mockStdout.write).not.toHaveBeenCalledWith(
ansiEscapes.clearTerminal,
);
- unmount!();
+ unmount();
});
});
@@ -3358,13 +3198,9 @@ describe('AppContainer State Management', () => {
vi.useRealTimers();
});
- it('sets showIsExpandableHint when overflow occurs in Standard Mode and hides after 10s', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ it('should set showIsExpandableHint when overflow occurs in Standard Mode and hides after 10s', async () => {
+ const { unmount } = await act(async () => renderAppContainer());
+ await waitFor(() => expect(capturedOverflowActions).toBeTruthy());
// Trigger overflow
act(() => {
@@ -3390,16 +3226,12 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.showIsExpandableHint).toBe(false);
});
- unmount!();
+ unmount();
});
it('resets the hint timer when a new component overflows (overflowingIdsSize increases)', async () => {
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { unmount } = await act(async () => renderAppContainer());
+ await waitFor(() => expect(capturedOverflowActions).toBeTruthy());
// 1. Trigger first overflow
act(() => {
@@ -3447,18 +3279,12 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.showIsExpandableHint).toBe(false);
});
- unmount!();
+ unmount();
});
it('toggles expansion state and resets the hint timer when Ctrl+O is pressed in Standard Mode', async () => {
- let unmount: () => void;
- let stdin: ReturnType['stdin'];
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- stdin = result.stdin;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { stdin, unmount } = await act(async () => renderAppContainer());
+ await waitFor(() => expect(capturedOverflowActions).toBeTruthy());
// Initial state is constrainHeight = true
expect(capturedUIState.constrainHeight).toBe(true);
@@ -3483,10 +3309,8 @@ describe('AppContainer State Management', () => {
stdin.write('\x0f'); // \x0f is Ctrl+O
});
- await waitFor(() => {
- // constrainHeight should toggle
- expect(capturedUIState.constrainHeight).toBe(false);
- });
+ // constrainHeight should toggle
+ expect(capturedUIState.constrainHeight).toBe(false);
// Advance enough that the original timer would have expired if it hadn't reset
act(() => {
@@ -3505,18 +3329,12 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.showIsExpandableHint).toBe(false);
});
- unmount!();
+ unmount();
});
it('toggles Ctrl+O multiple times and verifies the hint disappears exactly after the last toggle', async () => {
- let unmount: () => void;
- let stdin: ReturnType['stdin'];
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- stdin = result.stdin;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ const { stdin, unmount } = await act(async () => renderAppContainer());
+ await waitFor(() => expect(capturedOverflowActions).toBeTruthy());
// Initial state is constrainHeight = true
expect(capturedUIState.constrainHeight).toBe(true);
@@ -3540,9 +3358,7 @@ describe('AppContainer State Management', () => {
act(() => {
stdin.write('\x0f'); // Ctrl+O
});
- await waitFor(() => {
- expect(capturedUIState.constrainHeight).toBe(false);
- });
+ expect(capturedUIState.constrainHeight).toBe(false);
// Wait 1 second
act(() => {
@@ -3554,9 +3370,7 @@ describe('AppContainer State Management', () => {
act(() => {
stdin.write('\x0f'); // Ctrl+O
});
- await waitFor(() => {
- expect(capturedUIState.constrainHeight).toBe(true);
- });
+ expect(capturedUIState.constrainHeight).toBe(true);
// Wait 1 second
act(() => {
@@ -3568,9 +3382,7 @@ describe('AppContainer State Management', () => {
act(() => {
stdin.write('\x0f'); // Ctrl+O
});
- await waitFor(() => {
- expect(capturedUIState.constrainHeight).toBe(false);
- });
+ expect(capturedUIState.constrainHeight).toBe(false);
// Now we wait just before the timeout from the LAST toggle.
// It should still be true.
@@ -3588,7 +3400,7 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.showIsExpandableHint).toBe(false);
});
- unmount!();
+ unmount();
});
it('DOES set showIsExpandableHint when overflow occurs in Alternate Buffer Mode', async () => {
@@ -3598,14 +3410,12 @@ describe('AppContainer State Management', () => {
vi.spyOn(mockConfig, 'getUseAlternateBuffer').mockReturnValue(true);
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer({
+ const { unmount } = await act(async () =>
+ renderAppContainer({
settings: settingsWithAlternateBuffer,
- });
- unmount = result.unmount;
- });
- await waitFor(() => expect(capturedUIState).toBeTruthy());
+ }),
+ );
+ await waitFor(() => expect(capturedOverflowActions).toBeTruthy());
// Trigger overflow
act(() => {
@@ -3617,7 +3427,7 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.showIsExpandableHint).toBe(true);
});
- unmount!();
+ unmount();
});
});
@@ -3628,10 +3438,9 @@ describe('AppContainer State Management', () => {
);
vi.mocked(checkPermissions).mockResolvedValue(['/test/file.txt']);
- let unmount: () => void;
- await act(async () => (unmount = renderAppContainer().unmount));
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => expect(capturedUIActions).toBeTruthy());
+ expect(capturedUIActions).toBeTruthy();
await act(async () =>
capturedUIActions.handleFinalSubmit('read @file.txt'),
@@ -3641,7 +3450,7 @@ describe('AppContainer State Management', () => {
expect(capturedUIState.permissionConfirmationRequest?.files).toEqual([
'/test/file.txt',
]);
- await act(async () => unmount!());
+ unmount();
});
it.each([true, false])(
@@ -3657,10 +3466,9 @@ describe('AppContainer State Management', () => {
);
const { submitQuery } = mockedUseGeminiStream();
- let unmount: () => void;
- await act(async () => (unmount = renderAppContainer().unmount));
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => expect(capturedUIActions).toBeTruthy());
+ expect(capturedUIActions).toBeTruthy();
await act(async () =>
capturedUIActions.handleFinalSubmit('read @file.txt'),
@@ -3679,7 +3487,7 @@ describe('AppContainer State Management', () => {
}
expect(submitQuery).toHaveBeenCalledWith('read @file.txt');
expect(capturedUIState.permissionConfirmationRequest).toBeNull();
- await act(async () => unmount!());
+ unmount();
},
);
});
@@ -3692,17 +3500,11 @@ describe('AppContainer State Management', () => {
pendingHistoryItems: [],
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => {
- expect(capturedUIState).toBeTruthy();
- expect(capturedUIState.allowPlanMode).toBe(true);
- });
- unmount!();
+ expect(capturedUIState).toBeTruthy();
+ expect(capturedUIState.allowPlanMode).toBe(true);
+ unmount();
});
it('should NOT allow plan mode when disabled in config', async () => {
@@ -3712,17 +3514,11 @@ describe('AppContainer State Management', () => {
pendingHistoryItems: [],
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => {
- expect(capturedUIState).toBeTruthy();
- expect(capturedUIState.allowPlanMode).toBe(false);
- });
- unmount!();
+ expect(capturedUIState).toBeTruthy();
+ expect(capturedUIState.allowPlanMode).toBe(false);
+ unmount();
});
it('should NOT allow plan mode when streaming', async () => {
@@ -3733,17 +3529,11 @@ describe('AppContainer State Management', () => {
pendingHistoryItems: [],
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => {
- expect(capturedUIState).toBeTruthy();
- expect(capturedUIState.allowPlanMode).toBe(false);
- });
- unmount!();
+ expect(capturedUIState).toBeTruthy();
+ expect(capturedUIState.allowPlanMode).toBe(false);
+ unmount();
});
it('should NOT allow plan mode when a tool is awaiting confirmation', async () => {
@@ -3764,17 +3554,11 @@ describe('AppContainer State Management', () => {
],
});
- let unmount: () => void;
- await act(async () => {
- const result = renderAppContainer();
- unmount = result.unmount;
- });
+ const { unmount } = await act(async () => renderAppContainer());
- await waitFor(() => {
- expect(capturedUIState).toBeTruthy();
- expect(capturedUIState.allowPlanMode).toBe(false);
- });
- unmount!();
+ expect(capturedUIState).toBeTruthy();
+ expect(capturedUIState.allowPlanMode).toBe(false);
+ unmount();
});
});
});
diff --git a/packages/cli/src/ui/IdeIntegrationNudge.test.tsx b/packages/cli/src/ui/IdeIntegrationNudge.test.tsx
index 5df3534f12..eb3e6a3e4c 100644
--- a/packages/cli/src/ui/IdeIntegrationNudge.test.tsx
+++ b/packages/cli/src/ui/IdeIntegrationNudge.test.tsx
@@ -53,10 +53,9 @@ describe('IdeIntegrationNudge', () => {
});
it('renders correctly with default options', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('Do you want to connect VS Code to Gemini CLI?');
@@ -72,8 +71,6 @@ describe('IdeIntegrationNudge', () => {
,
);
- await waitUntilReady();
-
// "Yes" is the first option and selected by default usually.
await act(async () => {
stdin.write('\r');
@@ -93,8 +90,6 @@ describe('IdeIntegrationNudge', () => {
,
);
- await waitUntilReady();
-
// Navigate down to "No (esc)"
await act(async () => {
stdin.write('\u001B[B'); // Down arrow
@@ -119,8 +114,6 @@ describe('IdeIntegrationNudge', () => {
,
);
- await waitUntilReady();
-
// Navigate down to "No, don't ask again"
await act(async () => {
stdin.write('\u001B[B'); // Down arrow
@@ -150,8 +143,6 @@ describe('IdeIntegrationNudge', () => {
,
);
- await waitUntilReady();
-
// Press Escape
await act(async () => {
stdin.write('\u001B');
@@ -178,8 +169,6 @@ describe('IdeIntegrationNudge', () => {
,
);
- await waitUntilReady();
-
const frame = lastFrame();
expect(frame).toContain(
diff --git a/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx b/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx
index b8de6adb0b..d46e0295a1 100644
--- a/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx
+++ b/packages/cli/src/ui/auth/ApiAuthDialog.test.tsx
@@ -73,23 +73,21 @@ describe('ApiAuthDialog', () => {
});
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders with a defaultValue', async () => {
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
expect(mockedUseTextBuffer).toHaveBeenCalledWith(
expect.objectContaining({
initialText: 'test-key',
@@ -113,10 +111,9 @@ describe('ApiAuthDialog', () => {
'calls $expectedCall.name when $keyName is pressed',
async ({ keyName, sequence, expectedCall, args }) => {
mockBuffer.text = 'submitted-key'; // Set for the onSubmit case
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
// calls[0] is the ApiAuthDialog's useKeypress (Ctrl+C handler)
// calls[1] is the TextInput's useKeypress (typing handler)
const keypressHandler = mockedUseKeypress.mock.calls[1][0];
@@ -136,24 +133,22 @@ describe('ApiAuthDialog', () => {
);
it('displays an error message', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Invalid API Key');
unmount();
});
it('calls clearApiKey and clears buffer when Ctrl+C is pressed', async () => {
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
// Call 0 is ApiAuthDialog (isActive: true)
// Call 1 is TextInput (isActive: true, priority: true)
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
diff --git a/packages/cli/src/ui/auth/AuthDialog.test.tsx b/packages/cli/src/ui/auth/AuthDialog.test.tsx
index 878b2a8ee0..4837a71490 100644
--- a/packages/cli/src/ui/auth/AuthDialog.test.tsx
+++ b/packages/cli/src/ui/auth/AuthDialog.test.tsx
@@ -143,10 +143,9 @@ describe('AuthDialog', () => {
for (const [key, value] of Object.entries(env)) {
vi.stubEnv(key, value as string);
}
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
for (const item of shouldContain) {
expect(items).toContainEqual(item);
@@ -161,10 +160,7 @@ describe('AuthDialog', () => {
it('filters auth types when enforcedType is set', async () => {
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
expect(items).toHaveLength(1);
expect(items[0].value).toBe(AuthType.USE_GEMINI);
@@ -173,10 +169,7 @@ describe('AuthDialog', () => {
it('sets initial index to 0 when enforcedType is set', async () => {
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { initialIndex } = mockedRadioButtonSelect.mock.calls[0][0];
expect(initialIndex).toBe(0);
unmount();
@@ -213,10 +206,7 @@ describe('AuthDialog', () => {
},
])('selects initial auth type $desc', async ({ setup, expected }) => {
setup();
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { items, initialIndex } = mockedRadioButtonSelect.mock.calls[0][0];
expect(items[initialIndex].value).toBe(expected);
unmount();
@@ -226,10 +216,7 @@ describe('AuthDialog', () => {
describe('handleAuthSelect', () => {
it('calls onAuthError if validation fails', async () => {
mockedValidateAuthMethod.mockReturnValue('Invalid method');
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
handleAuthSelect(AuthType.USE_GEMINI);
@@ -245,10 +232,7 @@ describe('AuthDialog', () => {
it('sets auth context with requiresRestart: true for LOGIN_WITH_GOOGLE', async () => {
mockedValidateAuthMethod.mockReturnValue(null);
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await handleAuthSelect(AuthType.LOGIN_WITH_GOOGLE);
@@ -261,10 +245,7 @@ describe('AuthDialog', () => {
it('sets auth context with empty object for other auth types', async () => {
mockedValidateAuthMethod.mockReturnValue(null);
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await handleAuthSelect(AuthType.USE_GEMINI);
@@ -278,10 +259,7 @@ describe('AuthDialog', () => {
vi.stubEnv('GEMINI_API_KEY', 'test-key-from-env');
// props.settings.merged.security.auth.selectedType is undefined here, simulating initial setup
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await handleAuthSelect(AuthType.USE_GEMINI);
@@ -297,10 +275,7 @@ describe('AuthDialog', () => {
vi.stubEnv('GEMINI_API_KEY', ''); // Empty string
// props.settings.merged.security.auth.selectedType is undefined here
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await handleAuthSelect(AuthType.USE_GEMINI);
@@ -316,10 +291,7 @@ describe('AuthDialog', () => {
// process.env['GEMINI_API_KEY'] is not set
// props.settings.merged.security.auth.selectedType is undefined here, simulating initial setup
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await handleAuthSelect(AuthType.USE_GEMINI);
@@ -337,10 +309,7 @@ describe('AuthDialog', () => {
props.settings.merged.security.auth.selectedType =
AuthType.LOGIN_WITH_GOOGLE;
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await handleAuthSelect(AuthType.USE_GEMINI);
@@ -360,10 +329,7 @@ describe('AuthDialog', () => {
vi.mocked(props.config.isBrowserLaunchSuppressed).mockReturnValue(true);
mockedValidateAuthMethod.mockReturnValue(null);
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const { onSelect: handleAuthSelect } =
mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => {
@@ -383,10 +349,9 @@ describe('AuthDialog', () => {
it('displays authError when provided', async () => {
props.authError = 'Something went wrong';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Something went wrong');
unmount();
});
@@ -429,10 +394,7 @@ describe('AuthDialog', () => {
},
])('$desc', async ({ setup, expectations }) => {
setup();
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
keypressHandler({ name: 'escape' });
expectations(props);
@@ -442,30 +404,27 @@ describe('AuthDialog', () => {
describe('Snapshots', () => {
it('renders correctly with default props', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with auth error', async () => {
props.authError = 'Something went wrong';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with enforced auth type', async () => {
props.settings.merged.security.auth.enforcedType = AuthType.USE_GEMINI;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/auth/AuthInProgress.test.tsx b/packages/cli/src/ui/auth/AuthInProgress.test.tsx
index bd6a3cb126..1c392be28d 100644
--- a/packages/cli/src/ui/auth/AuthInProgress.test.tsx
+++ b/packages/cli/src/ui/auth/AuthInProgress.test.tsx
@@ -55,20 +55,18 @@ describe('AuthInProgress', () => {
});
it('renders initial state with spinner', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- 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 } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = vi.mocked(useKeypress).mock.calls[0][0];
await act(async () => {
@@ -84,10 +82,9 @@ describe('AuthInProgress', () => {
});
it('calls onTimeout when Ctrl+C is pressed', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = vi.mocked(useKeypress).mock.calls[0][0];
await act(async () => {
@@ -100,10 +97,9 @@ describe('AuthInProgress', () => {
});
it('calls onTimeout and shows timeout message after 3 minutes', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
await act(async () => {
vi.advanceTimersByTime(180000);
@@ -116,10 +112,7 @@ describe('AuthInProgress', () => {
});
it('clears timer on unmount', async () => {
- const { waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await render();
await act(async () => {
unmount();
diff --git a/packages/cli/src/ui/auth/BannedAccountDialog.test.tsx b/packages/cli/src/ui/auth/BannedAccountDialog.test.tsx
index 0670c81bc9..4b5d44e6d5 100644
--- a/packages/cli/src/ui/auth/BannedAccountDialog.test.tsx
+++ b/packages/cli/src/ui/auth/BannedAccountDialog.test.tsx
@@ -73,14 +73,13 @@ describe('BannedAccountDialog', () => {
});
it('renders the suspension message from accountSuspensionInfo', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('Account Suspended');
expect(frame).toContain('violation of Terms of Service');
@@ -89,14 +88,13 @@ describe('BannedAccountDialog', () => {
});
it('renders menu options with appeal link text from response', async () => {
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
expect(items).toHaveLength(3);
expect(items[0].label).toBe('Appeal Here');
@@ -109,14 +107,13 @@ describe('BannedAccountDialog', () => {
const infoWithoutUrl: AccountSuspensionInfo = {
message: 'Account suspended.',
};
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
expect(items).toHaveLength(2);
expect(items[0].label).toBe('Change authentication');
@@ -129,28 +126,26 @@ describe('BannedAccountDialog', () => {
message: 'Account suspended.',
appealUrl: 'https://example.com/appeal',
};
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
expect(items[0].label).toBe('Open the Google Form');
unmount();
});
it('opens browser when appeal option is selected', async () => {
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await onSelect('open_form');
expect(mockedOpenBrowser).toHaveBeenCalledWith(
@@ -162,14 +157,13 @@ describe('BannedAccountDialog', () => {
it('shows URL when browser cannot be launched', async () => {
mockedShouldLaunchBrowser.mockReturnValue(false);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
onSelect('open_form');
await waitFor(() => {
@@ -180,14 +174,13 @@ describe('BannedAccountDialog', () => {
});
it('calls onExit when "Exit" is selected', async () => {
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await onSelect('exit');
expect(mockedRunExitCleanup).toHaveBeenCalled();
@@ -196,14 +189,13 @@ describe('BannedAccountDialog', () => {
});
it('calls onChangeAuth when "Change authentication" is selected', async () => {
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
onSelect('change_auth');
expect(onChangeAuth).toHaveBeenCalled();
@@ -212,14 +204,13 @@ describe('BannedAccountDialog', () => {
});
it('exits on escape key', async () => {
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
const result = keypressHandler({ name: 'escape' });
expect(result).toBe(true);
@@ -227,14 +218,13 @@ describe('BannedAccountDialog', () => {
});
it('renders snapshot correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/auth/LoginWithGoogleRestartDialog.test.tsx b/packages/cli/src/ui/auth/LoginWithGoogleRestartDialog.test.tsx
index 77310e3069..4dd13a3334 100644
--- a/packages/cli/src/ui/auth/LoginWithGoogleRestartDialog.test.tsx
+++ b/packages/cli/src/ui/auth/LoginWithGoogleRestartDialog.test.tsx
@@ -45,25 +45,23 @@ describe('LoginWithGoogleRestartDialog', () => {
});
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('calls onDismiss when escape is pressed', async () => {
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
keypressHandler({
@@ -83,13 +81,12 @@ describe('LoginWithGoogleRestartDialog', () => {
async (keyName) => {
vi.useFakeTimers();
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
keypressHandler({
diff --git a/packages/cli/src/ui/auth/useAuth.test.tsx b/packages/cli/src/ui/auth/useAuth.test.tsx
index f236428ff1..8d51e46a64 100644
--- a/packages/cli/src/ui/auth/useAuth.test.tsx
+++ b/packages/cli/src/ui/auth/useAuth.test.tsx
@@ -4,15 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import {
- describe,
- it,
- expect,
- vi,
- beforeEach,
- afterEach,
- type Mock,
-} from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { act } from 'react';
import { renderHook } from '../../test-utils/render.js';
import { useAuthCommand, validateAuthMethodWithSettings } from './useAuth.js';
import {
@@ -22,7 +15,6 @@ import {
} from '@google/gemini-cli-core';
import { AuthState } from '../types.js';
import type { LoadedSettings } from '../../config/settings.js';
-import { waitFor } from '../../test-utils/async.js';
// Mock dependencies
const mockLoadApiKey = vi.fn();
@@ -142,171 +134,202 @@ describe('useAuth', () => {
},
}) as LoadedSettings;
+ let deferredRefreshAuth: {
+ resolve: () => void;
+ reject: (e: Error) => void;
+ };
+
+ beforeEach(() => {
+ vi.mocked(mockConfig.refreshAuth).mockImplementation(
+ () =>
+ new Promise((resolve, reject) => {
+ deferredRefreshAuth = { resolve, reject };
+ }),
+ );
+ });
+
it('should initialize with Unauthenticated state', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.LOGIN_WITH_GOOGLE), mockConfig),
);
+ // Because we defer refreshAuth, the initial state is safely caught here
expect(result.current.authState).toBe(AuthState.Unauthenticated);
- await waitFor(() => {
- expect(result.current.authState).toBe(AuthState.Authenticated);
+ await act(async () => {
+ deferredRefreshAuth.resolve();
});
+
+ expect(result.current.authState).toBe(AuthState.Authenticated);
});
it('should set error if no auth type is selected and no env key', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(undefined), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authError).toBe(
- 'No authentication method selected.',
- );
- expect(result.current.authState).toBe(AuthState.Updating);
- });
+ // This happens synchronously, no deferred promise
+ expect(result.current.authError).toBe(
+ 'No authentication method selected.',
+ );
+ expect(result.current.authState).toBe(AuthState.Updating);
});
it('should set error if no auth type is selected but env key exists', async () => {
process.env['GEMINI_API_KEY'] = 'env-key';
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(undefined), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authError).toContain(
- 'Existing API key detected (GEMINI_API_KEY)',
- );
- expect(result.current.authState).toBe(AuthState.Updating);
- });
+ expect(result.current.authError).toContain(
+ 'Existing API key detected (GEMINI_API_KEY)',
+ );
+ expect(result.current.authState).toBe(AuthState.Updating);
});
it('should transition to AwaitingApiKeyInput if USE_GEMINI and no key found', async () => {
- mockLoadApiKey.mockResolvedValue(null);
- const { result } = renderHook(() =>
+ let deferredLoadKey: { resolve: (k: string | null) => void };
+ mockLoadApiKey.mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredLoadKey = { resolve };
+ }),
+ );
+
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.USE_GEMINI), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authState).toBe(AuthState.AwaitingApiKeyInput);
+ await act(async () => {
+ deferredLoadKey.resolve(null);
});
+
+ expect(result.current.authState).toBe(AuthState.AwaitingApiKeyInput);
});
it('should authenticate if USE_GEMINI and key is found', async () => {
- mockLoadApiKey.mockResolvedValue('stored-key');
- const { result } = renderHook(() =>
+ let deferredLoadKey: { resolve: (k: string | null) => void };
+ mockLoadApiKey.mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredLoadKey = { resolve };
+ }),
+ );
+
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.USE_GEMINI), mockConfig),
);
- await waitFor(() => {
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
- AuthType.USE_GEMINI,
- );
- expect(result.current.authState).toBe(AuthState.Authenticated);
- expect(result.current.apiKeyDefaultValue).toBe('stored-key');
+ await act(async () => {
+ deferredLoadKey.resolve('stored-key');
});
+
+ await act(async () => {
+ deferredRefreshAuth.resolve();
+ });
+
+ expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.USE_GEMINI);
+ expect(result.current.authState).toBe(AuthState.Authenticated);
+ expect(result.current.apiKeyDefaultValue).toBe('stored-key');
});
it('should authenticate if USE_GEMINI and env key is found', async () => {
- mockLoadApiKey.mockResolvedValue(null);
process.env['GEMINI_API_KEY'] = 'env-key';
- const { result } = renderHook(() =>
+
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.USE_GEMINI), mockConfig),
);
- await waitFor(() => {
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
- AuthType.USE_GEMINI,
- );
- expect(result.current.authState).toBe(AuthState.Authenticated);
- expect(result.current.apiKeyDefaultValue).toBe('env-key');
+ await act(async () => {
+ deferredRefreshAuth.resolve();
});
+
+ expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.USE_GEMINI);
+ expect(result.current.authState).toBe(AuthState.Authenticated);
+ expect(result.current.apiKeyDefaultValue).toBe('env-key');
});
it('should prioritize env key over stored key when both are present', async () => {
- mockLoadApiKey.mockResolvedValue('stored-key');
process.env['GEMINI_API_KEY'] = 'env-key';
- const { result } = renderHook(() =>
+
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.USE_GEMINI), mockConfig),
);
- await waitFor(() => {
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
- AuthType.USE_GEMINI,
- );
- expect(result.current.authState).toBe(AuthState.Authenticated);
- // The environment key should take precedence
- expect(result.current.apiKeyDefaultValue).toBe('env-key');
+ await act(async () => {
+ deferredRefreshAuth.resolve();
});
+
+ expect(mockConfig.refreshAuth).toHaveBeenCalledWith(AuthType.USE_GEMINI);
+ expect(result.current.authState).toBe(AuthState.Authenticated);
+ expect(result.current.apiKeyDefaultValue).toBe('env-key');
});
it('should set error if validation fails', async () => {
mockValidateAuthMethod.mockReturnValue('Validation Failed');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.LOGIN_WITH_GOOGLE), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authError).toBe('Validation Failed');
- expect(result.current.authState).toBe(AuthState.Updating);
- });
+ expect(result.current.authError).toBe('Validation Failed');
+ expect(result.current.authState).toBe(AuthState.Updating);
});
it('should set error if GEMINI_DEFAULT_AUTH_TYPE is invalid', async () => {
process.env['GEMINI_DEFAULT_AUTH_TYPE'] = 'INVALID_TYPE';
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.LOGIN_WITH_GOOGLE), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authError).toContain(
- 'Invalid value for GEMINI_DEFAULT_AUTH_TYPE',
- );
- expect(result.current.authState).toBe(AuthState.Updating);
- });
+ expect(result.current.authError).toContain(
+ 'Invalid value for GEMINI_DEFAULT_AUTH_TYPE',
+ );
+ expect(result.current.authState).toBe(AuthState.Updating);
});
it('should authenticate successfully for valid auth type', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.LOGIN_WITH_GOOGLE), mockConfig),
);
- await waitFor(() => {
- expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
- AuthType.LOGIN_WITH_GOOGLE,
- );
- expect(result.current.authState).toBe(AuthState.Authenticated);
- expect(result.current.authError).toBeNull();
+ await act(async () => {
+ deferredRefreshAuth.resolve();
});
+
+ expect(mockConfig.refreshAuth).toHaveBeenCalledWith(
+ AuthType.LOGIN_WITH_GOOGLE,
+ );
+ expect(result.current.authState).toBe(AuthState.Authenticated);
+ expect(result.current.authError).toBeNull();
});
it('should handle refreshAuth failure', async () => {
- (mockConfig.refreshAuth as Mock).mockRejectedValue(
- new Error('Auth Failed'),
- );
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.LOGIN_WITH_GOOGLE), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authError).toContain('Failed to sign in');
- expect(result.current.authState).toBe(AuthState.Updating);
+ await act(async () => {
+ deferredRefreshAuth.reject(new Error('Auth Failed'));
});
+
+ expect(result.current.authError).toContain('Failed to sign in');
+ expect(result.current.authState).toBe(AuthState.Updating);
});
it('should handle ProjectIdRequiredError without "Failed to login" prefix', async () => {
const projectIdError = new ProjectIdRequiredError();
- (mockConfig.refreshAuth as Mock).mockRejectedValue(projectIdError);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useAuthCommand(createSettings(AuthType.LOGIN_WITH_GOOGLE), mockConfig),
);
- await waitFor(() => {
- expect(result.current.authError).toBe(
- 'This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID env var. See https://goo.gle/gemini-cli-auth-docs#workspace-gca',
- );
- expect(result.current.authError).not.toContain('Failed to login');
- expect(result.current.authState).toBe(AuthState.Updating);
+ await act(async () => {
+ deferredRefreshAuth.reject(projectIdError);
});
+
+ expect(result.current.authError).toBe(
+ 'This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID env var. See https://goo.gle/gemini-cli-auth-docs#workspace-gca',
+ );
+ expect(result.current.authError).not.toContain('Failed to login');
+ expect(result.current.authState).toBe(AuthState.Updating);
});
});
});
diff --git a/packages/cli/src/ui/components/AboutBox.test.tsx b/packages/cli/src/ui/components/AboutBox.test.tsx
index 1db36b1f60..9115ca31c1 100644
--- a/packages/cli/src/ui/components/AboutBox.test.tsx
+++ b/packages/cli/src/ui/components/AboutBox.test.tsx
@@ -25,10 +25,9 @@ describe('AboutBox', () => {
};
it('renders with required props', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('About Gemini CLI');
expect(output).toContain('1.0.0');
@@ -46,10 +45,9 @@ describe('AboutBox', () => {
['tier', 'Enterprise', 'Tier'],
])('renders optional prop %s', async (prop, value, label) => {
const props = { ...defaultProps, [prop]: value };
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain(label);
expect(output).toContain(value);
@@ -58,10 +56,9 @@ describe('AboutBox', () => {
it('renders Auth Method with email when userEmail is provided', async () => {
const props = { ...defaultProps, userEmail: 'test@example.com' };
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Signed in with Google (test@example.com)');
unmount();
@@ -69,10 +66,9 @@ describe('AboutBox', () => {
it('renders Auth Method correctly when not oauth', async () => {
const props = { ...defaultProps, selectedAuthType: 'api-key' };
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('api-key');
unmount();
diff --git a/packages/cli/src/ui/components/AdminSettingsChangedDialog.test.tsx b/packages/cli/src/ui/components/AdminSettingsChangedDialog.test.tsx
index 19db058b87..76a36fe4dc 100644
--- a/packages/cli/src/ui/components/AdminSettingsChangedDialog.test.tsx
+++ b/packages/cli/src/ui/components/AdminSettingsChangedDialog.test.tsx
@@ -17,15 +17,14 @@ describe('AdminSettingsChangedDialog', () => {
});
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('restarts on "r" key press', async () => {
- const { stdin, waitUntilReady } = await renderWithProviders(
+ const { stdin } = await renderWithProviders(
,
{
uiActions: {
@@ -33,7 +32,6 @@ describe('AdminSettingsChangedDialog', () => {
},
},
);
- await waitUntilReady();
act(() => {
stdin.write('r');
@@ -43,7 +41,7 @@ describe('AdminSettingsChangedDialog', () => {
});
it.each(['r', 'R'])('restarts on "%s" key press', async (key) => {
- const { stdin, waitUntilReady } = await renderWithProviders(
+ const { stdin } = await renderWithProviders(
,
{
uiActions: {
@@ -51,7 +49,6 @@ describe('AdminSettingsChangedDialog', () => {
},
},
);
- await waitUntilReady();
act(() => {
stdin.write(key);
diff --git a/packages/cli/src/ui/components/AgentConfigDialog.test.tsx b/packages/cli/src/ui/components/AgentConfigDialog.test.tsx
index a2bfe052bb..2c6ea454db 100644
--- a/packages/cli/src/ui/components/AgentConfigDialog.test.tsx
+++ b/packages/cli/src/ui/components/AgentConfigDialog.test.tsx
@@ -126,7 +126,6 @@ describe('AgentConfigDialog', () => {
/>,
{ settings, uiState: { mainAreaWidth: 100 } },
);
- await result.waitUntilReady();
return result;
};
diff --git a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx
index da71895485..571e0d36d3 100644
--- a/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx
+++ b/packages/cli/src/ui/components/AlternateBufferQuittingDisplay.test.tsx
@@ -108,7 +108,7 @@ describe('AlternateBufferQuittingDisplay', () => {
it('renders with active and pending tool messages', async () => {
persistentStateMock.setData({ tipsShown: 0 });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -118,14 +118,13 @@ describe('AlternateBufferQuittingDisplay', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('with_history_and_pending');
unmount();
});
it('renders with empty history and no pending items', async () => {
persistentStateMock.setData({ tipsShown: 0 });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -135,14 +134,13 @@ describe('AlternateBufferQuittingDisplay', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('empty');
unmount();
});
it('renders with history but no pending items', async () => {
persistentStateMock.setData({ tipsShown: 0 });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -152,14 +150,13 @@ describe('AlternateBufferQuittingDisplay', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('with_history_no_pending');
unmount();
});
it('renders with pending items but no history', async () => {
persistentStateMock.setData({ tipsShown: 0 });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -169,7 +166,6 @@ describe('AlternateBufferQuittingDisplay', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('with_pending_no_history');
unmount();
});
@@ -195,7 +191,7 @@ describe('AlternateBufferQuittingDisplay', () => {
],
},
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -205,7 +201,6 @@ describe('AlternateBufferQuittingDisplay', () => {
},
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Action Required (was prompted):');
expect(output).toContain('confirming_tool');
@@ -220,7 +215,7 @@ describe('AlternateBufferQuittingDisplay', () => {
{ id: 1, type: 'user', text: 'Hello Gemini' },
{ id: 2, type: 'gemini', text: 'Hello User!' },
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -230,7 +225,6 @@ describe('AlternateBufferQuittingDisplay', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('with_user_gemini_messages');
unmount();
});
diff --git a/packages/cli/src/ui/components/AnsiOutput.test.tsx b/packages/cli/src/ui/components/AnsiOutput.test.tsx
index ac824fefe6..758361be0a 100644
--- a/packages/cli/src/ui/components/AnsiOutput.test.tsx
+++ b/packages/cli/src/ui/components/AnsiOutput.test.tsx
@@ -29,10 +29,9 @@ describe('', () => {
createAnsiToken({ text: 'world!' }),
],
];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().trim()).toBe('Hello, world!');
unmount();
});
@@ -47,10 +46,9 @@ describe('', () => {
{ style: { inverse: true }, text: 'Inverse' },
])('correctly applies style $text', async ({ style, text }) => {
const data: AnsiOutput = [[createAnsiToken({ text, ...style })]];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().trim()).toBe(text);
unmount();
});
@@ -61,10 +59,9 @@ describe('', () => {
{ color: { fg: '#00ff00', bg: '#ff00ff' }, text: 'Green FG Magenta BG' },
])('correctly applies color $text', async ({ color, text }) => {
const data: AnsiOutput = [[createAnsiToken({ text, ...color })]];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().trim()).toBe(text);
unmount();
});
@@ -76,10 +73,9 @@ describe('', () => {
[createAnsiToken({ text: 'Third line' })],
[createAnsiToken({ text: '' })],
];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toBeDefined();
const lines = output.split('\n');
@@ -96,10 +92,9 @@ describe('', () => {
[createAnsiToken({ text: 'Line 3' })],
[createAnsiToken({ text: 'Line 4' })],
];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Line 1');
expect(output).not.toContain('Line 2');
@@ -115,10 +110,9 @@ describe('', () => {
[createAnsiToken({ text: 'Line 3' })],
[createAnsiToken({ text: 'Line 4' })],
];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Line 1');
expect(output).not.toContain('Line 2');
@@ -135,7 +129,7 @@ describe('', () => {
[createAnsiToken({ text: 'Line 4' })],
];
// availableTerminalHeight=3, maxLines=2 => show 2 lines
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
width={80}
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Line 2');
expect(output).toContain('Line 3');
@@ -156,10 +149,9 @@ describe('', () => {
for (let i = 0; i < 1000; i++) {
largeData.push([createAnsiToken({ text: `Line ${i}` })]);
}
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
// We are just checking that it renders something without crashing.
expect(lastFrame()).toBeDefined();
unmount();
diff --git a/packages/cli/src/ui/components/AppHeader.test.tsx b/packages/cli/src/ui/components/AppHeader.test.tsx
index 0d7e2b3a7b..8ff4caaacf 100644
--- a/packages/cli/src/ui/components/AppHeader.test.tsx
+++ b/packages/cli/src/ui/components/AppHeader.test.tsx
@@ -27,13 +27,12 @@ describe('', () => {
bannerVisible: true,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('This is the default banner');
expect(lastFrame()).toMatchSnapshot();
@@ -50,13 +49,12 @@ describe('', () => {
bannerVisible: true,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('There are capacity issues');
expect(lastFrame()).toMatchSnapshot();
@@ -72,13 +70,12 @@ describe('', () => {
},
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('Banner');
expect(lastFrame()).toMatchSnapshot();
@@ -103,13 +100,12 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('This is the default banner');
expect(lastFrame()).toMatchSnapshot();
@@ -129,13 +125,12 @@ describe('', () => {
// and interfering with the expected persistentState.set call.
persistentStateMock.setData({ tipsShown: 10 });
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(persistentStateMock.set).toHaveBeenCalledWith(
'defaultBannerShownCount',
@@ -159,13 +154,12 @@ describe('', () => {
bannerVisible: true,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('First line\\nSecond line');
unmount();
@@ -183,13 +177,12 @@ describe('', () => {
persistentStateMock.setData({ tipsShown: 5 });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('Tips');
expect(persistentStateMock.set).toHaveBeenCalledWith('tipsShown', 6);
@@ -206,13 +199,12 @@ describe('', () => {
persistentStateMock.setData({ tipsShown: 10 });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
},
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('Tips');
unmount();
@@ -234,7 +226,6 @@ describe('', () => {
const session1 = await renderWithProviders(, {
uiState,
});
- await session1.waitUntilReady();
expect(session1.lastFrame()).toContain('Tips');
expect(persistentStateMock.get('tipsShown')).toBe(10);
@@ -245,7 +236,6 @@ describe('', () => {
,
{},
);
- await session2.waitUntilReady();
expect(session2.lastFrame()).not.toContain('Tips');
session2.unmount();
diff --git a/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx b/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx
index 4386891c7a..1b2decbe16 100644
--- a/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx
+++ b/packages/cli/src/ui/components/ApprovalModeIndicator.test.tsx
@@ -11,56 +11,50 @@ import { ApprovalMode } from '@google/gemini-cli-core';
describe('ApprovalModeIndicator', () => {
it('renders correctly for AUTO_EDIT mode', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly for AUTO_EDIT mode with plan enabled', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly for PLAN mode', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly for YOLO mode', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly for DEFAULT mode', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly for DEFAULT mode with plan enabled', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/AskUserDialog.test.tsx b/packages/cli/src/ui/components/AskUserDialog.test.tsx
index 8ed240389c..864800a061 100644
--- a/packages/cli/src/ui/components/AskUserDialog.test.tsx
+++ b/packages/cli/src/ui/components/AskUserDialog.test.tsx
@@ -48,7 +48,7 @@ describe('AskUserDialog', () => {
];
it('renders question and options', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -397,7 +396,7 @@ describe('AskUserDialog', () => {
},
];
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('hides progress header for single question', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('shows keyboard hints', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -471,7 +467,6 @@ describe('AskUserDialog', () => {
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toContain('Which testing framework?');
writeKey(stdin, '\x1b[C'); // Right arrow
@@ -582,7 +577,7 @@ describe('AskUserDialog', () => {
},
];
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -736,7 +730,7 @@ describe('AskUserDialog', () => {
},
];
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -759,7 +752,7 @@ describe('AskUserDialog', () => {
},
];
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -820,7 +812,7 @@ describe('AskUserDialog', () => {
},
];
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
{ width: 120 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
diff --git a/packages/cli/src/ui/components/BackgroundShellDisplay.test.tsx b/packages/cli/src/ui/components/BackgroundShellDisplay.test.tsx
index 847dcd9a87..c097028a0d 100644
--- a/packages/cli/src/ui/components/BackgroundShellDisplay.test.tsx
+++ b/packages/cli/src/ui/components/BackgroundShellDisplay.test.tsx
@@ -145,7 +145,7 @@ describe('', () => {
it('renders the output of the active shell', async () => {
const width = 80;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -166,7 +165,7 @@ describe('', () => {
it('renders tabs for multiple shells', async () => {
const width = 100;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -187,7 +185,7 @@ describe('', () => {
it('highlights the focused state', async () => {
const width = 80;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -208,7 +205,7 @@ describe('', () => {
it('resizes the PTY on mount and when dimensions change', async () => {
const width = 80;
- const { rerender, waitUntilReady, unmount } = render(
+ const { rerender, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(ShellExecutionService.resizePty).toHaveBeenCalledWith(
shell1.pid,
@@ -241,7 +237,6 @@ describe('', () => {
/>
,
);
- await waitUntilReady();
expect(ShellExecutionService.resizePty).toHaveBeenCalledWith(
shell1.pid,
@@ -253,7 +248,7 @@ describe('', () => {
it('renders the process list when isListOpenProp is true', async () => {
const width = 80;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -274,7 +268,7 @@ describe('', () => {
it('selects the current process and closes the list when Ctrl+L is pressed in list view', async () => {
const width = 80;
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
// Simulate down arrow to select the second process (handled by RadioButtonSelect)
await act(async () => {
simulateKey({ name: 'down' });
});
- await waitUntilReady();
// Simulate Ctrl+L (handled by BackgroundShellDisplay)
await act(async () => {
simulateKey({ name: 'l', ctrl: true });
});
- await waitUntilReady();
expect(mockSetActiveBackgroundShellPid).toHaveBeenCalledWith(shell2.pid);
expect(mockSetIsBackgroundShellListOpen).toHaveBeenCalledWith(false);
@@ -308,7 +299,7 @@ describe('', () => {
it('kills the highlighted process when Ctrl+K is pressed in list view', async () => {
const width = 80;
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
// Initial state: shell1 (active) is highlighted
@@ -329,13 +319,11 @@ describe('', () => {
await act(async () => {
simulateKey({ name: 'down' });
});
- await waitUntilReady();
// Press Ctrl+K
await act(async () => {
simulateKey({ name: 'k', ctrl: true });
});
- await waitUntilReady();
expect(mockDismissBackgroundShell).toHaveBeenCalledWith(shell2.pid);
unmount();
@@ -343,7 +331,7 @@ describe('', () => {
it('kills the active process when Ctrl+K is pressed in output view', async () => {
const width = 80;
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
await act(async () => {
simulateKey({ name: 'k', ctrl: true });
});
- await waitUntilReady();
expect(mockDismissBackgroundShell).toHaveBeenCalledWith(shell1.pid);
unmount();
@@ -370,7 +356,7 @@ describe('', () => {
it('scrolls to active shell when list opens', async () => {
// shell2 is active
const width = 80;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -402,7 +387,7 @@ describe('', () => {
mockShells.set(exitedShell.pid, exitedShell);
const width = 80;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
width,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
diff --git a/packages/cli/src/ui/components/Checklist.test.tsx b/packages/cli/src/ui/components/Checklist.test.tsx
index 442ee0400f..329a560aec 100644
--- a/packages/cli/src/ui/components/Checklist.test.tsx
+++ b/packages/cli/src/ui/components/Checklist.test.tsx
@@ -18,10 +18,9 @@ describe('', () => {
];
it('renders nothing when list is empty', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
});
@@ -30,15 +29,14 @@ describe('', () => {
{ status: 'completed', label: 'Task 1' },
{ status: 'cancelled', label: 'Task 2' },
];
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
});
it('renders summary view correctly (collapsed)', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
', () => {
toggleHint="toggle me"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders expanded view correctly', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
', () => {
toggleHint="toggle me"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -68,10 +64,9 @@ describe('', () => {
{ status: 'completed', label: 'Task 1' },
{ status: 'pending', label: 'Task 2' },
];
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/ChecklistItem.test.tsx b/packages/cli/src/ui/components/ChecklistItem.test.tsx
index 4176f7914b..c71af523e1 100644
--- a/packages/cli/src/ui/components/ChecklistItem.test.tsx
+++ b/packages/cli/src/ui/components/ChecklistItem.test.tsx
@@ -17,8 +17,7 @@ describe('', () => {
{ status: 'cancelled', label: 'Skipped this' },
{ status: 'blocked', label: 'Blocked this' },
] as ChecklistItemData[])('renders %s item correctly', async (item) => {
- const { lastFrame, waitUntilReady } = render();
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
@@ -28,12 +27,11 @@ describe('', () => {
label:
'This is a very long text that should be truncated because the wrap prop is set to truncate',
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -43,12 +41,11 @@ describe('', () => {
label:
'This is a very long text that should wrap because the default behavior is wrapping',
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/CliSpinner.test.tsx b/packages/cli/src/ui/components/CliSpinner.test.tsx
index cca997f370..4da6abb199 100644
--- a/packages/cli/src/ui/components/CliSpinner.test.tsx
+++ b/packages/cli/src/ui/components/CliSpinner.test.tsx
@@ -17,10 +17,7 @@ describe('', () => {
it('should increment debugNumAnimatedComponents on mount and decrement on unmount', async () => {
expect(debugState.debugNumAnimatedComponents).toBe(0);
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders();
expect(debugState.debugNumAnimatedComponents).toBe(1);
unmount();
expect(debugState.debugNumAnimatedComponents).toBe(0);
@@ -28,11 +25,9 @@ describe('', () => {
it('should not render when showSpinner is false', async () => {
const settings = createMockSettings({ ui: { showSpinner: false } });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- { settings },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ settings,
+ });
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
diff --git a/packages/cli/src/ui/components/ColorsDisplay.test.tsx b/packages/cli/src/ui/components/ColorsDisplay.test.tsx
index fdd08fd653..d934831c0e 100644
--- a/packages/cli/src/ui/components/ColorsDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ColorsDisplay.test.tsx
@@ -96,10 +96,9 @@ describe('ColorsDisplay', () => {
it('renders correctly', async () => {
const mockTheme = themeManager.getActiveTheme();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
// Check for title and description
diff --git a/packages/cli/src/ui/components/Composer.test.tsx b/packages/cli/src/ui/components/Composer.test.tsx
index 641fc24810..8df5f690e7 100644
--- a/packages/cli/src/ui/components/Composer.test.tsx
+++ b/packages/cli/src/ui/components/Composer.test.tsx
@@ -251,7 +251,7 @@ const renderComposer = async (
config = createMockConfig(),
uiActions = createMockUIActions(),
) => {
- const result = render(
+ const result = await render(
@@ -262,7 +262,6 @@ const renderComposer = async (
,
);
- await result.waitUntilReady();
// Wait for shortcuts hint debounce if using fake timers
if (vi.isFakeTimers()) {
diff --git a/packages/cli/src/ui/components/ConfigInitDisplay.test.tsx b/packages/cli/src/ui/components/ConfigInitDisplay.test.tsx
index 45ead4862e..b4ae8b93b1 100644
--- a/packages/cli/src/ui/components/ConfigInitDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ConfigInitDisplay.test.tsx
@@ -43,10 +43,7 @@ describe('ConfigInitDisplay', () => {
});
it('renders initial state', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { lastFrame } = await renderWithProviders();
expect(lastFrame()).toMatchSnapshot();
});
diff --git a/packages/cli/src/ui/components/ConsentPrompt.test.tsx b/packages/cli/src/ui/components/ConsentPrompt.test.tsx
index dd69c44dd5..09a2dde16e 100644
--- a/packages/cli/src/ui/components/ConsentPrompt.test.tsx
+++ b/packages/cli/src/ui/components/ConsentPrompt.test.tsx
@@ -33,14 +33,13 @@ describe('ConsentPrompt', () => {
it('renders a string prompt with MarkdownDisplay', async () => {
const prompt = 'Are you sure?';
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
expect(MockedMarkdownDisplay).toHaveBeenCalledWith(
{
@@ -55,14 +54,13 @@ describe('ConsentPrompt', () => {
it('renders a ReactNode prompt directly', async () => {
const prompt = Are you sure?;
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(MockedMarkdownDisplay).not.toHaveBeenCalled();
expect(lastFrame()).toContain('Are you sure?');
@@ -71,14 +69,13 @@ describe('ConsentPrompt', () => {
it('calls onConfirm with true when "Yes" is selected', async () => {
const prompt = 'Are you sure?';
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = MockedRadioButtonSelect.mock.calls[0][0].onSelect;
await act(async () => {
@@ -92,14 +89,13 @@ describe('ConsentPrompt', () => {
it('calls onConfirm with false when "No" is selected', async () => {
const prompt = 'Are you sure?';
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = MockedRadioButtonSelect.mock.calls[0][0].onSelect;
await act(async () => {
@@ -113,14 +109,13 @@ describe('ConsentPrompt', () => {
it('passes correct items to RadioButtonSelect', async () => {
const prompt = 'Are you sure?';
- const { waitUntilReady, unmount } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
expect(MockedRadioButtonSelect).toHaveBeenCalledWith(
expect.objectContaining({
diff --git a/packages/cli/src/ui/components/ConsoleSummaryDisplay.test.tsx b/packages/cli/src/ui/components/ConsoleSummaryDisplay.test.tsx
index cb8db1a895..b7662c3a26 100644
--- a/packages/cli/src/ui/components/ConsoleSummaryDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ConsoleSummaryDisplay.test.tsx
@@ -10,10 +10,9 @@ import { describe, it, expect } from 'vitest';
describe('ConsoleSummaryDisplay', () => {
it('renders nothing when errorCount is 0', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -22,10 +21,9 @@ describe('ConsoleSummaryDisplay', () => {
[1, '1 error'],
[5, '5 errors'],
])('renders correct message for %i errors', async (count, expectedText) => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain(expectedText);
expect(output).toContain('✖');
diff --git a/packages/cli/src/ui/components/ContextSummaryDisplay.test.tsx b/packages/cli/src/ui/components/ContextSummaryDisplay.test.tsx
index f48cfb2a31..1049e97912 100644
--- a/packages/cli/src/ui/components/ContextSummaryDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ContextSummaryDisplay.test.tsx
@@ -26,8 +26,7 @@ const renderWithWidth = async (
props: React.ComponentProps,
) => {
useTerminalSizeMock.mockReturnValue({ columns: width, rows: 24 });
- const result = render();
- await result.waitUntilReady();
+ const result = await render();
return result;
};
diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx
index 904e06635c..d8ec1650ee 100644
--- a/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ContextUsageDisplay.test.tsx
@@ -19,35 +19,33 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
describe('ContextUsageDisplay', () => {
it('renders correct percentage used', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('50% used');
unmount();
});
it('renders correctly when usage is 0%', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('0% used');
unmount();
});
it('renders abbreviated label when terminal width is small', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
{ width: 80 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('20%');
expect(output).not.toContain('context used');
@@ -63,28 +60,26 @@ describe('ContextUsageDisplay', () => {
});
it('renders 80% correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('80% used');
unmount();
});
it('renders 100% when full', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('100% used');
unmount();
diff --git a/packages/cli/src/ui/components/CopyModeWarning.test.tsx b/packages/cli/src/ui/components/CopyModeWarning.test.tsx
index 6f202ced4a..cc20a142dd 100644
--- a/packages/cli/src/ui/components/CopyModeWarning.test.tsx
+++ b/packages/cli/src/ui/components/CopyModeWarning.test.tsx
@@ -22,8 +22,7 @@ describe('CopyModeWarning', () => {
mockUseUIState.mockReturnValue({
copyModeEnabled: false,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -32,8 +31,7 @@ describe('CopyModeWarning', () => {
mockUseUIState.mockReturnValue({
copyModeEnabled: true,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('In Copy Mode');
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
expect(lastFrame()).toContain('Press Ctrl+S or any other key to exit');
diff --git a/packages/cli/src/ui/components/DebugProfiler.test.tsx b/packages/cli/src/ui/components/DebugProfiler.test.tsx
index d4c0e28902..a014c740f0 100644
--- a/packages/cli/src/ui/components/DebugProfiler.test.tsx
+++ b/packages/cli/src/ui/components/DebugProfiler.test.tsx
@@ -242,8 +242,7 @@ describe('DebugProfiler Component', () => {
showDebugProfiler: false,
constrainHeight: false,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -257,8 +256,7 @@ describe('DebugProfiler Component', () => {
profiler.totalIdleFrames = 5;
profiler.totalFlickerFrames = 2;
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
const output = lastFrame();
expect(output).toContain('Renders: 10 (total)');
@@ -275,8 +273,7 @@ describe('DebugProfiler Component', () => {
const reportActionSpy = vi.spyOn(profiler, 'reportAction');
- const { waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { waitUntilReady, unmount } = await render();
await act(async () => {
coreEvents.emitModelChanged('new-model');
@@ -295,8 +292,7 @@ describe('DebugProfiler Component', () => {
const reportActionSpy = vi.spyOn(profiler, 'reportAction');
- const { waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { waitUntilReady, unmount } = await render();
await act(async () => {
appEvents.emit(AppEvent.SelectionWarning);
diff --git a/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx b/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx
index b2f4185842..30f98a6eda 100644
--- a/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx
+++ b/packages/cli/src/ui/components/DetailedMessagesDisplay.test.tsx
@@ -41,13 +41,12 @@ describe('DetailedMessagesDisplay', () => {
});
});
it('renders nothing when messages are empty', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -64,13 +63,12 @@ describe('DetailedMessagesDisplay', () => {
clearConsoleMessages: vi.fn(),
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -86,13 +84,12 @@ describe('DetailedMessagesDisplay', () => {
clearConsoleMessages: vi.fn(),
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { errorVerbosity: 'low' } }),
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('(F12 to close)');
unmount();
});
@@ -106,13 +103,12 @@ describe('DetailedMessagesDisplay', () => {
clearConsoleMessages: vi.fn(),
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('(F12 to close)');
unmount();
});
@@ -126,13 +122,12 @@ describe('DetailedMessagesDisplay', () => {
clearConsoleMessages: vi.fn(),
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/DialogManager.test.tsx b/packages/cli/src/ui/components/DialogManager.test.tsx
index 6f6dbb0289..31b28f5223 100644
--- a/packages/cli/src/ui/components/DialogManager.test.tsx
+++ b/packages/cli/src/ui/components/DialogManager.test.tsx
@@ -104,11 +104,10 @@ describe('DialogManager', () => {
};
it('renders nothing by default', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ uiState: baseUiState as Partial as UIState },
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -197,7 +196,7 @@ describe('DialogManager', () => {
it.each(testCases)(
'renders %s when state is %o',
async (uiStateOverride, expectedComponent) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: {
@@ -206,7 +205,6 @@ describe('DialogManager', () => {
} as Partial as UIState,
},
);
- await waitUntilReady();
expect(lastFrame()).toContain(expectedComponent);
unmount();
},
diff --git a/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx b/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx
index bd995652b1..18b47def7b 100644
--- a/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx
+++ b/packages/cli/src/ui/components/EditorSettingsDialog.test.tsx
@@ -55,27 +55,25 @@ describe('EditorSettingsDialog', () => {
renderWithProviders(ui);
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProvider(
+ const { lastFrame } = await renderWithProvider(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('calls onSelect when an editor is selected', async () => {
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady } = await renderWithProvider(
+ const { lastFrame } = await renderWithProvider(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('VS Code');
});
@@ -88,7 +86,6 @@ describe('EditorSettingsDialog', () => {
onExit={vi.fn()}
/>,
);
- await waitUntilReady();
// Initial focus on editor
expect(lastFrame()).toContain('> Select Editor');
@@ -134,7 +131,6 @@ describe('EditorSettingsDialog', () => {
onExit={onExit}
/>,
);
- await waitUntilReady();
await act(async () => {
stdin.write('\u001B'); // Escape
@@ -162,14 +158,13 @@ describe('EditorSettingsDialog', () => {
},
} as unknown as LoadedSettings;
- const { lastFrame, waitUntilReady } = await renderWithProvider(
+ const { lastFrame } = await renderWithProvider(
,
);
- await waitUntilReady();
const frame = lastFrame() || '';
if (!frame.includes('(Also modified')) {
diff --git a/packages/cli/src/ui/components/EmptyWalletDialog.test.tsx b/packages/cli/src/ui/components/EmptyWalletDialog.test.tsx
index 23a2038b10..74de1a8a41 100644
--- a/packages/cli/src/ui/components/EmptyWalletDialog.test.tsx
+++ b/packages/cli/src/ui/components/EmptyWalletDialog.test.tsx
@@ -30,7 +30,7 @@ describe('EmptyWalletDialog', () => {
describe('rendering', () => {
it('should match snapshot with fallback available', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
onChoice={mockOnChoice}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should match snapshot without fallback', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should display the model name and usage limit message', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame() ?? '';
expect(output).toContain('gemini-2.5-pro');
@@ -73,13 +70,12 @@ describe('EmptyWalletDialog', () => {
});
it('should display purchase prompt and credits update notice', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame() ?? '';
expect(output).toContain('purchase more AI Credits');
@@ -90,14 +86,13 @@ describe('EmptyWalletDialog', () => {
});
it('should display reset time when provided', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame() ?? '';
expect(output).toContain('3:45 PM');
@@ -106,13 +101,12 @@ describe('EmptyWalletDialog', () => {
});
it('should not display reset time when not provided', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame() ?? '';
expect(output).not.toContain('Access resets at');
@@ -120,13 +114,12 @@ describe('EmptyWalletDialog', () => {
});
it('should display slash command hints', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame() ?? '';
expect(output).toContain('/stats');
@@ -139,14 +132,13 @@ describe('EmptyWalletDialog', () => {
describe('onChoice handling', () => {
it('should call onGetCredits and onChoice when get_credits is selected', async () => {
// get_credits is the first item, so just press Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
writeKey(stdin, '\r');
@@ -158,13 +150,12 @@ describe('EmptyWalletDialog', () => {
});
it('should call onChoice without onGetCredits when onGetCredits is not provided', async () => {
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
writeKey(stdin, '\r');
@@ -177,14 +168,13 @@ describe('EmptyWalletDialog', () => {
it('should call onChoice with use_fallback when selected', async () => {
// With fallback: items are [get_credits, use_fallback, stop]
// use_fallback is the second item: Down + Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
@@ -198,13 +188,12 @@ describe('EmptyWalletDialog', () => {
it('should call onChoice with stop when selected', async () => {
// Without fallback: items are [get_credits, stop]
// stop is the second item: Down + Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
diff --git a/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx b/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx
index f369e7ff8e..d6fc23dd70 100644
--- a/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx
+++ b/packages/cli/src/ui/components/ExitPlanModeDialog.test.tsx
@@ -440,36 +440,38 @@ Implement a comprehensive authentication system with multiple providers.
return <>{children}>;
};
- const { stdin, lastFrame } = await renderWithProviders(
-
-
- ,
- {
- config: {
- getTargetDir: () => mockTargetDir,
- getIdeMode: () => false,
- isTrustedFolder: () => true,
- storage: {
- getPlansDir: () => mockPlansDir,
- },
- getFileSystemService: (): FileSystemService => ({
- readTextFile: vi.fn(),
- writeTextFile: vi.fn(),
+ const { stdin, lastFrame } = await act(async () =>
+ renderWithProviders(
+
+
+ ,
+ {
+ config: {
+ getTargetDir: () => mockTargetDir,
+ getIdeMode: () => false,
+ isTrustedFolder: () => true,
+ storage: {
+ getPlansDir: () => mockPlansDir,
+ },
+ getFileSystemService: (): FileSystemService => ({
+ readTextFile: vi.fn(),
+ writeTextFile: vi.fn(),
+ }),
+ getUseAlternateBuffer: () => useAlternateBuffer ?? true,
+ } as unknown as import('@google/gemini-cli-core').Config,
+ settings: createMockSettings({
+ ui: { useAlternateBuffer: useAlternateBuffer ?? true },
}),
- getUseAlternateBuffer: () => useAlternateBuffer ?? true,
- } as unknown as import('@google/gemini-cli-core').Config,
- settings: createMockSettings({
- ui: { useAlternateBuffer: useAlternateBuffer ?? true },
- }),
- },
+ },
+ ),
);
await act(async () => {
diff --git a/packages/cli/src/ui/components/ExitWarning.test.tsx b/packages/cli/src/ui/components/ExitWarning.test.tsx
index 6d495a5e21..a504670d03 100644
--- a/packages/cli/src/ui/components/ExitWarning.test.tsx
+++ b/packages/cli/src/ui/components/ExitWarning.test.tsx
@@ -24,8 +24,7 @@ describe('ExitWarning', () => {
ctrlCPressedOnce: false,
ctrlDPressedOnce: false,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -36,8 +35,7 @@ describe('ExitWarning', () => {
ctrlCPressedOnce: true,
ctrlDPressedOnce: false,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('Press Ctrl+C again to exit');
unmount();
});
@@ -48,8 +46,7 @@ describe('ExitWarning', () => {
ctrlCPressedOnce: false,
ctrlDPressedOnce: true,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('Press Ctrl+D again to exit');
unmount();
});
@@ -60,8 +57,7 @@ describe('ExitWarning', () => {
ctrlCPressedOnce: true,
ctrlDPressedOnce: true,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
diff --git a/packages/cli/src/ui/components/FolderTrustDialog.test.tsx b/packages/cli/src/ui/components/FolderTrustDialog.test.tsx
index c1d04b3ff9..de6e8096ec 100644
--- a/packages/cli/src/ui/components/FolderTrustDialog.test.tsx
+++ b/packages/cli/src/ui/components/FolderTrustDialog.test.tsx
@@ -48,10 +48,9 @@ describe('FolderTrustDialog', () => {
});
it('should render the dialog with title and description', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Do you trust the files in this folder?');
expect(lastFrame()).toContain(
@@ -72,7 +71,7 @@ describe('FolderTrustDialog', () => {
discoveryErrors: [],
securityWarnings: [],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('This folder contains:');
expect(lastFrame()).toContain('hidden');
unmount();
@@ -103,7 +101,7 @@ describe('FolderTrustDialog', () => {
discoveryErrors: [],
securityWarnings: [],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
},
);
- await waitUntilReady();
// With maxHeight=4, the intro text (4 lines) will take most of the space.
// The discovery results will likely be hidden.
expect(lastFrame()).toContain('hidden');
@@ -135,7 +132,7 @@ describe('FolderTrustDialog', () => {
discoveryErrors: [],
securityWarnings: [],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('hidden');
unmount();
});
@@ -182,9 +178,7 @@ describe('FolderTrustDialog', () => {
// Initial state: truncated
await waitFor(() => {
expect(lastFrame()).toContain('Do you trust the files in this folder?');
- // In standard terminal mode, the expansion hint is handled globally by ToastDisplay
- // via AppContainer, so it should not be present in the dialog's local frame.
- expect(lastFrame()).not.toContain('Press Ctrl+O');
+ expect(lastFrame()).toContain('Press Ctrl+O');
expect(lastFrame()).toContain('hidden');
});
@@ -221,7 +215,6 @@ describe('FolderTrustDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
await act(async () => {
stdin.write('\u001b[27u'); // Press kitty escape key
@@ -246,10 +239,9 @@ describe('FolderTrustDialog', () => {
});
it('should display restart message when isRestarting is true', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Gemini CLI is restarting');
unmount();
@@ -260,10 +252,9 @@ describe('FolderTrustDialog', () => {
const relaunchApp = vi
.spyOn(processUtils, 'relaunchApp')
.mockResolvedValue(undefined);
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await vi.advanceTimersByTimeAsync(250);
expect(relaunchApp).toHaveBeenCalled();
unmount();
@@ -275,10 +266,9 @@ describe('FolderTrustDialog', () => {
const relaunchApp = vi
.spyOn(processUtils, 'relaunchApp')
.mockResolvedValue(undefined);
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
// Unmount immediately (before 250ms)
unmount();
@@ -292,7 +282,6 @@ describe('FolderTrustDialog', () => {
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await act(async () => {
stdin.write('r');
@@ -308,30 +297,27 @@ describe('FolderTrustDialog', () => {
describe('directory display', () => {
it('should correctly display the folder name for a nested directory', async () => {
mockedCwd.mockReturnValue('/home/user/project');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Trust folder (project)');
unmount();
});
it('should correctly display the parent folder name for a nested directory', async () => {
mockedCwd.mockReturnValue('/home/user/project');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Trust parent folder (user)');
unmount();
});
it('should correctly display an empty parent folder name for a directory directly under root', async () => {
mockedCwd.mockReturnValue('/project');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Trust parent folder ()');
unmount();
});
@@ -348,7 +334,7 @@ describe('FolderTrustDialog', () => {
discoveryErrors: [],
securityWarnings: [],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
{ width: 80 },
);
- await waitUntilReady();
expect(lastFrame()).toContain('This folder contains:');
expect(lastFrame()).toContain('• Commands (2):');
expect(lastFrame()).toContain('- cmd1');
@@ -386,14 +371,13 @@ describe('FolderTrustDialog', () => {
discoveryErrors: [],
securityWarnings: ['Dangerous setting detected!'],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Security Warnings:');
expect(lastFrame()).toContain('Dangerous setting detected!');
unmount();
@@ -410,14 +394,13 @@ describe('FolderTrustDialog', () => {
discoveryErrors: ['Failed to load custom commands'],
securityWarnings: [],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Discovery Errors:');
expect(lastFrame()).toContain('Failed to load custom commands');
unmount();
@@ -434,7 +417,7 @@ describe('FolderTrustDialog', () => {
discoveryErrors: [],
securityWarnings: [],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
},
);
- await waitUntilReady();
// In alternate buffer + expanded, the title should be visible (StickyHeader)
expect(lastFrame()).toContain('Do you trust the files in this folder?');
// And it should NOT use MaxSizedBox truncation
@@ -470,7 +452,7 @@ describe('FolderTrustDialog', () => {
securityWarnings: [`${ansiRed}warning-with-ansi${ansiReset}`],
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
{ width: 100, uiState: { terminalHeight: 40 } },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('cmd-with-ansi');
diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx
index 39f20e1c86..c0a52af868 100644
--- a/packages/cli/src/ui/components/Footer.test.tsx
+++ b/packages/cli/src/ui/components/Footer.test.tsx
@@ -138,33 +138,25 @@ describe('', () => {
});
it('renders the component', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- branchName: defaultProps.branchName,
- sessionStats: mockSessionStats,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ branchName: defaultProps.branchName,
+ sessionStats: mockSessionStats,
},
- );
- await waitUntilReady();
+ });
expect(lastFrame()).toBeDefined();
unmount();
});
describe('path display', () => {
it('should display a shortened path on a narrow terminal', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 79,
- uiState: { sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 79,
+ uiState: { sessionStats: mockSessionStats },
+ });
const output = lastFrame();
expect(output).toBeDefined();
// Should contain some part of the path, likely shortened
@@ -173,15 +165,11 @@ describe('', () => {
});
it('should use wide layout at 80 columns', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 80,
- uiState: { sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 80,
+ uiState: { sessionStats: mockSessionStats },
+ });
const output = lastFrame();
expect(output).toBeDefined();
expect(output).toContain(path.join('make', 'it'));
@@ -189,28 +177,24 @@ describe('', () => {
});
it('should not truncate high-priority items on narrow terminals (regression)', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 60,
- uiState: {
- sessionStats: mockSessionStats,
- },
- settings: createMockSettings({
- general: {
- vimMode: true,
- },
- ui: {
- footer: {
- showLabels: true,
- items: ['workspace', 'model-name'],
- },
- },
- }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 60,
+ uiState: {
+ sessionStats: mockSessionStats,
},
- );
- await waitUntilReady();
+ settings: createMockSettings({
+ general: {
+ vimMode: true,
+ },
+ ui: {
+ footer: {
+ showLabels: true,
+ items: ['workspace', 'model-name'],
+ },
+ },
+ }),
+ });
const output = lastFrame();
// [INSERT] is high priority and should be fully visible
// (Note: VimModeProvider defaults to 'INSERT' mode when enabled)
@@ -222,168 +206,140 @@ describe('', () => {
});
it('displays the branch name when provided', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- branchName: defaultProps.branchName,
- sessionStats: mockSessionStats,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ branchName: defaultProps.branchName,
+ sessionStats: mockSessionStats,
},
- );
- await waitUntilReady();
+ });
expect(lastFrame()).toContain(defaultProps.branchName);
unmount();
});
it('does not display the branch name when not provided', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { branchName: undefined, sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { branchName: undefined, sessionStats: mockSessionStats },
+ });
expect(lastFrame()).not.toContain('Branch');
unmount();
});
it('displays the model name and context percentage', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- currentModel: defaultProps.model,
- sessionStats: {
- ...mockSessionStats,
- lastPromptTokenCount: 1000,
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ currentModel: defaultProps.model,
+ sessionStats: {
+ ...mockSessionStats,
+ lastPromptTokenCount: 1000,
+ },
+ },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideContextPercentage: false,
},
},
- settings: createMockSettings({
- ui: {
- footer: {
- hideContextPercentage: false,
- },
- },
- }),
- },
- );
- await waitUntilReady();
+ }),
+ });
expect(lastFrame()).toContain(defaultProps.model);
expect(lastFrame()).toMatch(/\d+% used/);
unmount();
});
it('displays the usage indicator when usage is low', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- quota: {
- userTier: undefined,
- stats: {
- remaining: 15,
- limit: 100,
- resetTime: undefined,
- },
- proQuotaRequest: null,
- validationRequest: null,
- overageMenuRequest: null,
- emptyWalletRequest: null,
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ quota: {
+ userTier: undefined,
+ stats: {
+ remaining: 15,
+ limit: 100,
+ resetTime: undefined,
},
+ proQuotaRequest: null,
+ validationRequest: null,
+ overageMenuRequest: null,
+ emptyWalletRequest: null,
},
},
- );
- await waitUntilReady();
+ });
expect(lastFrame()).toContain('85%');
expect(normalizeFrame(lastFrame())).toMatchSnapshot();
unmount();
});
it('hides the usage indicator when usage is not near limit', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- quota: {
- userTier: undefined,
- stats: {
- remaining: 85,
- limit: 100,
- resetTime: undefined,
- },
- proQuotaRequest: null,
- validationRequest: null,
- overageMenuRequest: null,
- emptyWalletRequest: null,
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ quota: {
+ userTier: undefined,
+ stats: {
+ remaining: 85,
+ limit: 100,
+ resetTime: undefined,
},
+ proQuotaRequest: null,
+ validationRequest: null,
+ overageMenuRequest: null,
+ emptyWalletRequest: null,
},
},
- );
- await waitUntilReady();
+ });
expect(normalizeFrame(lastFrame())).not.toContain('used');
expect(normalizeFrame(lastFrame())).toMatchSnapshot();
unmount();
});
it('displays "Limit reached" message when remaining is 0', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- quota: {
- userTier: undefined,
- stats: {
- remaining: 0,
- limit: 100,
- resetTime: undefined,
- },
- proQuotaRequest: null,
- validationRequest: null,
- overageMenuRequest: null,
- emptyWalletRequest: null,
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ quota: {
+ userTier: undefined,
+ stats: {
+ remaining: 0,
+ limit: 100,
+ resetTime: undefined,
},
+ proQuotaRequest: null,
+ validationRequest: null,
+ overageMenuRequest: null,
+ emptyWalletRequest: null,
},
},
- );
- await waitUntilReady();
+ });
expect(lastFrame()?.toLowerCase()).toContain('limit reached');
expect(normalizeFrame(lastFrame())).toMatchSnapshot();
unmount();
});
it('displays the model name and abbreviated context used label on narrow terminals', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 99,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideContextPercentage: false,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 99,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideContextPercentage: false,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(lastFrame()).toContain(defaultProps.model);
expect(lastFrame()).toMatch(/\d+%/);
expect(lastFrame()).not.toContain('context used');
@@ -392,33 +348,25 @@ describe('', () => {
describe('sandbox and trust info', () => {
it('should display untrusted when isTrustedFolder is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { isTrustedFolder: false, sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { isTrustedFolder: false, sessionStats: mockSessionStats },
+ });
expect(lastFrame()).toContain('untrusted');
unmount();
});
it('should display custom sandbox info when SANDBOX env is set', async () => {
vi.stubEnv('SANDBOX', 'gemini-cli-test-sandbox');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- isTrustedFolder: undefined,
- sessionStats: mockSessionStats,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ isTrustedFolder: undefined,
+ sessionStats: mockSessionStats,
},
- );
- await waitUntilReady();
+ });
expect(lastFrame()).toContain('test');
vi.unstubAllEnvs();
unmount();
@@ -427,15 +375,11 @@ describe('', () => {
it('should display macOS Seatbelt info when SANDBOX is sandbox-exec', async () => {
vi.stubEnv('SANDBOX', 'sandbox-exec');
vi.stubEnv('SEATBELT_PROFILE', 'test-profile');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { isTrustedFolder: true, sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { isTrustedFolder: true, sessionStats: mockSessionStats },
+ });
expect(lastFrame()).toMatch(/macOS Seatbelt.*\(test-profile\)/s);
vi.unstubAllEnvs();
unmount();
@@ -444,15 +388,11 @@ describe('', () => {
it('should display "no sandbox" when SANDBOX is not set and folder is trusted', async () => {
// Clear any SANDBOX env var that might be set.
vi.stubEnv('SANDBOX', '');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { isTrustedFolder: true, sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { isTrustedFolder: true, sessionStats: mockSessionStats },
+ });
expect(lastFrame()).toContain('no sandbox');
vi.unstubAllEnvs();
unmount();
@@ -460,15 +400,11 @@ describe('', () => {
it('should prioritize untrusted message over sandbox info', async () => {
vi.stubEnv('SANDBOX', 'gemini-cli-test-sandbox');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { isTrustedFolder: false, sessionStats: mockSessionStats },
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { isTrustedFolder: false, sessionStats: mockSessionStats },
+ });
expect(lastFrame()).toContain('untrusted');
expect(lastFrame()).not.toMatch(/test-sandbox/s);
vi.unstubAllEnvs();
@@ -478,22 +414,18 @@ describe('', () => {
describe('footer configuration filtering (golden snapshots)', () => {
it('renders complete footer with all sections visible (baseline)', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideContextPercentage: false,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideContextPercentage: false,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(normalizeFrame(lastFrame())).toMatchSnapshot(
'complete-footer-wide',
);
@@ -523,47 +455,39 @@ describe('', () => {
});
it('renders footer with only model info hidden (partial filtering)', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideCWD: false,
- hideSandboxStatus: false,
- hideModelInfo: true,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideCWD: false,
+ hideSandboxStatus: false,
+ hideModelInfo: true,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(normalizeFrame(lastFrame())).toMatchSnapshot('footer-no-model');
unmount();
});
it('renders footer with CWD and model info hidden to test alignment (only sandbox visible)', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideCWD: true,
- hideSandboxStatus: false,
- hideModelInfo: true,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideCWD: true,
+ hideSandboxStatus: false,
+ hideModelInfo: true,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(normalizeFrame(lastFrame())).toMatchSnapshot(
'footer-only-sandbox',
);
@@ -571,64 +495,52 @@ describe('', () => {
});
it('hides the context percentage when hideContextPercentage is true', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideContextPercentage: true,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideContextPercentage: true,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(lastFrame()).toContain(defaultProps.model);
expect(lastFrame()).not.toMatch(/\d+% used/);
unmount();
});
it('shows the context percentage when hideContextPercentage is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideContextPercentage: false,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideContextPercentage: false,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(lastFrame()).toContain(defaultProps.model);
expect(lastFrame()).toMatch(/\d+% used/);
unmount();
});
it('renders complete footer in narrow terminal (baseline narrow)', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 79,
- uiState: { sessionStats: mockSessionStats },
- settings: createMockSettings({
- ui: {
- footer: {
- hideContextPercentage: false,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 79,
+ uiState: { sessionStats: mockSessionStats },
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ hideContextPercentage: false,
},
- }),
- },
- );
- await waitUntilReady();
+ },
+ }),
+ });
expect(normalizeFrame(lastFrame())).toMatchSnapshot(
'complete-footer-narrow',
);
@@ -714,60 +626,48 @@ describe('', () => {
});
it('hides error summary in low verbosity mode out of dev mode', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- errorCount: 2,
- showErrorDetails: false,
- },
- settings: createMockSettings({ ui: { errorVerbosity: 'low' } }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ errorCount: 2,
+ showErrorDetails: false,
},
- );
- await waitUntilReady();
+ settings: createMockSettings({ ui: { errorVerbosity: 'low' } }),
+ });
expect(lastFrame()).not.toContain('F12 for details');
unmount();
});
it('shows error summary in low verbosity mode in dev mode', async () => {
mocks.isDevelopment = true;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- errorCount: 2,
- showErrorDetails: false,
- },
- settings: createMockSettings({ ui: { errorVerbosity: 'low' } }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ errorCount: 2,
+ showErrorDetails: false,
},
- );
- await waitUntilReady();
+ settings: createMockSettings({ ui: { errorVerbosity: 'low' } }),
+ });
expect(lastFrame()).toContain('F12 for details');
expect(lastFrame()).toContain('2 errors');
unmount();
});
it('shows error summary in full verbosity mode', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- errorCount: 2,
- showErrorDetails: false,
- },
- settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ errorCount: 2,
+ showErrorDetails: false,
},
- );
- await waitUntilReady();
+ settings: createMockSettings({ ui: { errorVerbosity: 'full' } }),
+ });
expect(lastFrame()).toContain('F12 for details');
expect(lastFrame()).toContain('2 errors');
unmount();
@@ -776,25 +676,21 @@ describe('', () => {
describe('Footer Custom Items', () => {
it('renders items in the specified order', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- currentModel: 'gemini-pro',
- sessionStats: mockSessionStats,
- },
- settings: createMockSettings({
- ui: {
- footer: {
- items: ['model-name', 'workspace'],
- },
- },
- }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ currentModel: 'gemini-pro',
+ sessionStats: mockSessionStats,
},
- );
- await waitUntilReady();
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ items: ['model-name', 'workspace'],
+ },
+ },
+ }),
+ });
const output = lastFrame();
const modelIdx = output.indexOf('/model');
@@ -804,28 +700,24 @@ describe('', () => {
});
it('renders multiple items with proper alignment', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- branchName: 'main',
- },
- settings: createMockSettings({
- vimMode: {
- vimMode: true,
- },
- ui: {
- footer: {
- items: ['workspace', 'git-branch', 'sandbox', 'model-name'],
- },
- },
- }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ branchName: 'main',
},
- );
- await waitUntilReady();
+ settings: createMockSettings({
+ vimMode: {
+ vimMode: true,
+ },
+ ui: {
+ footer: {
+ items: ['workspace', 'git-branch', 'sandbox', 'model-name'],
+ },
+ },
+ }),
+ });
const output = lastFrame();
expect(output).toBeDefined();
@@ -862,25 +754,21 @@ describe('', () => {
});
it('does not render items that are conditionally hidden', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- branchName: undefined, // No branch
- },
- settings: createMockSettings({
- ui: {
- footer: {
- items: ['workspace', 'git-branch', 'model-name'],
- },
- },
- }),
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ branchName: undefined, // No branch
},
- );
- await waitUntilReady();
+ settings: createMockSettings({
+ ui: {
+ footer: {
+ items: ['workspace', 'git-branch', 'model-name'],
+ },
+ },
+ }),
+ });
const output = lastFrame();
expect(output).toBeDefined();
@@ -893,18 +781,14 @@ describe('', () => {
describe('fallback mode display', () => {
it('should display Flash model when in fallback mode, not the configured Pro model', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- currentModel: 'gemini-2.5-flash', // Fallback active, showing Flash
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ currentModel: 'gemini-2.5-flash', // Fallback active, showing Flash
},
- );
- await waitUntilReady();
+ });
// Footer should show the effective model (Flash), not the config model (Pro)
expect(lastFrame()).toContain('gemini-2.5-flash');
@@ -913,18 +797,14 @@ describe('', () => {
});
it('should display Pro model when NOT in fallback mode', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- width: 120,
- uiState: {
- sessionStats: mockSessionStats,
- currentModel: 'gemini-2.5-pro', // Normal mode, showing Pro
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ config: mockConfig,
+ width: 120,
+ uiState: {
+ sessionStats: mockSessionStats,
+ currentModel: 'gemini-2.5-pro', // Normal mode, showing Pro
},
- );
- await waitUntilReady();
+ });
expect(lastFrame()).toContain('gemini-2.5-pro');
unmount();
diff --git a/packages/cli/src/ui/components/FooterConfigDialog.test.tsx b/packages/cli/src/ui/components/FooterConfigDialog.test.tsx
index d4dd74f189..12829cd99a 100644
--- a/packages/cli/src/ui/components/FooterConfigDialog.test.tsx
+++ b/packages/cli/src/ui/components/FooterConfigDialog.test.tsx
@@ -30,19 +30,17 @@ describe('', () => {
{ settings },
);
- await renderResult.waitUntilReady();
expect(renderResult.lastFrame()).toMatchSnapshot();
await expect(renderResult).toMatchSvgSnapshot();
});
it('toggles an item when enter is pressed', async () => {
const settings = createMockSettings();
- const { lastFrame, stdin, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, stdin } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
act(() => {
stdin.write('\r'); // Enter to toggle
});
@@ -62,12 +60,11 @@ describe('', () => {
it('reorders items with arrow keys', async () => {
const settings = createMockSettings();
- const { lastFrame, stdin, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, stdin } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
// Initial order: workspace, git-branch, ...
const output = lastFrame();
const cwdIdx = output.indexOf('] workspace');
@@ -93,12 +90,11 @@ describe('', () => {
it('closes on Esc', async () => {
const settings = createMockSettings();
- const { stdin, waitUntilReady } = await renderWithProviders(
+ const { stdin } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
act(() => {
stdin.write('\x1b'); // Esc
});
@@ -115,9 +111,8 @@ describe('', () => {
{ settings },
);
- const { lastFrame, stdin, waitUntilReady } = renderResult;
+ const { lastFrame, stdin } = renderResult;
- await waitUntilReady();
expect(lastFrame()).toContain('~/project/path');
// Move focus down to 'code-changes' (which has colored elements)
@@ -148,13 +143,11 @@ describe('', () => {
it('shows an empty preview when all items are deselected', async () => {
const settings = createMockSettings();
- const { lastFrame, stdin, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, stdin } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
-
// Default items are the first 5. We toggle them off.
for (let i = 0; i < 5; i++) {
act(() => {
@@ -178,11 +171,10 @@ describe('', () => {
it('moves item correctly after trying to move up at the top', async () => {
const settings = createMockSettings();
- const { lastFrame, stdin, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, stdin } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
// Default initial items in mock settings are 'git-branch', 'workspace', ...
await waitFor(() => {
@@ -222,8 +214,7 @@ describe('', () => {
{ settings },
);
- const { lastFrame, stdin, waitUntilReady } = renderResult;
- await waitUntilReady();
+ const { lastFrame, stdin } = renderResult;
// By default labels are on
expect(lastFrame()).toContain('workspace (/directory)');
diff --git a/packages/cli/src/ui/components/GeminiRespondingSpinner.test.tsx b/packages/cli/src/ui/components/GeminiRespondingSpinner.test.tsx
index a60f91cd80..e725ca3714 100644
--- a/packages/cli/src/ui/components/GeminiRespondingSpinner.test.tsx
+++ b/packages/cli/src/ui/components/GeminiRespondingSpinner.test.tsx
@@ -41,10 +41,7 @@ describe('GeminiRespondingSpinner', () => {
it('renders spinner when responding', async () => {
mockUseStreamingContext.mockReturnValue(StreamingState.Responding);
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('GeminiSpinner');
unmount();
});
@@ -52,30 +49,23 @@ describe('GeminiRespondingSpinner', () => {
it('renders screen reader text when responding and screen reader enabled', async () => {
mockUseStreamingContext.mockReturnValue(StreamingState.Responding);
mockUseIsScreenReaderEnabled.mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain(SCREEN_READER_RESPONDING);
unmount();
});
it('renders nothing when not responding and no non-responding display', async () => {
mockUseStreamingContext.mockReturnValue(StreamingState.Idle);
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('renders non-responding display when provided', async () => {
mockUseStreamingContext.mockReturnValue(StreamingState.Idle);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Waiting...');
unmount();
});
@@ -83,10 +73,9 @@ describe('GeminiRespondingSpinner', () => {
it('renders screen reader loading text when non-responding display provided and screen reader enabled', async () => {
mockUseStreamingContext.mockReturnValue(StreamingState.Idle);
mockUseIsScreenReaderEnabled.mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(SCREEN_READER_LOADING);
unmount();
});
diff --git a/packages/cli/src/ui/components/GradientRegression.test.tsx b/packages/cli/src/ui/components/GradientRegression.test.tsx
index 378aefdfcf..dfdad4f1aa 100644
--- a/packages/cli/src/ui/components/GradientRegression.test.tsx
+++ b/packages/cli/src/ui/components/GradientRegression.test.tsx
@@ -72,53 +72,46 @@ useSessionStatsMock.mockReturnValue({
describe('Gradient Crash Regression Tests', () => {
it(' should not crash when theme.ui.gradient is empty', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
width: 120,
},
);
- await waitUntilReady();
expect(lastFrame()).toBeDefined();
unmount();
});
it(' should not crash when theme.ui.gradient is empty', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{}} />,
{
width: 120,
},
);
- await waitUntilReady();
expect(lastFrame()).toBeDefined();
unmount();
});
it(' should not crash when theme.ui.gradient is empty', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
width: 120,
},
);
- await waitUntilReady();
expect(lastFrame()).toBeDefined();
unmount();
});
it(' should not crash when theme.ui.gradient has only one color (or empty) and nightly is true', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- width: 120,
- uiState: {
- nightly: true, // Enable nightly to trigger Gradient usage logic
- sessionStats: mockSessionStats,
- },
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ width: 120,
+ uiState: {
+ nightly: true, // Enable nightly to trigger Gradient usage logic
+ sessionStats: mockSessionStats,
},
- );
- await waitUntilReady();
+ });
// If it crashes, this line won't be reached or lastFrame() will throw
expect(lastFrame()).toBeDefined();
// It should fall back to rendering text without gradient
@@ -127,7 +120,7 @@ describe('Gradient Crash Regression Tests', () => {
});
it(' should not crash when theme.ui.gradient is empty', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
width: 120,
@@ -136,7 +129,6 @@ describe('Gradient Crash Regression Tests', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame()).toBeDefined();
// Ensure title is rendered
expect(lastFrame()).toContain('My Stats');
diff --git a/packages/cli/src/ui/components/Header.test.tsx b/packages/cli/src/ui/components/Header.test.tsx
index 46cdaf5ba0..dabd5ccb0b 100644
--- a/packages/cli/src/ui/components/Header.test.tsx
+++ b/packages/cli/src/ui/components/Header.test.tsx
@@ -39,12 +39,12 @@ describe('', () => {
vi.clearAllMocks();
});
- it('renders the long logo on a wide terminal', () => {
+ it('renders the long logo on a wide terminal', async () => {
vi.spyOn(useTerminalSize, 'useTerminalSize').mockReturnValue({
columns: 120,
rows: 20,
});
- render();
+ await render();
expect(Text).toHaveBeenCalledWith(
expect.objectContaining({
children: longAsciiLogo,
@@ -53,9 +53,9 @@ describe('', () => {
);
});
- it('renders custom ASCII art when provided', () => {
+ it('renders custom ASCII art when provided', async () => {
const customArt = 'CUSTOM ART';
- render(
+ await render(
,
);
expect(Text).toHaveBeenCalledWith(
@@ -66,8 +66,8 @@ describe('', () => {
);
});
- it('displays the version number when nightly is true', () => {
- render();
+ it('displays the version number when nightly is true', async () => {
+ await render();
const textCalls = (Text as Mock).mock.calls;
const versionText = Array.isArray(textCalls[1][0].children)
? textCalls[1][0].children.join('')
@@ -75,8 +75,8 @@ describe('', () => {
expect(versionText).toBe('v1.0.0');
});
- it('does not display the version number when nightly is false', () => {
- render();
+ it('does not display the version number when nightly is false', async () => {
+ await render();
expect(Text).not.toHaveBeenCalledWith(
expect.objectContaining({
children: 'v1.0.0',
@@ -119,7 +119,7 @@ describe('', () => {
},
});
const Gradient = await import('ink-gradient');
- render();
+ await render();
expect(Gradient.default).not.toHaveBeenCalled();
const textCalls = (Text as Mock).mock.calls;
expect(textCalls[0][0]).toHaveProperty('color', '#123456');
@@ -131,7 +131,7 @@ describe('', () => {
ui: { gradient: [singleColor] },
} as typeof semanticColors.theme);
const Gradient = await import('ink-gradient');
- render();
+ await render();
expect(Gradient.default).not.toHaveBeenCalled();
const textCalls = (Text as Mock).mock.calls;
expect(textCalls.length).toBe(1);
@@ -144,7 +144,7 @@ describe('', () => {
ui: { gradient: gradientColors },
} as typeof semanticColors.theme);
const Gradient = await import('ink-gradient');
- render();
+ await render();
expect(Gradient.default).toHaveBeenCalledWith(
expect.objectContaining({
colors: gradientColors,
diff --git a/packages/cli/src/ui/components/Help.test.tsx b/packages/cli/src/ui/components/Help.test.tsx
index dc86cb70dc..ed685f76c9 100644
--- a/packages/cli/src/ui/components/Help.test.tsx
+++ b/packages/cli/src/ui/components/Help.test.tsx
@@ -43,10 +43,9 @@ const mockCommands: readonly SlashCommand[] = [
describe('Help Component', () => {
it('should not render hidden commands', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('/test');
@@ -55,10 +54,9 @@ describe('Help Component', () => {
});
it('should not render hidden subcommands', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('visible-child');
@@ -67,10 +65,9 @@ describe('Help Component', () => {
});
it('should render keyboard shortcuts', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Keyboard Shortcuts:');
diff --git a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
index fa10340e09..ddbc30c022 100644
--- a/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
+++ b/packages/cli/src/ui/components/HistoryItemDisplay.test.tsx
@@ -39,10 +39,9 @@ describe('', () => {
type: MessageType.USER,
text: 'Hello',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Hello');
unmount();
});
@@ -53,10 +52,9 @@ describe('', () => {
type: 'hint',
text: 'Try using ripgrep first',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Try using ripgrep first');
unmount();
});
@@ -67,10 +65,9 @@ describe('', () => {
type: MessageType.USER,
text: '/theme',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('/theme');
unmount();
});
@@ -83,14 +80,13 @@ describe('', () => {
type: MessageType.INFO,
text: '⚡ Line 1\n⚡ Line 2\n⚡ Line 3',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: makeFakeConfig({ useAlternateBuffer }),
settings: createMockSettings({ ui: { useAlternateBuffer } }),
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
@@ -114,10 +110,9 @@ describe('', () => {
},
],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -128,12 +123,11 @@ describe('', () => {
type: MessageType.STATS,
duration: '1s',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Stats');
unmount();
});
@@ -150,10 +144,9 @@ describe('', () => {
gcpProject: 'test-project',
ideClient: 'test-ide',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('About Gemini CLI');
unmount();
});
@@ -163,12 +156,11 @@ describe('', () => {
...baseItem,
type: 'model_stats',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(
'No API calls have been made in this session.',
);
@@ -180,12 +172,11 @@ describe('', () => {
...baseItem,
type: 'tool_stats',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(
'No tool calls have been made in this session.',
);
@@ -198,12 +189,11 @@ describe('', () => {
type: 'quit',
duration: '1s',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Agent powering down. Goodbye!');
unmount();
});
@@ -215,14 +205,13 @@ describe('', () => {
text: 'Hello, \u001b[31mred\u001b[0m world!',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
// The ANSI codes should be escaped for display.
expect(lastFrame()).toContain('Hello, \\u001b[31mred\\u001b[0m world!');
@@ -253,14 +242,13 @@ describe('', () => {
],
};
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const passedProps = vi.mocked(ToolGroupMessage).mock.calls[0][0];
const confirmationDetails = passedProps.toolCalls[0]
@@ -279,13 +267,12 @@ describe('', () => {
type: 'thinking',
thought: { subject: 'Thinking', description: 'test' },
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { inlineThinkingMode: 'full' } }),
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -297,13 +284,12 @@ describe('', () => {
type: 'thinking',
thought: { subject: 'Thinking', description: 'test' },
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { inlineThinkingMode: 'full' } }),
},
);
- await waitUntilReady();
expect(lastFrame()).toContain(' Thinking...');
expect(lastFrame()).toMatchSnapshot();
@@ -315,13 +301,12 @@ describe('', () => {
type: 'thinking',
thought: { subject: 'Thinking', description: 'test' },
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings: createMockSettings({ ui: { inlineThinkingMode: 'off' } }),
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
@@ -343,21 +328,18 @@ describe('', () => {
type: 'gemini',
text: longCode,
};
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- {
- config: makeFakeConfig({ useAlternateBuffer }),
- settings: createMockSettings({ ui: { useAlternateBuffer } }),
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({ ui: { useAlternateBuffer } }),
+ },
+ );
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -368,22 +350,19 @@ describe('', () => {
type: 'gemini',
text: longCode,
};
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- {
- config: makeFakeConfig({ useAlternateBuffer }),
- settings: createMockSettings({ ui: { useAlternateBuffer } }),
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({ ui: { useAlternateBuffer } }),
+ },
+ );
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -394,21 +373,18 @@ describe('', () => {
type: 'gemini_content',
text: longCode,
};
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- {
- config: makeFakeConfig({ useAlternateBuffer }),
- settings: createMockSettings({ ui: { useAlternateBuffer } }),
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({ ui: { useAlternateBuffer } }),
+ },
+ );
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -419,22 +395,19 @@ describe('', () => {
type: 'gemini_content',
text: longCode,
};
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- {
- config: makeFakeConfig({ useAlternateBuffer }),
- settings: createMockSettings({ ui: { useAlternateBuffer } }),
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ {
+ config: makeFakeConfig({ useAlternateBuffer }),
+ settings: createMockSettings({ ui: { useAlternateBuffer } }),
+ },
+ );
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/components/HookStatusDisplay.test.tsx b/packages/cli/src/ui/components/HookStatusDisplay.test.tsx
index fbf9ccb555..54c824d76a 100644
--- a/packages/cli/src/ui/components/HookStatusDisplay.test.tsx
+++ b/packages/cli/src/ui/components/HookStatusDisplay.test.tsx
@@ -18,10 +18,9 @@ describe('', () => {
const props = {
activeHooks: [{ name: 'test-hook', eventName: 'BeforeAgent' }],
};
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -33,10 +32,9 @@ describe('', () => {
{ name: 'h2', eventName: 'BeforeAgent' },
],
};
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -47,20 +45,18 @@ describe('', () => {
{ name: 'step', eventName: 'BeforeAgent', index: 1, total: 3 },
],
};
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should return empty string if no active hooks', async () => {
const props = { activeHooks: [] };
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
diff --git a/packages/cli/src/ui/components/HooksDialog.test.tsx b/packages/cli/src/ui/components/HooksDialog.test.tsx
index 15acbe1c53..94b221892f 100644
--- a/packages/cli/src/ui/components/HooksDialog.test.tsx
+++ b/packages/cli/src/ui/components/HooksDialog.test.tsx
@@ -35,20 +35,18 @@ describe('HooksDialog', () => {
describe('snapshots', () => {
it('renders empty hooks dialog', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders single hook with security warning, source, and tips', async () => {
const hooks = [createMockHook('test-hook', 'before-tool', true)];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -59,10 +57,9 @@ describe('HooksDialog', () => {
createMockHook('hook2', 'before-tool', false),
createMockHook('hook3', 'after-agent', true),
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -80,10 +77,9 @@ describe('HooksDialog', () => {
},
}),
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -100,10 +96,9 @@ describe('HooksDialog', () => {
enabled: true,
},
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -112,10 +107,9 @@ describe('HooksDialog', () => {
describe('keyboard interaction', () => {
it('should call onClose when escape key is pressed', async () => {
const onClose = vi.fn();
- const { waitUntilReady, stdin, unmount } = await renderWithProviders(
+ const { stdin, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
act(() => {
stdin.write('\u001b[27u');
@@ -137,10 +131,9 @@ describe('HooksDialog', () => {
createMockHook('hook1', 'before-tool', true),
createMockHook('hook2', 'after-tool', false),
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('▲');
expect(lastFrame()).not.toContain('▼');
@@ -149,10 +142,9 @@ describe('HooksDialog', () => {
it('should show scroll down indicator when there are more hooks than maxVisibleHooks', async () => {
const hooks = createManyHooks(15);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('▼');
unmount();
@@ -164,7 +156,6 @@ describe('HooksDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
// Initially should not show up indicator
expect(lastFrame()).not.toContain('▲');
@@ -185,7 +176,6 @@ describe('HooksDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
// Scroll down twice
act(() => {
@@ -213,7 +203,6 @@ describe('HooksDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
// Scroll down many times past the end
act(() => {
@@ -236,7 +225,6 @@ describe('HooksDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
// Try to scroll up when already at top
act(() => {
diff --git a/packages/cli/src/ui/components/IdeTrustChangeDialog.test.tsx b/packages/cli/src/ui/components/IdeTrustChangeDialog.test.tsx
index cb1dbbe95a..d02675f9f0 100644
--- a/packages/cli/src/ui/components/IdeTrustChangeDialog.test.tsx
+++ b/packages/cli/src/ui/components/IdeTrustChangeDialog.test.tsx
@@ -17,10 +17,9 @@ describe('IdeTrustChangeDialog', () => {
});
it('renders the correct message for CONNECTION_CHANGE', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frameText = lastFrame();
expect(frameText).toContain(
@@ -31,10 +30,9 @@ describe('IdeTrustChangeDialog', () => {
});
it('renders the correct message for TRUST_CHANGE', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frameText = lastFrame();
expect(frameText).toContain(
@@ -48,10 +46,9 @@ describe('IdeTrustChangeDialog', () => {
const debugLoggerWarnSpy = vi
.spyOn(debugLogger, 'warn')
.mockImplementation(() => {});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frameText = lastFrame();
expect(frameText).toContain('Workspace trust has changed.');
@@ -68,7 +65,6 @@ describe('IdeTrustChangeDialog', () => {
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await act(async () => {
stdin.write('r');
@@ -86,7 +82,6 @@ describe('IdeTrustChangeDialog', () => {
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await act(async () => {
stdin.write('R');
@@ -104,7 +99,6 @@ describe('IdeTrustChangeDialog', () => {
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await act(async () => {
stdin.write('a');
diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
index 84b9b4a58b..5dc9aa543e 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
@@ -55,20 +55,18 @@ describe('', () => {
};
it('should render blank when streamingState is Idle and no loading phrase or thought', async () => {
- const { lastFrame, waitUntilReady } = await renderWithContext(
+ const { lastFrame } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })?.trim()).toBe('');
});
it('should render spinner, phrase, and time when streamingState is Responding', async () => {
- const { lastFrame, waitUntilReady } = await renderWithContext(
+ const { lastFrame } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('MockRespondingSpinner');
expect(output).toContain('Loading...');
@@ -80,11 +78,10 @@ describe('', () => {
currentLoadingPhrase: 'Confirm action',
elapsedTime: 10,
};
- const { lastFrame, waitUntilReady } = await renderWithContext(
+ const { lastFrame } = await renderWithContext(
,
StreamingState.WaitingForConfirmation,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('⠏'); // Static char for WaitingForConfirmation
expect(output).toContain('Confirm action');
@@ -97,11 +94,10 @@ describe('', () => {
currentLoadingPhrase: 'Processing data...',
elapsedTime: 3,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Processing data...');
unmount();
});
@@ -111,11 +107,10 @@ describe('', () => {
currentLoadingPhrase: 'Working...',
elapsedTime: 60,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
expect(lastFrame()).toContain('(esc to cancel, 1m)');
unmount();
});
@@ -125,22 +120,20 @@ describe('', () => {
currentLoadingPhrase: 'Working...',
elapsedTime: 125,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
expect(lastFrame()).toContain('(esc to cancel, 2m 5s)');
unmount();
});
it('should render rightContent when provided', async () => {
const rightContent = Extra Info;
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Extra Info');
unmount();
});
@@ -181,7 +174,6 @@ describe('', () => {
const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })?.trim()).toBe(''); // Initial: Idle (no loading phrase)
// Transition to Responding
@@ -232,11 +224,10 @@ describe('', () => {
currentLoadingPhrase: 'Loading...',
elapsedTime: 5,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Loading...');
unmount();
@@ -250,11 +241,10 @@ describe('', () => {
},
elapsedTime: 5,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toBeDefined();
if (output) {
@@ -274,11 +264,10 @@ describe('', () => {
},
elapsedTime: 5,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Thinking... Planning the response...');
unmount();
@@ -293,11 +282,10 @@ describe('', () => {
currentLoadingPhrase: 'This should not be displayed',
elapsedTime: 5,
};
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Thinking... ');
expect(output).toContain('This should be displayed');
@@ -306,20 +294,19 @@ describe('', () => {
});
it('should not display thought indicator for non-thought loading phrases', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('Thinking... ');
unmount();
});
it('should truncate long primary text instead of wrapping', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
StreamingState.Responding,
80,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -337,7 +323,7 @@ describe('', () => {
describe('responsive layout', () => {
it('should render on a single line on a wide terminal', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
Right}
@@ -345,7 +331,6 @@ describe('', () => {
StreamingState.Responding,
120,
);
- await waitUntilReady();
const output = lastFrame();
// Check for single line output
expect(output?.trim().includes('\n')).toBe(false);
@@ -356,7 +341,7 @@ describe('', () => {
});
it('should render on multiple lines on a narrow terminal', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
Right}
@@ -364,7 +349,6 @@ describe('', () => {
StreamingState.Responding,
79,
);
- await waitUntilReady();
const output = lastFrame();
const lines = output?.trim().split('\n');
// Expecting 3 lines:
@@ -382,23 +366,21 @@ describe('', () => {
});
it('should use wide layout at 80 columns', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
80,
);
- await waitUntilReady();
expect(lastFrame()?.trim().includes('\n')).toBe(false);
unmount();
});
it('should use narrow layout at 79 columns', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding,
79,
);
- await waitUntilReady();
expect(lastFrame()?.includes('\n')).toBe(true);
unmount();
});
diff --git a/packages/cli/src/ui/components/LogoutConfirmationDialog.test.tsx b/packages/cli/src/ui/components/LogoutConfirmationDialog.test.tsx
index 6436c5ed34..55a0ba16ef 100644
--- a/packages/cli/src/ui/components/LogoutConfirmationDialog.test.tsx
+++ b/packages/cli/src/ui/components/LogoutConfirmationDialog.test.tsx
@@ -23,10 +23,9 @@ describe('LogoutConfirmationDialog', () => {
});
it('should render the dialog with title, description, and hint', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('You are now signed out');
expect(lastFrame()).toContain(
@@ -37,10 +36,9 @@ describe('LogoutConfirmationDialog', () => {
});
it('should render RadioButtonSelect with Login and Exit options', async () => {
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(RadioButtonSelect).toHaveBeenCalled();
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
@@ -57,7 +55,6 @@ describe('LogoutConfirmationDialog', () => {
const { waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
await act(async () => {
@@ -74,7 +71,6 @@ describe('LogoutConfirmationDialog', () => {
const { waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const mockCall = vi.mocked(RadioButtonSelect).mock.calls[0][0];
await act(async () => {
@@ -91,7 +87,6 @@ describe('LogoutConfirmationDialog', () => {
const { stdin, waitUntilReady, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await act(async () => {
// Send kitty escape key sequence
diff --git a/packages/cli/src/ui/components/LoopDetectionConfirmation.test.tsx b/packages/cli/src/ui/components/LoopDetectionConfirmation.test.tsx
index 5eb7ec3011..28369f8aa0 100644
--- a/packages/cli/src/ui/components/LoopDetectionConfirmation.test.tsx
+++ b/packages/cli/src/ui/components/LoopDetectionConfirmation.test.tsx
@@ -12,21 +12,19 @@ describe('LoopDetectionConfirmation', () => {
const onComplete = vi.fn();
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width: 101 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('contains the expected options', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('A potential loop was detected');
diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx
index b2c18aa7d8..070b2c835c 100644
--- a/packages/cli/src/ui/components/MainContent.test.tsx
+++ b/packages/cli/src/ui/components/MainContent.test.tsx
@@ -364,14 +364,9 @@ describe('MainContent', () => {
it('renders in alternate buffer mode', async () => {
vi.mocked(useAlternateBuffer).mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: defaultMockUiState as Partial,
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: defaultMockUiState as Partial,
+ });
const output = lastFrame();
expect(output).toContain('AppHeader(full)');
expect(output).toContain('Hello');
@@ -452,14 +447,9 @@ describe('MainContent', () => {
it('does not constrain height in alternate buffer mode', async () => {
vi.mocked(useAlternateBuffer).mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: defaultMockUiState as Partial,
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: defaultMockUiState as Partial,
+ });
const output = lastFrame();
expect(output).toContain('AppHeader(full)');
expect(output).toContain('Hello');
@@ -479,16 +469,11 @@ describe('MainContent', () => {
staticAreaMaxItemHeight: 5,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: uiState as Partial,
- config: makeFakeConfig({ useAlternateBuffer: true }),
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
-
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: uiState as Partial,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -507,16 +492,11 @@ describe('MainContent', () => {
staticAreaMaxItemHeight: 5,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: uiState as unknown as Partial,
- config: makeFakeConfig({ useAlternateBuffer: true }),
- settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
- },
- );
-
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: uiState as unknown as Partial,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
+ });
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -564,14 +544,9 @@ describe('MainContent', () => {
],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- uiState: uiState as Partial,
- },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(, {
+ uiState: uiState as Partial,
+ });
const output = lastFrame();
// Verify Part 1 and Part 2 are rendered.
expect(output).toContain('Part 1');
@@ -629,7 +604,6 @@ describe('MainContent', () => {
const renderResult = await renderWithProviders(, {
uiState: uiState as Partial,
});
- await renderResult.waitUntilReady();
const output = renderResult.lastFrame();
expect(output).toContain('Initial analysis');
@@ -732,15 +706,16 @@ describe('MainContent', () => {
bannerVisible: false,
};
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(, {
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ {
uiState: uiState as Partial,
config: makeFakeConfig({ useAlternateBuffer: isAlternateBuffer }),
settings: createMockSettings({
ui: { useAlternateBuffer: isAlternateBuffer },
}),
- });
- await waitUntilReady();
+ },
+ );
const output = lastFrame();
diff --git a/packages/cli/src/ui/components/MemoryUsageDisplay.test.tsx b/packages/cli/src/ui/components/MemoryUsageDisplay.test.tsx
index 681b48d997..19435a4716 100644
--- a/packages/cli/src/ui/components/MemoryUsageDisplay.test.tsx
+++ b/packages/cli/src/ui/components/MemoryUsageDisplay.test.tsx
@@ -30,19 +30,15 @@ describe('MemoryUsageDisplay', () => {
});
it('renders memory usage', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('50.0 MB');
unmount();
});
it('updates memory usage over time', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('50.0 MB');
vi.mocked(process.memoryUsage).mockReturnValue({
diff --git a/packages/cli/src/ui/components/ModelDialog.test.tsx b/packages/cli/src/ui/components/ModelDialog.test.tsx
index 2f1fde86b9..b6921d1371 100644
--- a/packages/cli/src/ui/components/ModelDialog.test.tsx
+++ b/packages/cli/src/ui/components/ModelDialog.test.tsx
@@ -115,7 +115,6 @@ describe('', () => {
settings,
},
);
- await result.waitUntilReady();
return result;
};
diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx
index 5da3c3a6d2..f71eb72266 100644
--- a/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ModelStatsDisplay.test.tsx
@@ -59,11 +59,10 @@ const renderWithMockedStats = async (
},
} as unknown as LoadedSettings);
- const result = render(
+ const result = await render(
,
width,
);
- await result.waitUntilReady();
return result;
};
@@ -529,14 +528,13 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Auth Method:');
diff --git a/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx b/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx
index 83f7a96e2e..25d592b95d 100644
--- a/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx
+++ b/packages/cli/src/ui/components/MultiFolderTrustDialog.test.tsx
@@ -73,10 +73,9 @@ describe('MultiFolderTrustDialog', () => {
it('renders the dialog with the list of folders', async () => {
const folders = ['/path/to/folder1', '/path/to/folder2'];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(
'Do you trust the following folders being added to this workspace?',
@@ -88,10 +87,9 @@ describe('MultiFolderTrustDialog', () => {
it('calls onComplete and finishAddingDirectories with an error on escape', async () => {
const folders = ['/path/to/folder1'];
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressCallback = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -121,10 +119,9 @@ describe('MultiFolderTrustDialog', () => {
it('calls finishAddingDirectories with an error and does not add directories when "No" is chosen', async () => {
const folders = ['/path/to/folder1'];
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => {
@@ -148,14 +145,13 @@ describe('MultiFolderTrustDialog', () => {
it('adds directories to workspace context when "Yes" is chosen', async () => {
const folders = ['/path/to/folder1', '/path/to/folder2'];
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => {
@@ -182,10 +178,9 @@ describe('MultiFolderTrustDialog', () => {
it('adds directories to workspace context and remembers them as trusted when "Yes, and remember" is chosen', async () => {
const folders = ['/path/to/folder1'];
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => {
@@ -212,10 +207,9 @@ describe('MultiFolderTrustDialog', () => {
it('shows submitting message after a choice is made', async () => {
const folders = ['/path/to/folder1'];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
@@ -230,14 +224,13 @@ describe('MultiFolderTrustDialog', () => {
it('shows an error message and completes when config is missing', async () => {
const folders = ['/path/to/folder1'];
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => {
@@ -263,14 +256,13 @@ describe('MultiFolderTrustDialog', () => {
});
const folders = ['/path/to/good', '/path/to/error'];
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const { onSelect } = mockedRadioButtonSelect.mock.calls[0][0];
await act(async () => {
diff --git a/packages/cli/src/ui/components/NewAgentsNotification.test.tsx b/packages/cli/src/ui/components/NewAgentsNotification.test.tsx
index 99bd6c0539..93189e1e6f 100644
--- a/packages/cli/src/ui/components/NewAgentsNotification.test.tsx
+++ b/packages/cli/src/ui/components/NewAgentsNotification.test.tsx
@@ -49,10 +49,9 @@ describe('NewAgentsNotification', () => {
const onSelect = vi.fn();
it('renders agent list', async () => {
- const { lastFrame, waitUntilReady, unmount } = await render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toMatchSnapshot();
@@ -68,10 +67,9 @@ describe('NewAgentsNotification', () => {
inputConfig: { inputSchema: {} },
}));
- const { lastFrame, waitUntilReady, unmount } = await render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/Notifications.test.tsx b/packages/cli/src/ui/components/Notifications.test.tsx
index 7e1bde4039..cbca3c8ccd 100644
--- a/packages/cli/src/ui/components/Notifications.test.tsx
+++ b/packages/cli/src/ui/components/Notifications.test.tsx
@@ -111,14 +111,13 @@ describe('Notifications', () => {
});
it('renders nothing when no notifications', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
width: 100,
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -137,7 +136,7 @@ describe('Notifications', () => {
version: '1.0.0',
} as AppState;
mockUseAppContext.mockReturnValue(appState);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
appState,
@@ -145,7 +144,6 @@ describe('Notifications', () => {
width: 100,
},
);
- await waitUntilReady();
const output = lastFrame();
warnings.forEach((warning) => {
expect(output).toContain(warning.message);
@@ -163,15 +161,11 @@ describe('Notifications', () => {
} as AppState;
mockUseAppContext.mockReturnValue(appState);
- const { waitUntilReady, unmount } = await renderWithProviders(
- ,
- {
- appState,
- settings,
- width: 100,
- },
- );
- await waitUntilReady();
+ const { unmount } = await renderWithProviders(, {
+ appState,
+ settings,
+ width: 100,
+ });
expect(persistentStateMock.set).toHaveBeenCalledWith(
'startupWarningCounts',
@@ -199,7 +193,7 @@ describe('Notifications', () => {
startupWarningCounts: { 'low-1': 3 },
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
appState,
@@ -207,7 +201,6 @@ describe('Notifications', () => {
width: 100,
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Low priority 1');
expect(output).toContain('High priority 1');
@@ -234,7 +227,6 @@ describe('Notifications', () => {
settings,
width: 100,
});
- await waitUntilReady();
expect(lastFrame()).toContain('High priority 1');
await act(async () => {
@@ -253,7 +245,7 @@ describe('Notifications', () => {
updateInfo: null,
} as unknown as UIState;
mockUseUIState.mockReturnValue(uiState);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
@@ -261,7 +253,6 @@ describe('Notifications', () => {
width: 100,
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -273,7 +264,7 @@ describe('Notifications', () => {
updateInfo: null,
} as unknown as UIState;
mockUseUIState.mockReturnValue(uiState);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
@@ -281,7 +272,6 @@ describe('Notifications', () => {
width: 100,
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -293,7 +283,7 @@ describe('Notifications', () => {
updateInfo: { message: 'Update available' },
} as unknown as UIState;
mockUseUIState.mockReturnValue(uiState);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState,
@@ -301,7 +291,6 @@ describe('Notifications', () => {
width: 100,
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -311,14 +300,13 @@ describe('Notifications', () => {
persistentStateMock.setData({ hasSeenScreenReaderNudge: false });
mockFsAccess.mockRejectedValue(new Error('No legacy file'));
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
width: 100,
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('screen reader-friendly view');
expect(persistentStateMock.set).toHaveBeenCalledWith(
@@ -352,14 +340,13 @@ describe('Notifications', () => {
mockUseIsScreenReaderEnabled.mockReturnValue(true);
persistentStateMock.setData({ hasSeenScreenReaderNudge: true });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
width: 100,
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
expect(persistentStateMock.set).not.toHaveBeenCalled();
diff --git a/packages/cli/src/ui/components/OverageMenuDialog.test.tsx b/packages/cli/src/ui/components/OverageMenuDialog.test.tsx
index 68639c3e02..2812005005 100644
--- a/packages/cli/src/ui/components/OverageMenuDialog.test.tsx
+++ b/packages/cli/src/ui/components/OverageMenuDialog.test.tsx
@@ -29,7 +29,7 @@ describe('OverageMenuDialog', () => {
describe('rendering', () => {
it('should match snapshot with fallback available', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
onChoice={mockOnChoice}
/>,
);
- await waitUntilReady();
-
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should match snapshot without fallback', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
-
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should display the credit balance', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
-
const output = lastFrame() ?? '';
expect(output).toContain('200');
expect(output).toContain('AI Credits available');
@@ -75,15 +69,13 @@ describe('OverageMenuDialog', () => {
});
it('should display the model name', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
-
const output = lastFrame() ?? '';
expect(output).toContain('gemini-2.5-pro');
expect(output).toContain('Usage limit reached');
@@ -91,7 +83,7 @@ describe('OverageMenuDialog', () => {
});
it('should display reset time when provided', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
onChoice={mockOnChoice}
/>,
);
- await waitUntilReady();
-
const output = lastFrame() ?? '';
expect(output).toContain('3:45 PM');
expect(output).toContain('Access resets at');
@@ -108,30 +98,26 @@ describe('OverageMenuDialog', () => {
});
it('should not display reset time when not provided', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
-
const output = lastFrame() ?? '';
expect(output).not.toContain('Access resets at');
unmount();
});
it('should display slash command hints', async () => {
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
-
const output = lastFrame() ?? '';
expect(output).toContain('/stats');
expect(output).toContain('/model');
@@ -143,15 +129,13 @@ describe('OverageMenuDialog', () => {
describe('onChoice handling', () => {
it('should call onChoice with use_credits when selected', async () => {
// use_credits is the first item, so just press Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
-
writeKey(stdin, '\r');
await waitFor(() => {
@@ -162,15 +146,13 @@ describe('OverageMenuDialog', () => {
it('should call onChoice with manage when selected', async () => {
// manage is the second item: Down + Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
-
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
@@ -183,7 +165,7 @@ describe('OverageMenuDialog', () => {
it('should call onChoice with use_fallback when selected', async () => {
// With fallback: items are [use_credits, manage, use_fallback, stop]
// use_fallback is the third item: Down x2 + Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
{
onChoice={mockOnChoice}
/>,
);
- await waitUntilReady();
-
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
@@ -206,15 +186,13 @@ describe('OverageMenuDialog', () => {
it('should call onChoice with stop when selected', async () => {
// Without fallback: items are [use_credits, manage, stop]
// stop is the third item: Down x2 + Enter
- const { unmount, stdin, waitUntilReady } = await renderWithProviders(
+ const { unmount, stdin } = await renderWithProviders(
,
);
- await waitUntilReady();
-
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\x1b[B'); // Down arrow
writeKey(stdin, '\r');
diff --git a/packages/cli/src/ui/components/PermissionsModifyTrustDialog.test.tsx b/packages/cli/src/ui/components/PermissionsModifyTrustDialog.test.tsx
index bc4cba74b3..acb7897ba1 100644
--- a/packages/cli/src/ui/components/PermissionsModifyTrustDialog.test.tsx
+++ b/packages/cli/src/ui/components/PermissionsModifyTrustDialog.test.tsx
@@ -72,10 +72,9 @@ describe('PermissionsModifyTrustDialog', () => {
});
it('should render the main dialog with current trust level', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain('Modify Trust Level');
@@ -96,10 +95,9 @@ describe('PermissionsModifyTrustDialog', () => {
commitTrustLevelChange: mockCommitTrustLevelChange,
isFolderTrustEnabled: true,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain(
@@ -120,10 +118,9 @@ describe('PermissionsModifyTrustDialog', () => {
commitTrustLevelChange: mockCommitTrustLevelChange,
isFolderTrustEnabled: true,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain(
@@ -134,10 +131,9 @@ describe('PermissionsModifyTrustDialog', () => {
});
it('should render the labels with folder names', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain('Trust this folder (dir)');
@@ -152,7 +148,6 @@ describe('PermissionsModifyTrustDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
@@ -191,7 +186,6 @@ describe('PermissionsModifyTrustDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
@@ -226,7 +220,6 @@ describe('PermissionsModifyTrustDialog', () => {
await renderWithProviders(
,
);
- await waitUntilReady();
await waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
diff --git a/packages/cli/src/ui/components/PolicyUpdateDialog.test.tsx b/packages/cli/src/ui/components/PolicyUpdateDialog.test.tsx
index 0600b16bbe..4b151c8fbf 100644
--- a/packages/cli/src/ui/components/PolicyUpdateDialog.test.tsx
+++ b/packages/cli/src/ui/components/PolicyUpdateDialog.test.tsx
@@ -57,7 +57,7 @@ describe('PolicyUpdateDialog', () => {
});
it('renders correctly and matches snapshot', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
expect(output).toContain('New or changed workspace policies detected');
diff --git a/packages/cli/src/ui/components/ProQuotaDialog.test.tsx b/packages/cli/src/ui/components/ProQuotaDialog.test.tsx
index 2b69770582..1f1ece6ca6 100644
--- a/packages/cli/src/ui/components/ProQuotaDialog.test.tsx
+++ b/packages/cli/src/ui/components/ProQuotaDialog.test.tsx
@@ -29,8 +29,8 @@ describe('ProQuotaDialog', () => {
});
describe('for flash model failures', () => {
- it('should render "Keep trying" and "Stop" options', () => {
- const { unmount } = render(
+ it('should render "Keep trying" and "Stop" options', async () => {
+ const { unmount } = await render(
{
describe('for non-flash model failures', () => {
describe('when it is a terminal quota error', () => {
- it('should render switch, upgrade, and stop options for LOGIN_WITH_GOOGLE', () => {
- const { unmount } = render(
+ it('should render switch, upgrade, and stop options for LOGIN_WITH_GOOGLE', async () => {
+ const { unmount } = await render(
{
unmount();
});
- it('should NOT render upgrade option for USE_GEMINI', () => {
- const { unmount } = render(
+ it('should NOT render upgrade option for USE_GEMINI', async () => {
+ const { unmount } = await render(
{
unmount();
});
- it('should render "Keep trying" and "Stop" options when failed model and fallback model are the same', () => {
- const { unmount } = render(
+ it('should render "Keep trying" and "Stop" options when failed model and fallback model are the same', async () => {
+ const { unmount } = await render(
{
unmount();
});
- it('should render switch, upgrade, and stop options for LOGIN_WITH_GOOGLE (free tier)', () => {
- const { unmount } = render(
+ it('should render switch, upgrade, and stop options for LOGIN_WITH_GOOGLE (free tier)', async () => {
+ const { unmount } = await render(
{
unmount();
});
- it('should NOT render upgrade option for LOGIN_WITH_GOOGLE if tier is Ultra', () => {
- const { unmount } = render(
+ it('should NOT render upgrade option for LOGIN_WITH_GOOGLE if tier is Ultra', async () => {
+ const { unmount } = await render(
{
});
describe('when it is a capacity error', () => {
- it('should render keep trying, switch, and stop options', () => {
- const { unmount } = render(
+ it('should render keep trying, switch, and stop options', async () => {
+ const { unmount } = await render(
{
});
describe('when it is a model not found error', () => {
- it('should render switch, upgrade, and stop options for LOGIN_WITH_GOOGLE', () => {
- const { unmount } = render(
+ it('should render switch, upgrade, and stop options for LOGIN_WITH_GOOGLE', async () => {
+ const { unmount } = await render(
{
unmount();
});
- it('should NOT render upgrade option for USE_GEMINI', () => {
- const { unmount } = render(
+ it('should NOT render upgrade option for USE_GEMINI', async () => {
+ const { unmount } = await render(
{
});
describe('onChoice handling', () => {
- it('should call onChoice with the selected value', () => {
- const { unmount } = render(
+ it('should call onChoice with the selected value', async () => {
+ const { unmount } = await render(
{
it('renders nothing when message queue is empty', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('displays single queued message', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Queued (press ↑ to edit):');
@@ -38,10 +36,9 @@ describe('QueuedMessageDisplay', () => {
'Third queued message',
];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Queued (press ↑ to edit):');
@@ -60,10 +57,9 @@ describe('QueuedMessageDisplay', () => {
'Message 5',
];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Queued (press ↑ to edit):');
@@ -79,10 +75,9 @@ describe('QueuedMessageDisplay', () => {
it('normalizes whitespace in messages', async () => {
const messageQueue = ['Message with\tmultiple\n whitespace'];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Queued (press ↑ to edit):');
diff --git a/packages/cli/src/ui/components/QuittingDisplay.test.tsx b/packages/cli/src/ui/components/QuittingDisplay.test.tsx
index dc20510759..c3835c07c8 100644
--- a/packages/cli/src/ui/components/QuittingDisplay.test.tsx
+++ b/packages/cli/src/ui/components/QuittingDisplay.test.tsx
@@ -43,8 +43,7 @@ describe('QuittingDisplay', () => {
mockUseUIState.mockReturnValue({
quittingMessages: null,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -58,8 +57,7 @@ describe('QuittingDisplay', () => {
quittingMessages: mockMessages,
constrainHeight: false,
} as unknown as UIState);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('Goodbye');
expect(lastFrame()).toContain('See you later');
unmount();
diff --git a/packages/cli/src/ui/components/QuotaDisplay.test.tsx b/packages/cli/src/ui/components/QuotaDisplay.test.tsx
index 5a8b8c5bf8..ad0adba12e 100644
--- a/packages/cli/src/ui/components/QuotaDisplay.test.tsx
+++ b/packages/cli/src/ui/components/QuotaDisplay.test.tsx
@@ -20,72 +20,65 @@ describe('QuotaDisplay', () => {
vi.unstubAllEnvs();
});
it('should not render when remaining is undefined', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('should not render when limit is undefined', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('should not render when limit is 0', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('should not render when usage < 80%', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('should render warning when used >= 80%', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should render critical when used >= 95%', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should render with reset time when provided', async () => {
const resetTime = new Date(Date.now() + 3600000).toISOString(); // 1 hour from now
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should NOT render reset time when terse is true', async () => {
const resetTime = new Date(Date.now() + 3600000).toISOString();
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
{
terse={true}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('should render terse limit reached message', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/components/RawMarkdownIndicator.test.tsx b/packages/cli/src/ui/components/RawMarkdownIndicator.test.tsx
index 0ae721ccd5..2c17ec1357 100644
--- a/packages/cli/src/ui/components/RawMarkdownIndicator.test.tsx
+++ b/packages/cli/src/ui/components/RawMarkdownIndicator.test.tsx
@@ -24,10 +24,7 @@ describe('RawMarkdownIndicator', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('raw markdown mode');
expect(lastFrame()).toContain('Option+M to toggle');
unmount();
@@ -37,10 +34,7 @@ describe('RawMarkdownIndicator', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
});
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('raw markdown mode');
expect(lastFrame()).toContain('Alt+M to toggle');
unmount();
diff --git a/packages/cli/src/ui/components/RewindConfirmation.test.tsx b/packages/cli/src/ui/components/RewindConfirmation.test.tsx
index 6616ec4174..92cc70ae34 100644
--- a/packages/cli/src/ui/components/RewindConfirmation.test.tsx
+++ b/packages/cli/src/ui/components/RewindConfirmation.test.tsx
@@ -23,7 +23,7 @@ describe('RewindConfirmation', () => {
details: [{ fileName: 'test.ts', diff: '' }],
};
const onConfirm = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
{ width: 80 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame()).toContain('Revert code changes');
@@ -40,7 +39,7 @@ describe('RewindConfirmation', () => {
it('renders correctly without stats', async () => {
const onConfirm = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
{ width: 80 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame()).not.toContain('Revert code changes');
@@ -58,7 +56,7 @@ describe('RewindConfirmation', () => {
it('calls onConfirm with Cancel on Escape', async () => {
const onConfirm = vi.fn();
- const { stdin, waitUntilReady, unmount } = await renderWithProviders(
+ const { stdin, unmount } = await renderWithProviders(
{
/>,
{ width: 80 },
);
- await waitUntilReady();
await act(async () => {
stdin.write('\x1b');
@@ -81,7 +78,7 @@ describe('RewindConfirmation', () => {
it('renders timestamp when provided', async () => {
const onConfirm = vi.fn();
const timestamp = new Date().toISOString();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
{ width: 80 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame()).not.toContain('Revert code changes');
diff --git a/packages/cli/src/ui/components/RewindViewer.test.tsx b/packages/cli/src/ui/components/RewindViewer.test.tsx
index 048a5f60d8..0dd7fa5c02 100644
--- a/packages/cli/src/ui/components/RewindViewer.test.tsx
+++ b/packages/cli/src/ui/components/RewindViewer.test.tsx
@@ -91,14 +91,13 @@ describe('RewindViewer', () => {
const conversation = createConversation([
{ type: 'user', content: 'Hello', id: '1', timestamp: '1' },
]);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Rewind');
expect(lastFrame()).toContain('Hello');
unmount();
@@ -130,14 +129,13 @@ describe('RewindViewer', () => {
const conversation = createConversation(messages as MessageRecord[]);
const onExit = vi.fn();
const onRewind = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -162,7 +160,6 @@ describe('RewindViewer', () => {
onRewind={onRewind}
/>,
);
- await waitUntilReady();
// Initial state
expect(lastFrame()).toMatchSnapshot('initial-state');
@@ -197,7 +194,6 @@ describe('RewindViewer', () => {
onRewind={vi.fn()}
/>,
);
- await waitUntilReady();
act(() => {
stdin.write(sequence);
@@ -230,7 +226,6 @@ describe('RewindViewer', () => {
onRewind={vi.fn()}
/>,
);
- await waitUntilReady();
// Up from first -> Last
act(() => {
@@ -308,7 +303,6 @@ describe('RewindViewer', () => {
onRewind={onRewind}
/>,
);
- await waitUntilReady();
// Select
await act(async () => {
@@ -366,7 +360,6 @@ describe('RewindViewer', () => {
onRewind={onRewind}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -403,14 +396,13 @@ describe('RewindViewer', () => {
const onExit = vi.fn();
const onRewind = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot('initial');
@@ -422,18 +414,14 @@ describe('RewindViewer', () => {
];
conversation = createConversation(newMessages);
- const {
- lastFrame: lastFrame2,
- waitUntilReady: waitUntilReady2,
- unmount: unmount2,
- } = await renderWithProviders(
- ,
- );
- await waitUntilReady2();
+ const { lastFrame: lastFrame2, unmount: unmount2 } =
+ await renderWithProviders(
+ ,
+ );
expect(lastFrame2()).toMatchSnapshot('after-update');
unmount2();
@@ -451,15 +439,13 @@ it('renders accessible screen reader view when screen reader is enabled', async
const onExit = vi.fn();
const onRewind = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
-
const frame = lastFrame();
expect(frame).toContain('Rewind - Select a conversation point:');
expect(frame).toContain('Stay at current position');
diff --git a/packages/cli/src/ui/components/SessionBrowser.test.tsx b/packages/cli/src/ui/components/SessionBrowser.test.tsx
index 83e3ae1aaa..70d6ee3ee7 100644
--- a/packages/cli/src/ui/components/SessionBrowser.test.tsx
+++ b/packages/cli/src/ui/components/SessionBrowser.test.tsx
@@ -154,7 +154,7 @@ describe('SessionBrowser component', () => {
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
const onExit = vi.fn();
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
testSessions={[]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -192,7 +191,7 @@ describe('SessionBrowser component', () => {
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
const onExit = vi.fn();
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
testSessions={[session1, session2]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -245,7 +243,7 @@ describe('SessionBrowser component', () => {
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
const onExit = vi.fn();
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame, waitUntilReady } = await render(
{
testSessions={[searchSession, otherSession]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Chat Sessions (2 total');
@@ -305,7 +302,7 @@ describe('SessionBrowser component', () => {
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
const onExit = vi.fn();
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame, waitUntilReady } = await render(
{
testSessions={[session1, session2]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Chat Sessions (2 total');
@@ -354,7 +350,7 @@ describe('SessionBrowser component', () => {
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
const onExit = vi.fn();
- const { waitUntilReady } = render(
+ const { waitUntilReady } = await render(
{
testSessions={[currentSession, otherSession]}
/>,
);
- await waitUntilReady();
// Active selection is at 0 (current session).
triggerKey({ name: 'enter', sequence: '\r' });
@@ -382,7 +377,7 @@ describe('SessionBrowser component', () => {
const onDeleteSession = vi.fn().mockResolvedValue(undefined);
const onExit = vi.fn();
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
testError="storage failure"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
diff --git a/packages/cli/src/ui/components/SessionBrowser/SessionBrowserSearchNav.test.tsx b/packages/cli/src/ui/components/SessionBrowser/SessionBrowserSearchNav.test.tsx
index af7f1a6906..be37317626 100644
--- a/packages/cli/src/ui/components/SessionBrowser/SessionBrowserSearchNav.test.tsx
+++ b/packages/cli/src/ui/components/SessionBrowser/SessionBrowserSearchNav.test.tsx
@@ -17,16 +17,12 @@ import type { SessionBrowserState } from '../SessionBrowser.js';
describe('SessionBrowser Search and Navigation Components', () => {
it('SearchModeDisplay renders correctly with query', async () => {
const mockState = { searchQuery: 'test query' } as SessionBrowserState;
- const { lastFrame, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
it('NavigationHelp renders correctly', async () => {
- const { lastFrame, waitUntilReady } = render();
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
@@ -37,10 +33,7 @@ describe('SessionBrowser Search and Navigation Components', () => {
sortOrder: 'date',
sortReverse: false,
} as SessionBrowserState;
- const { lastFrame, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
@@ -51,19 +44,13 @@ describe('SessionBrowser Search and Navigation Components', () => {
sortOrder: 'name',
sortReverse: true,
} as SessionBrowserState;
- const { lastFrame, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
it('NoResultsDisplay renders correctly', async () => {
const mockState = { searchQuery: 'no match' } as SessionBrowserState;
- const { lastFrame, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/SessionBrowser/SessionBrowserStates.test.tsx b/packages/cli/src/ui/components/SessionBrowser/SessionBrowserStates.test.tsx
index 2b816a8211..0607c28a24 100644
--- a/packages/cli/src/ui/components/SessionBrowser/SessionBrowserStates.test.tsx
+++ b/packages/cli/src/ui/components/SessionBrowser/SessionBrowserStates.test.tsx
@@ -13,23 +13,20 @@ import type { SessionBrowserState } from '../SessionBrowser.js';
describe('SessionBrowser UI States', () => {
it('SessionBrowserLoading renders correctly', async () => {
- const { lastFrame, waitUntilReady } = render();
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
it('SessionBrowserError renders correctly', async () => {
const mockState = { error: 'Test error message' } as SessionBrowserState;
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('SessionBrowserEmpty renders correctly', async () => {
- const { lastFrame, waitUntilReady } = render();
- await waitUntilReady();
+ const { lastFrame } = await render();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/SettingsDialog.test.tsx b/packages/cli/src/ui/components/SettingsDialog.test.tsx
index 40df8d89f0..9887415a57 100644
--- a/packages/cli/src/ui/components/SettingsDialog.test.tsx
+++ b/packages/cli/src/ui/components/SettingsDialog.test.tsx
@@ -270,11 +270,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
const output = lastFrame();
expect(output).toContain('Settings');
@@ -288,14 +284,9 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- {
- availableTerminalHeight: 20,
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect, {
+ availableTerminalHeight: 20,
+ });
const output = lastFrame();
// Should still render properly with the height prop
@@ -310,7 +301,6 @@ describe('SettingsDialog', () => {
const onSelect = vi.fn();
const renderResult = await renderDialog(settings, onSelect);
- await renderResult.waitUntilReady();
await expect(renderResult).toMatchSvgSnapshot();
renderResult.unmount();
@@ -321,14 +311,9 @@ describe('SettingsDialog', () => {
const onSelect = vi.fn();
// Render with a fixed height of 25 rows
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- {
- availableTerminalHeight: 25,
- },
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect, {
+ availableTerminalHeight: 25,
+ });
// Wait for the dialog to render
await waitFor(() => {
@@ -348,11 +333,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
const output = lastFrame();
// 'general.vimMode' has description 'Enable Vim keybindings' in settingsSchema.ts
@@ -385,7 +366,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
const initialFrame = lastFrame();
expect(initialFrame).toContain('Vim Mode');
@@ -420,7 +400,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Enter 'j' and 'k' in search
await act(async () => stdin.write('j'));
@@ -446,7 +425,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Try to go up from first item
await act(async () => {
@@ -469,11 +447,10 @@ describe('SettingsDialog', () => {
const setValueSpy = vi.spyOn(settings, 'setValue');
const onSelect = vi.fn();
- const { stdin, unmount, lastFrame, waitUntilReady } = await renderDialog(
+ const { stdin, unmount, lastFrame } = await renderDialog(
settings,
onSelect,
);
- await waitUntilReady();
// Wait for initial render and verify we're on Vim Mode (first setting)
await waitFor(() => {
@@ -526,7 +503,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
await act(async () => {
stdin.write(TerminalKeys.DOWN_ARROW as string);
@@ -558,7 +534,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Navigate to vim mode setting and toggle it
// This would require knowing the exact position, so we'll just test that the mock is called
@@ -581,7 +556,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Switch to scope focus
await act(async () => {
@@ -598,11 +572,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, unmount, waitUntilReady } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// Wait for initial render
await waitFor(() => {
@@ -625,14 +595,9 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onRestartRequest = vi.fn();
- const { unmount, waitUntilReady } = await renderDialog(
- settings,
- vi.fn(),
- {
- onRestartRequest,
- },
- );
- await waitUntilReady();
+ const { unmount } = await renderDialog(settings, vi.fn(), {
+ onRestartRequest,
+ });
// This test would need to trigger a restart-required setting change
// The exact steps depend on which settings require restart
@@ -651,7 +616,6 @@ describe('SettingsDialog', () => {
onRestartRequest,
},
);
- await waitUntilReady();
// Press 'r' key (this would only work if restart prompt is showing)
await act(async () => {
@@ -669,11 +633,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, unmount, waitUntilReady } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// Wait for initial render
await waitFor(() => {
@@ -700,7 +660,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Switch to scope selector and change scope
await act(async () => {
@@ -733,11 +692,7 @@ describe('SettingsDialog', () => {
});
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// Should show user scope values initially
const output = lastFrame();
@@ -755,7 +710,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Toggle a setting, then toggle another setting
await act(async () => {
@@ -783,7 +737,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Navigate down many times to test scrolling
await act(async () => {
@@ -818,11 +771,7 @@ describe('SettingsDialog', () => {
});
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
const output = lastFrame();
// Should contain settings labels
@@ -838,7 +787,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Toggle a non-restart-required setting (like hideTips)
await act(async () => {
@@ -854,11 +802,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, unmount, waitUntilReady } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// This test would need to navigate to a specific restart-required setting
// Since we can't easily target specific settings, we test the general behavior
@@ -877,11 +821,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { unmount, waitUntilReady } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { unmount } = await renderDialog(settings, onSelect);
// Restart prompt should be cleared when switching scopes
unmount();
@@ -899,11 +839,7 @@ describe('SettingsDialog', () => {
});
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
const output = lastFrame();
// Settings should show inherited values
@@ -926,11 +862,7 @@ describe('SettingsDialog', () => {
});
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
const output = lastFrame();
// Should show settings with override indicators
@@ -1011,7 +943,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Rapid navigation
await act(async () => {
@@ -1039,7 +970,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
await act(async () => {
stdin.write(code);
@@ -1059,7 +989,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Try to navigate when potentially at bounds
await act(async () => {
@@ -1078,11 +1007,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, unmount, waitUntilReady } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// Wait for initial render
await waitFor(() => {
@@ -1112,11 +1037,7 @@ describe('SettingsDialog', () => {
});
const onSelect = vi.fn();
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// Should still render without crashing
expect(lastFrame()).toContain('Settings');
@@ -1128,11 +1049,7 @@ describe('SettingsDialog', () => {
const onSelect = vi.fn();
// Should not crash even if some settings are missing definitions
- const { lastFrame, waitUntilReady, unmount } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
expect(lastFrame()).toContain('Settings');
unmount();
@@ -1144,11 +1061,7 @@ describe('SettingsDialog', () => {
const settings = createMockSettings();
const onSelect = vi.fn();
- const { lastFrame, unmount, waitUntilReady } = await renderDialog(
- settings,
- onSelect,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderDialog(settings, onSelect);
// Wait for initial render
await waitFor(() => {
@@ -1177,7 +1090,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Toggle multiple settings
await act(async () => {
@@ -1214,7 +1126,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Multiple scope changes
await act(async () => {
@@ -1253,7 +1164,6 @@ describe('SettingsDialog', () => {
onRestartRequest,
},
);
- await waitUntilReady();
// This would test the restart workflow if we could trigger it
await act(async () => {
@@ -1281,7 +1191,6 @@ describe('SettingsDialog', () => {
onRestartRequest,
},
);
- await waitUntilReady();
// Wait for initial render
await waitFor(() => expect(lastFrame()).toContain('Show Color'));
@@ -1330,7 +1239,6 @@ describe('SettingsDialog', () => {
settings,
vi.fn(),
);
- await waitUntilReady();
// Search box should be visible initially (searchPlaceholder)
expect(lastFrame()).toContain('Search to filter');
@@ -1374,7 +1282,6 @@ describe('SettingsDialog', () => {
,
{ settings, config: makeFakeConfig() },
);
- await waitUntilReady();
// Search for 'chat history' to filter the list
await act(async () => {
@@ -1503,7 +1410,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Wait for initial render and verify that search is not active
await waitFor(() => {
@@ -1533,7 +1439,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
await act(async () => {
stdin.write('yolo');
@@ -1556,7 +1461,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
await act(async () => {
stdin.write('vim');
@@ -1589,7 +1493,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
await act(async () => {
stdin.write('vimm');
@@ -1622,7 +1525,6 @@ describe('SettingsDialog', () => {
settings,
onSelect,
);
- await waitUntilReady();
// Type a search query that won't match any settings
await act(async () => {
diff --git a/packages/cli/src/ui/components/ShellInputPrompt.test.tsx b/packages/cli/src/ui/components/ShellInputPrompt.test.tsx
index 0a46b1527e..794c7beaff 100644
--- a/packages/cli/src/ui/components/ShellInputPrompt.test.tsx
+++ b/packages/cli/src/ui/components/ShellInputPrompt.test.tsx
@@ -48,19 +48,17 @@ describe('ShellInputPrompt', () => {
});
it('renders nothing', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('sends tab to pty', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
@@ -84,10 +82,9 @@ describe('ShellInputPrompt', () => {
['a', 'a'],
['b', 'b'],
])('handles keypress input: %s', async (name, sequence) => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
// Get the registered handler
const handler = mockUseKeypress.mock.calls[0][0];
@@ -113,10 +110,9 @@ describe('ShellInputPrompt', () => {
['up', -1],
['down', 1],
])('handles scroll %s (Command.SCROLL_%s)', async (key, direction) => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
@@ -135,10 +131,9 @@ describe('ShellInputPrompt', () => {
])(
'handles page scroll %s (Command.PAGE_%s) with default size',
async (key, expectedScroll) => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
@@ -159,14 +154,13 @@ describe('ShellInputPrompt', () => {
);
it('respects scrollPageSize prop', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
@@ -199,10 +193,9 @@ describe('ShellInputPrompt', () => {
});
it('does not handle input when not focused', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
@@ -223,10 +216,9 @@ describe('ShellInputPrompt', () => {
});
it('does not handle input when no active shell', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
@@ -247,10 +239,9 @@ describe('ShellInputPrompt', () => {
});
it('ignores Command.UNFOCUS_SHELL (Shift+Tab) to allow focus navigation', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const handler = mockUseKeypress.mock.calls[0][0];
diff --git a/packages/cli/src/ui/components/ShellModeIndicator.test.tsx b/packages/cli/src/ui/components/ShellModeIndicator.test.tsx
index 321077ff21..0ab5d42116 100644
--- a/packages/cli/src/ui/components/ShellModeIndicator.test.tsx
+++ b/packages/cli/src/ui/components/ShellModeIndicator.test.tsx
@@ -10,10 +10,7 @@ import { describe, it, expect } from 'vitest';
describe('ShellModeIndicator', () => {
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('shell mode enabled');
expect(lastFrame()).toContain('esc to disable');
unmount();
diff --git a/packages/cli/src/ui/components/ShortcutsHelp.test.tsx b/packages/cli/src/ui/components/ShortcutsHelp.test.tsx
index f5da5109a0..8129dcb59b 100644
--- a/packages/cli/src/ui/components/ShortcutsHelp.test.tsx
+++ b/packages/cli/src/ui/components/ShortcutsHelp.test.tsx
@@ -42,13 +42,12 @@ describe('ShortcutsHelp', () => {
value: platform.value,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
width,
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('shell mode');
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -57,7 +56,7 @@ describe('ShortcutsHelp', () => {
it('always shows Tab focus UI shortcut', async () => {
const rendered = await renderWithProviders();
- await rendered.waitUntilReady();
+
expect(rendered.lastFrame()).toContain('Tab focus UI');
rendered.unmount();
});
diff --git a/packages/cli/src/ui/components/ShowMoreLines.test.tsx b/packages/cli/src/ui/components/ShowMoreLines.test.tsx
index dbdc8085a2..dd3ee03064 100644
--- a/packages/cli/src/ui/components/ShowMoreLines.test.tsx
+++ b/packages/cli/src/ui/components/ShowMoreLines.test.tsx
@@ -36,10 +36,9 @@ describe('ShowMoreLines', () => {
ReturnType
>);
mockUseStreamingContext.mockReturnValue(streamingState);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
},
@@ -51,10 +50,9 @@ describe('ShowMoreLines', () => {
overflowingIds: new Set(['1']),
} as NonNullable>);
mockUseStreamingContext.mockReturnValue(StreamingState.Idle);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().toLowerCase()).toContain(
'press ctrl+o to show more lines',
);
@@ -73,10 +71,9 @@ describe('ShowMoreLines', () => {
overflowingIds: new Set(['1']),
} as NonNullable>);
mockUseStreamingContext.mockReturnValue(streamingState);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().toLowerCase()).toContain(
'press ctrl+o to show more lines',
);
@@ -90,10 +87,9 @@ describe('ShowMoreLines', () => {
overflowingIds: new Set(),
} as NonNullable>);
mockUseStreamingContext.mockReturnValue(StreamingState.Idle);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().toLowerCase()).toContain(
'press ctrl+o to show more lines',
);
@@ -105,10 +101,9 @@ describe('ShowMoreLines', () => {
overflowingIds: new Set(['1']),
} as NonNullable>);
mockUseStreamingContext.mockReturnValue(StreamingState.Idle);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
diff --git a/packages/cli/src/ui/components/ShowMoreLinesLayout.test.tsx b/packages/cli/src/ui/components/ShowMoreLinesLayout.test.tsx
index b5f8eb3b8b..3073c81770 100644
--- a/packages/cli/src/ui/components/ShowMoreLinesLayout.test.tsx
+++ b/packages/cli/src/ui/components/ShowMoreLinesLayout.test.tsx
@@ -43,8 +43,7 @@ describe('ShowMoreLines layout and padding', () => {
);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
// lastFrame() strips some formatting but keeps layout
const output = lastFrame({ allowEmpty: true });
@@ -76,8 +75,7 @@ describe('ShowMoreLines layout and padding', () => {
);
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
const output = lastFrame({ allowEmpty: true });
const lines = output.split('\n');
diff --git a/packages/cli/src/ui/components/StatsDisplay.test.tsx b/packages/cli/src/ui/components/StatsDisplay.test.tsx
index 48d60b75c6..8c979afcc6 100644
--- a/packages/cli/src/ui/components/StatsDisplay.test.tsx
+++ b/packages/cli/src/ui/components/StatsDisplay.test.tsx
@@ -81,9 +81,7 @@ describe('', () => {
it('renders only the Performance section in its zero state', async () => {
const zeroMetrics = createTestMetrics();
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(zeroMetrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(zeroMetrics);
const output = lastFrame();
expect(output).toContain('Performance');
@@ -123,8 +121,7 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady } = await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
const output = lastFrame();
expect(output).toContain('gemini-2.5-pro');
@@ -179,8 +176,7 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady } = await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
const output = lastFrame();
expect(output).toContain('Performance');
@@ -221,9 +217,7 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
const output = lastFrame();
expect(output).toContain('Interaction Summary');
@@ -251,9 +245,7 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -277,9 +269,7 @@ describe('', () => {
byName: {},
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
expect(lastFrame()).toMatchSnapshot();
});
@@ -299,9 +289,7 @@ describe('', () => {
byName: {},
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
expect(lastFrame()).toMatchSnapshot();
});
@@ -321,9 +309,7 @@ describe('', () => {
byName: {},
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
expect(lastFrame()).toMatchSnapshot();
});
});
@@ -350,9 +336,7 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
const output = lastFrame();
expect(output).toContain('Code Changes:');
@@ -378,9 +362,7 @@ describe('', () => {
},
});
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(metrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(metrics);
const output = lastFrame();
expect(output).not.toContain('Code Changes:');
@@ -392,9 +374,7 @@ describe('', () => {
const zeroMetrics = createTestMetrics();
it('renders the default title when no title prop is provided', async () => {
- const { lastFrame, waitUntilReady } =
- await renderWithMockedStats(zeroMetrics);
- await waitUntilReady();
+ const { lastFrame } = await renderWithMockedStats(zeroMetrics);
const output = lastFrame();
expect(output).toContain('Session Stats');
expect(output).not.toContain('Agent powering down');
@@ -415,11 +395,10 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Agent powering down. Goodbye!');
expect(output).not.toContain('Session Stats');
@@ -477,11 +456,10 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Model usage');
@@ -525,7 +503,7 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
', () => {
/>,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
// (1 - 710/1100) * 100 = 35.5%
@@ -581,11 +558,10 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('gemini-2.5-flash');
@@ -614,7 +590,7 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
', () => {
/>,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Auth Method:');
@@ -647,11 +622,10 @@ describe('', () => {
startNewPrompt: vi.fn(),
});
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
{ width: 100 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Auth Method:');
diff --git a/packages/cli/src/ui/components/StatusDisplay.test.tsx b/packages/cli/src/ui/components/StatusDisplay.test.tsx
index fcb66ea0b2..82b439e65f 100644
--- a/packages/cli/src/ui/components/StatusDisplay.test.tsx
+++ b/packages/cli/src/ui/components/StatusDisplay.test.tsx
@@ -75,7 +75,7 @@ const renderStatusDisplay = async (
settings = createMockSettings(),
config = createMockConfig(),
) => {
- const result = render(
+ const result = await render(
@@ -84,7 +84,6 @@ const renderStatusDisplay = async (
,
);
- await result.waitUntilReady();
return result;
};
diff --git a/packages/cli/src/ui/components/StickyHeader.test.tsx b/packages/cli/src/ui/components/StickyHeader.test.tsx
index 7ff503423d..4576718c35 100644
--- a/packages/cli/src/ui/components/StickyHeader.test.tsx
+++ b/packages/cli/src/ui/components/StickyHeader.test.tsx
@@ -13,7 +13,7 @@ describe('StickyHeader', () => {
it.each([true, false])(
'renders children with isFirst=%s',
async (isFirst) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
Hello Sticky
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Hello Sticky');
unmount();
},
diff --git a/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx b/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx
index dbd5281bc6..c28d52332c 100644
--- a/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx
+++ b/packages/cli/src/ui/components/SuggestionsDisplay.test.tsx
@@ -17,7 +17,7 @@ describe('SuggestionsDisplay', () => {
];
it('renders loading state', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
mode="reverse"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders nothing when empty and not loading', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
mode="reverse"
/>,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
});
it('renders suggestions list', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
mode="reverse"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('highlights active item', async () => {
// This test relies on visual inspection or implementation details (colors)
// For now, we just ensure it renders without error and contains the item
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
mode="reverse"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -89,7 +85,7 @@ describe('SuggestionsDisplay', () => {
description: `Description ${i}`,
}));
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
mode="reverse"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -113,7 +108,7 @@ describe('SuggestionsDisplay', () => {
},
];
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
mode="reverse"
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -150,7 +144,7 @@ describe('SuggestionsDisplay', () => {
},
];
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
/>,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('-- auto --');
expect(frame).toContain('-- checkpoints --');
diff --git a/packages/cli/src/ui/components/Table.test.tsx b/packages/cli/src/ui/components/Table.test.tsx
index e8f312d9af..f898c98b5b 100644
--- a/packages/cli/src/ui/components/Table.test.tsx
+++ b/packages/cli/src/ui/components/Table.test.tsx
@@ -19,9 +19,11 @@ describe('Table', () => {
{ id: 2, name: 'Bob' },
];
- const renderResult = render(, 100);
- const { lastFrame, waitUntilReady } = renderResult;
- await waitUntilReady?.();
+ const renderResult = await render(
+ ,
+ 100,
+ );
+ const { lastFrame } = renderResult;
const output = lastFrame();
expect(output).toContain('ID');
@@ -46,9 +48,11 @@ describe('Table', () => {
];
const data = [{ value: 10 }];
- const renderResult = render(, 100);
- const { lastFrame, waitUntilReady } = renderResult;
- await waitUntilReady?.();
+ const renderResult = await render(
+ ,
+ 100,
+ );
+ const { lastFrame } = renderResult;
const output = lastFrame();
expect(output).toContain('20');
@@ -58,11 +62,10 @@ describe('Table', () => {
it('should handle undefined values gracefully', async () => {
const columns = [{ key: 'name', header: 'Name', flexGrow: 1 }];
const data: Array<{ name: string | undefined }> = [{ name: undefined }];
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
100,
);
- await waitUntilReady?.();
const output = lastFrame();
expect(output).toContain('undefined');
});
@@ -80,9 +83,11 @@ describe('Table', () => {
];
const data = [{ status: 'Active' }];
- const renderResult = render(, 100);
- const { lastFrame, waitUntilReady } = renderResult;
- await waitUntilReady?.();
+ const renderResult = await render(
+ ,
+ 100,
+ );
+ const { lastFrame } = renderResult;
const output = lastFrame();
expect(output).toContain('Active');
diff --git a/packages/cli/src/ui/components/ThemeDialog.test.tsx b/packages/cli/src/ui/components/ThemeDialog.test.tsx
index ecb6e1c197..dbb980071a 100644
--- a/packages/cli/src/ui/components/ThemeDialog.test.tsx
+++ b/packages/cli/src/ui/components/ThemeDialog.test.tsx
@@ -51,11 +51,10 @@ describe('ThemeDialog Snapshots', () => {
async (isDev) => {
mockIsDevelopment.value = isDev;
const settings = createMockSettings();
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -69,7 +68,6 @@ describe('ThemeDialog Snapshots', () => {
,
{ settings },
);
- await waitUntilReady();
// Press Tab to switch to scope selector mode
await act(async () => {
@@ -94,7 +92,6 @@ describe('ThemeDialog Snapshots', () => {
/>,
{ settings },
);
- await waitUntilReady();
await act(async () => {
stdin.write('\x1b');
@@ -119,7 +116,6 @@ describe('ThemeDialog Snapshots', () => {
settings,
},
);
- await waitUntilReady();
// Press Enter to select the theme
await act(async () => {
@@ -149,14 +145,13 @@ describe('Initial Theme Selection', () => {
it('should default to a light theme when terminal background is light and no theme is set', async () => {
const settings = createMockSettings(); // No theme set
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
uiState: { terminalBackgroundColor: '#FFFFFF' }, // Light background
},
);
- await waitUntilReady();
// The snapshot will show which theme is highlighted.
// We expect 'DefaultLight' to be the one with the '>' indicator.
@@ -166,14 +161,13 @@ describe('Initial Theme Selection', () => {
it('should default to a dark theme when terminal background is dark and no theme is set', async () => {
const settings = createMockSettings(); // No theme set
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
uiState: { terminalBackgroundColor: '#000000' }, // Dark background
},
);
- await waitUntilReady();
// We expect 'DefaultDark' to be highlighted.
expect(lastFrame()).toMatchSnapshot();
@@ -182,14 +176,13 @@ describe('Initial Theme Selection', () => {
it('should use the theme from settings even if terminal background suggests a different theme type', async () => {
const settings = createMockSettings({ ui: { theme: 'DefaultLight' } }); // Light theme set
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
uiState: { terminalBackgroundColor: '#000000' }, // Dark background
},
);
- await waitUntilReady();
// We expect 'DefaultLight' to be highlighted, respecting the settings.
expect(lastFrame()).toMatchSnapshot();
@@ -208,14 +201,13 @@ describe('Hint Visibility', () => {
it('should show hint when theme background matches terminal background', async () => {
const settings = createMockSettings({ ui: { theme: 'Default' } });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
uiState: { terminalBackgroundColor: '#000000' },
},
);
- await waitUntilReady();
expect(lastFrame()).toContain('(Matches terminal)');
unmount();
@@ -223,14 +215,13 @@ describe('Hint Visibility', () => {
it('should not show hint when theme background does not match terminal background', async () => {
const settings = createMockSettings({ ui: { theme: 'Default' } });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
settings,
uiState: { terminalBackgroundColor: '#FFFFFF' },
},
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('(Matches terminal)');
unmount();
diff --git a/packages/cli/src/ui/components/ThemedGradient.test.tsx b/packages/cli/src/ui/components/ThemedGradient.test.tsx
index 6632a63300..312a6c7011 100644
--- a/packages/cli/src/ui/components/ThemedGradient.test.tsx
+++ b/packages/cli/src/ui/components/ThemedGradient.test.tsx
@@ -26,10 +26,9 @@ vi.mock('../semantic-colors.js', () => ({
describe('ThemedGradient', () => {
it('renders children', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
Hello,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Hello');
unmount();
});
diff --git a/packages/cli/src/ui/components/Tips.test.tsx b/packages/cli/src/ui/components/Tips.test.tsx
index 873230fb87..1cec0bb530 100644
--- a/packages/cli/src/ui/components/Tips.test.tsx
+++ b/packages/cli/src/ui/components/Tips.test.tsx
@@ -18,10 +18,7 @@ describe('Tips', () => {
getGeminiMdFileCount: vi.fn().mockReturnValue(fileCount),
} as unknown as Config;
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/components/ToastDisplay.test.tsx b/packages/cli/src/ui/components/ToastDisplay.test.tsx
index 380470a42a..9bd2847b3f 100644
--- a/packages/cli/src/ui/components/ToastDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ToastDisplay.test.tsx
@@ -112,92 +112,82 @@ describe('ToastDisplay', () => {
});
it('renders nothing by default', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay();
- await waitUntilReady();
+ const { lastFrame } = await renderToastDisplay();
expect(lastFrame({ allowEmpty: true })).toBe('');
});
it('renders Ctrl+C prompt', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
ctrlCPressedOnce: true,
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders warning message', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
transientMessage: {
text: 'This is a warning',
type: TransientMessageType.Warning,
},
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders hint message', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
transientMessage: {
text: 'This is a hint',
type: TransientMessageType.Hint,
},
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders Ctrl+D prompt', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
ctrlDPressedOnce: true,
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders Escape prompt when buffer is empty', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
showEscapePrompt: true,
history: [{ id: 1, type: 'user', text: 'test' }] as HistoryItem[],
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders Escape prompt when buffer is NOT empty', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
showEscapePrompt: true,
buffer: { text: 'some text' } as TextBuffer,
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders Queue Error Message', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
queueErrorMessage: 'Queue Error',
});
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders expansion hint when showIsExpandableHint is true', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
showIsExpandableHint: true,
constrainHeight: true,
});
- await waitUntilReady();
expect(lastFrame()).toContain(
'Press Ctrl+O to show more lines of the last response',
);
});
it('renders collapse hint when showIsExpandableHint is true and constrainHeight is false', async () => {
- const { lastFrame, waitUntilReady } = await renderToastDisplay({
+ const { lastFrame } = await renderToastDisplay({
showIsExpandableHint: true,
constrainHeight: false,
});
- await waitUntilReady();
expect(lastFrame()).toContain(
'Ctrl+O to collapse lines of the last response',
);
diff --git a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
index 94a2a812a2..90d762581d 100644
--- a/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
+++ b/packages/cli/src/ui/components/ToolConfirmationQueue.test.tsx
@@ -5,6 +5,7 @@
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { act } from 'react';
import { Box } from 'ink';
import { ToolConfirmationQueue } from './ToolConfirmationQueue.js';
import { StreamingState } from '../types.js';
@@ -79,7 +80,7 @@ describe('ToolConfirmationQueue', () => {
total: 3,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -90,7 +91,6 @@ describe('ToolConfirmationQueue', () => {
},
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Action Required');
@@ -117,7 +117,7 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -128,7 +128,6 @@ describe('ToolConfirmationQueue', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
@@ -156,7 +155,7 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
},
},
);
- await waitUntilReady();
await waitFor(() =>
expect(lastFrame()?.toLowerCase()).toContain(
@@ -210,7 +208,7 @@ describe('ToolConfirmationQueue', () => {
};
// Use a small availableTerminalHeight to force truncation
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -226,7 +224,6 @@ describe('ToolConfirmationQueue', () => {
},
},
);
- await waitUntilReady();
// With availableTerminalHeight = 10:
// maxHeight = Math.max(10 - 1, 4) = 9
@@ -261,11 +258,7 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
- const {
- lastFrame,
- waitUntilReady,
- unmount = vi.fn(),
- } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -280,7 +273,6 @@ describe('ToolConfirmationQueue', () => {
},
},
);
- await waitUntilReady();
// Calculation:
// availableTerminalHeight: 20 -> maxHeight: 19 (20-1)
@@ -321,7 +313,7 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -335,7 +327,6 @@ describe('ToolConfirmationQueue', () => {
},
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Press CTRL-O to show more lines');
@@ -360,7 +351,7 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -371,7 +362,6 @@ describe('ToolConfirmationQueue', () => {
},
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -398,16 +388,18 @@ describe('ToolConfirmationQueue', () => {
total: 1,
};
- const { lastFrame, unmount } = await renderWithProviders(
- ,
- {
- config: mockConfig,
- uiState: {
- terminalWidth: 80,
+ const { lastFrame, unmount } = await act(async () =>
+ renderWithProviders(
+ ,
+ {
+ config: mockConfig,
+ uiState: {
+ terminalWidth: 80,
+ },
},
- },
+ ),
);
await waitFor(() => {
diff --git a/packages/cli/src/ui/components/ToolStatsDisplay.test.tsx b/packages/cli/src/ui/components/ToolStatsDisplay.test.tsx
index 197c7d84d5..8d104c9109 100644
--- a/packages/cli/src/ui/components/ToolStatsDisplay.test.tsx
+++ b/packages/cli/src/ui/components/ToolStatsDisplay.test.tsx
@@ -36,8 +36,7 @@ const renderWithMockedStats = async (metrics: SessionMetrics) => {
startNewPrompt: vi.fn(),
});
- const result = render();
- await result.waitUntilReady();
+ const result = await render();
return result;
};
diff --git a/packages/cli/src/ui/components/UpdateNotification.test.tsx b/packages/cli/src/ui/components/UpdateNotification.test.tsx
index fa8d4598ec..7b59d225fc 100644
--- a/packages/cli/src/ui/components/UpdateNotification.test.tsx
+++ b/packages/cli/src/ui/components/UpdateNotification.test.tsx
@@ -10,10 +10,9 @@ import { describe, it, expect } from 'vitest';
describe('UpdateNotification', () => {
it('renders message', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Update available!');
unmount();
});
diff --git a/packages/cli/src/ui/components/UserIdentity.test.tsx b/packages/cli/src/ui/components/UserIdentity.test.tsx
index 0d9eff2b36..b8c37adbf6 100644
--- a/packages/cli/src/ui/components/UserIdentity.test.tsx
+++ b/packages/cli/src/ui/components/UserIdentity.test.tsx
@@ -39,10 +39,9 @@ describe('', () => {
} as unknown as ContentGeneratorConfig);
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Signed in with Google: test@example.com');
@@ -85,10 +84,9 @@ describe('', () => {
} as unknown as ContentGeneratorConfig);
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Signed in with Google');
@@ -106,10 +104,9 @@ describe('', () => {
} as unknown as ContentGeneratorConfig);
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue('Premium Plan');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Signed in with Google: test@example.com');
@@ -135,10 +132,9 @@ describe('', () => {
{} as unknown as ContentGeneratorConfig,
);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
@@ -152,10 +148,9 @@ describe('', () => {
} as unknown as ContentGeneratorConfig);
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue(undefined);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain(`Authenticated with ${AuthType.USE_GEMINI}`);
@@ -172,10 +167,9 @@ describe('', () => {
} as unknown as ContentGeneratorConfig);
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue('Enterprise Tier');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Plan: Enterprise Tier');
@@ -191,10 +185,9 @@ describe('', () => {
} as unknown as ContentGeneratorConfig);
vi.spyOn(mockConfig, 'getUserTierName').mockReturnValue('Advanced Ultra');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Plan: Advanced Ultra');
diff --git a/packages/cli/src/ui/components/ValidationDialog.test.tsx b/packages/cli/src/ui/components/ValidationDialog.test.tsx
index 51fcacd220..11e559ebfd 100644
--- a/packages/cli/src/ui/components/ValidationDialog.test.tsx
+++ b/packages/cli/src/ui/components/ValidationDialog.test.tsx
@@ -68,10 +68,9 @@ describe('ValidationDialog', () => {
describe('initial render (choosing state)', () => {
it('should render the main message and two options', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(
'Further action is required to use this service.',
@@ -97,13 +96,12 @@ describe('ValidationDialog', () => {
});
it('should render learn more URL when provided', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Learn more:');
expect(lastFrame()).toContain('https://example.com/help');
@@ -111,10 +109,9 @@ describe('ValidationDialog', () => {
});
it('should call onChoice with cancel when ESCAPE is pressed', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
// Verify the keypress hook is active
expect(mockKeypressOptions.isActive).toBe(true);
@@ -143,10 +140,9 @@ describe('ValidationDialog', () => {
describe('onChoice handling', () => {
it('should call onChoice with change_auth when that option is selected', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = (RadioButtonSelect as Mock).mock.calls[0][0].onSelect;
await act(async () => {
@@ -159,10 +155,9 @@ describe('ValidationDialog', () => {
});
it('should call onChoice with verify when no validation link is provided', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = (RadioButtonSelect as Mock).mock.calls[0][0].onSelect;
await act(async () => {
@@ -175,13 +170,12 @@ describe('ValidationDialog', () => {
});
it('should open browser and transition to waiting state when verify is selected with a link', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = (RadioButtonSelect as Mock).mock.calls[0][0].onSelect;
await act(async () => {
@@ -201,13 +195,12 @@ describe('ValidationDialog', () => {
it('should show URL in message when browser cannot be launched', async () => {
mockShouldLaunchBrowser.mockReturnValue(false);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = (RadioButtonSelect as Mock).mock.calls[0][0].onSelect;
await act(async () => {
@@ -226,13 +219,12 @@ describe('ValidationDialog', () => {
it('should show error and options when browser fails to open', async () => {
mockOpenBrowserSecurely.mockRejectedValue(new Error('Browser not found'));
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelect = (RadioButtonSelect as Mock).mock.calls[0][0].onSelect;
await act(async () => {
diff --git a/packages/cli/src/ui/components/__snapshots__/ConfigInitDisplay.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/ConfigInitDisplay.test.tsx.snap
index 8d03baaa49..28929deee5 100644
--- a/packages/cli/src/ui/components/__snapshots__/ConfigInitDisplay.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/ConfigInitDisplay.test.tsx.snap
@@ -18,8 +18,20 @@ Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
"
`;
+exports[`ConfigInitDisplay > truncates list of waiting servers if too many 2`] = `
+"
+Spinner Connecting to MCP servers... (0/5) - Waiting for: s1, s2, s3, +2 more
+"
+`;
+
exports[`ConfigInitDisplay > updates message on McpClientUpdate event 1`] = `
"
Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
"
`;
+
+exports[`ConfigInitDisplay > updates message on McpClientUpdate event 2`] = `
+"
+Spinner Connecting to MCP servers... (1/2) - Waiting for: server2
+"
+`;
diff --git a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx
index c86aafc0ce..ac645d312c 100644
--- a/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/CompressionMessage.test.tsx
@@ -29,10 +29,9 @@ describe('', () => {
describe('pending state', () => {
it('renders pending message when compression is in progress', async () => {
const props = createCompressionProps({ isPending: true });
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Compressing chat history');
@@ -48,10 +47,9 @@ describe('', () => {
newTokenCount: 50,
compressionStatus: CompressionStatus.COMPRESSED,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -73,9 +71,9 @@ describe('', () => {
newTokenCount: newTokens,
compressionStatus: CompressionStatus.COMPRESSED,
});
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders();
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ );
const output = lastFrame();
expect(output).toContain('✦');
@@ -98,10 +96,9 @@ describe('', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -119,10 +116,9 @@ describe('', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain(
@@ -158,9 +154,9 @@ describe('', () => {
newTokenCount: newTokens,
compressionStatus: CompressionStatus.COMPRESSED,
});
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders();
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ );
const output = lastFrame();
expect(output).toContain(expected);
@@ -182,9 +178,9 @@ describe('', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders();
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ );
const output = lastFrame();
expect(output).toContain(
@@ -209,9 +205,9 @@ describe('', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
});
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders();
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ );
const output = lastFrame();
expect(output).toContain('compression did not reduce size');
@@ -228,10 +224,9 @@ describe('', () => {
isPending: false,
compressionStatus: CompressionStatus.COMPRESSION_FAILED_EMPTY_SUMMARY,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('✦');
@@ -247,10 +242,9 @@ describe('', () => {
compressionStatus:
CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain(
diff --git a/packages/cli/src/ui/components/messages/ErrorMessage.test.tsx b/packages/cli/src/ui/components/messages/ErrorMessage.test.tsx
index 928266a266..f0df1d173d 100644
--- a/packages/cli/src/ui/components/messages/ErrorMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ErrorMessage.test.tsx
@@ -10,10 +10,9 @@ import { describe, it, expect } from 'vitest';
describe('ErrorMessage', () => {
it('renders with the correct prefix and text', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -22,10 +21,9 @@ describe('ErrorMessage', () => {
it('renders multiline error messages', async () => {
const message = 'Error line 1\nError line 2';
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/messages/GeminiMessage.test.tsx b/packages/cli/src/ui/components/messages/GeminiMessage.test.tsx
index 59150e988c..b02eab67ba 100644
--- a/packages/cli/src/ui/components/messages/GeminiMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/GeminiMessage.test.tsx
@@ -24,13 +24,12 @@ describe(' - Raw Markdown Display Snapshots', () => {
])(
'renders with renderMarkdown=$renderMarkdown $description',
async ({ renderMarkdown }) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
@@ -39,13 +38,12 @@ describe(' - Raw Markdown Display Snapshots', () => {
it.each([{ renderMarkdown: true }, { renderMarkdown: false }])(
'renders pending state with renderMarkdown=$renderMarkdown',
async ({ renderMarkdown }) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
uiState: { renderMarkdown, streamingState: StreamingState.Idle },
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
@@ -55,7 +53,7 @@ describe(' - Raw Markdown Display Snapshots', () => {
const terminalWidth = 20;
const text =
'This is a long line that should wrap correctly without truncation';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
- Raw Markdown Display Snapshots', () => {
uiState: { renderMarkdown: false, streamingState: StreamingState.Idle },
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/components/messages/InfoMessage.test.tsx b/packages/cli/src/ui/components/messages/InfoMessage.test.tsx
index 3b47e729ad..80a0c1a11c 100644
--- a/packages/cli/src/ui/components/messages/InfoMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/InfoMessage.test.tsx
@@ -10,10 +10,9 @@ import { describe, it, expect } from 'vitest';
describe('InfoMessage', () => {
it('renders with the correct default prefix and text', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -21,10 +20,9 @@ describe('InfoMessage', () => {
});
it('renders with a custom icon', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -33,10 +31,7 @@ describe('InfoMessage', () => {
it('renders multiline info messages', async () => {
const message = 'Info line 1\nInfo line 2';
- const { lastFrame, waitUntilReady, unmount } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
const output = lastFrame();
expect(output).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx b/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx
index a236be80ba..68e8ae6ebe 100644
--- a/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx
+++ b/packages/cli/src/ui/components/messages/RedirectionConfirmation.test.tsx
@@ -33,7 +33,7 @@ describe('ToolConfirmationMessage Redirection', () => {
rootCommands: ['echo'],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={100}
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
index 6135d3574e..a5981e4e2d 100644
--- a/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ShellToolMessage.test.tsx
@@ -170,11 +170,10 @@ describe('', () => {
},
],
])('%s', async (_, props, options) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ uiActions, ...options },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -219,31 +218,29 @@ describe('', () => {
focused,
constrainHeight,
) => {
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- {
- uiActions,
- config: makeFakeConfig({ useAlternateBuffer: true }),
- settings: createMockSettings({
- ui: { useAlternateBuffer: true },
- }),
- uiState: {
- activePtyId: focused ? 1 : 2,
- embeddedShellFocused: focused,
- constrainHeight,
- },
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ {
+ uiActions,
+ config: makeFakeConfig({ useAlternateBuffer: true }),
+ settings: createMockSettings({
+ ui: { useAlternateBuffer: true },
+ }),
+ uiState: {
+ activePtyId: focused ? 1 : 2,
+ embeddedShellFocused: focused,
+ constrainHeight,
},
- );
+ },
+ );
- await waitUntilReady();
const frame = lastFrame();
expect(frame.match(/Line \d+/g)?.length).toBe(expectedMaxLines);
expect(frame).toMatchSnapshot();
@@ -276,7 +273,7 @@ describe('', () => {
});
it('fully expands in alternate buffer mode when constrainHeight is false and isExpandable is true', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
await waitFor(() => {
const frame = lastFrame();
// Should show all 100 lines because constrainHeight is false and isExpandable is true
@@ -306,7 +302,7 @@ describe('', () => {
});
it('stays constrained in alternate buffer mode when isExpandable is false even if constrainHeight is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
await waitFor(() => {
const frame = lastFrame();
// Should still be constrained to 12 (15 - 3) because isExpandable is false
diff --git a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx
index 757ec24654..9279d98f66 100644
--- a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx
+++ b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx
@@ -83,11 +83,7 @@ describe('', () => {
});
it('renders collapsed view by default with correct agent counts and states', async () => {
- const { lastFrame, waitUntilReady } = await renderSubagentGroup(
- mockToolCalls,
- 40,
- );
- await waitUntilReady();
+ const { lastFrame } = await renderSubagentGroup(mockToolCalls, 40);
expect(lastFrame()).toMatchSnapshot();
});
diff --git a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx
index f2c57f9662..955c4a5f8a 100644
--- a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx
+++ b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx
@@ -35,10 +35,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -59,10 +58,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -81,10 +79,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -103,10 +100,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -127,10 +123,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -148,10 +143,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -163,10 +157,9 @@ describe('', () => {
state: 'cancelled',
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
@@ -184,10 +177,9 @@ describe('', () => {
],
};
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/messages/Todo.test.tsx b/packages/cli/src/ui/components/messages/Todo.test.tsx
index 17c4f623bf..91782bdc19 100644
--- a/packages/cli/src/ui/components/messages/Todo.test.tsx
+++ b/packages/cli/src/ui/components/messages/Todo.test.tsx
@@ -32,12 +32,11 @@ describe.each([true, false])(
' (showFullTodos: %s)',
async (showFullTodos: boolean) => {
const renderWithUiState = async (uiState: Partial) => {
- const result = render(
+ const result = await render(
,
);
- await result.waitUntilReady();
return result;
};
@@ -91,7 +90,7 @@ describe.each([true, false])(
});
it('renders a todo list with long descriptions that wrap when full view is on', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
index 5398f2c23f..1759b0484c 100644
--- a/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolConfirmationMessage.test.tsx
@@ -50,7 +50,7 @@ describe('ToolConfirmationMessage', () => {
urls: ['https://example.com'],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -77,7 +76,7 @@ describe('ToolConfirmationMessage', () => {
],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -101,7 +99,7 @@ describe('ToolConfirmationMessage', () => {
urls: ['https://täst.com'],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
);
- await waitUntilReady();
-
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
expect(output).toContain('Original: https://täst.com');
@@ -132,7 +128,7 @@ describe('ToolConfirmationMessage', () => {
rootCommands: ['curl'],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
);
- await waitUntilReady();
-
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
expect(output).toContain('Original: https://еxample.com/');
@@ -163,7 +157,7 @@ describe('ToolConfirmationMessage', () => {
rootCommands: ['curl'],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
);
- await waitUntilReady();
-
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
// It should extract "https://еxample.com" and NOT "https://еxample.com;ls"
@@ -193,7 +185,7 @@ describe('ToolConfirmationMessage', () => {
urls: ['https://еxample.com', 'https://täst.com'],
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
/>,
);
- await waitUntilReady();
-
const output = lastFrame();
expect(output).toContain('Deceptive URL(s) detected');
expect(output).toContain('Original: https://еxample.com/');
@@ -223,7 +213,7 @@ describe('ToolConfirmationMessage', () => {
commands: ['echo "hello"', 'ls -la', 'whoami'], // Multi-command list
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('echo "hello"');
@@ -336,18 +325,16 @@ describe('ToolConfirmationMessage', () => {
getIdeMode: () => false,
getDisableAlwaysAllow: () => false,
} as unknown as Config;
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ );
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -360,18 +347,16 @@ describe('ToolConfirmationMessage', () => {
getDisableAlwaysAllow: () => false,
} as unknown as Config;
- const { lastFrame, waitUntilReady, unmount } =
- await renderWithProviders(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ );
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -396,7 +381,7 @@ describe('ToolConfirmationMessage', () => {
getIdeMode: () => false,
getDisableAlwaysAllow: () => false,
} as unknown as Config;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
}),
},
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('Allow for all future sessions');
unmount();
@@ -423,7 +407,7 @@ describe('ToolConfirmationMessage', () => {
getIdeMode: () => false,
getDisableAlwaysAllow: () => false,
} as unknown as Config;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
}),
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('future sessions');
@@ -471,7 +454,7 @@ describe('ToolConfirmationMessage', () => {
isDiffingEnabled: false,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Modify with external editor');
unmount();
@@ -499,7 +481,7 @@ describe('ToolConfirmationMessage', () => {
isDiffingEnabled: false,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Modify with external editor');
unmount();
@@ -527,7 +508,7 @@ describe('ToolConfirmationMessage', () => {
isDiffingEnabled: true,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('Modify with external editor');
unmount();
@@ -554,7 +534,7 @@ describe('ToolConfirmationMessage', () => {
onConfirm: vi.fn(),
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
const output = lastFrame();
// BiDi characters \u202E and \u202D should be stripped
@@ -600,7 +579,7 @@ describe('ToolConfirmationMessage', () => {
onConfirm: vi.fn(),
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('MCP Tool Details:');
@@ -632,7 +610,7 @@ describe('ToolConfirmationMessage', () => {
onConfirm: vi.fn(),
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('MCP Tool Details:');
@@ -677,7 +654,7 @@ describe('ToolConfirmationMessage', () => {
urls: ['https://example.com'],
};
- const { stdin, waitUntilReady, unmount } = await renderWithProviders(
+ const { stdin, unmount } = await renderWithProviders(
{
terminalWidth={80}
/>,
);
- await waitUntilReady();
stdin.write('\x1b');
diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
index 6b249fc288..4240bc3b86 100644
--- a/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolGroupMessage.test.tsx
@@ -75,7 +75,7 @@ describe('', () => {
it('renders single successful tool call', async () => {
const toolCalls = [createToolCall()];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -90,7 +90,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -109,13 +108,12 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
// Should now hide confirming tools (to avoid duplication with Global Queue)
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -130,12 +128,11 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot('canceled_tool');
unmount();
@@ -164,7 +161,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -180,7 +177,6 @@ describe('', () => {
},
);
// pending-tool should now be visible
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('successful-tool');
expect(output).toContain('pending-tool');
@@ -205,7 +201,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -219,7 +215,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('successful-tool');
expect(output).not.toContain('error-tool');
@@ -238,7 +233,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -253,7 +248,6 @@ describe('', () => {
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('client-error-tool');
unmount();
@@ -282,7 +276,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -298,7 +292,6 @@ describe('', () => {
},
);
// write_file (Pending) should now be visible
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('read_file');
expect(output).toContain('run_shell_command');
@@ -324,7 +317,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -358,7 +350,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -386,7 +377,7 @@ describe('', () => {
it('renders empty tool calls array', async () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -401,7 +392,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -423,7 +413,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
@@ -440,7 +430,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -456,7 +445,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -471,7 +460,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -496,7 +484,7 @@ describe('', () => {
];
const item2 = createItem(toolCalls2);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -541,7 +528,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -556,7 +543,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -571,7 +557,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{
config: baseMockConfig,
@@ -586,7 +572,6 @@ describe('', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -609,7 +594,7 @@ describe('', () => {
}),
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -676,17 +660,10 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } =
- await renderWithProviders(
- ,
- { config: baseMockConfig, settings: fullVerbositySettings },
- );
- await waitUntilReady();
-
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ { config: baseMockConfig, settings: fullVerbositySettings },
+ );
if (shouldHide) {
expect(lastFrame({ allowEmpty: true })).toBe('');
} else {
@@ -711,12 +688,11 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ config: baseMockConfig, settings: fullVerbositySettings },
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
@@ -734,7 +710,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
{ config: baseMockConfig, settings: fullVerbositySettings },
);
// AskUser tools in progress are rendered by AskUserDialog, so we expect nothing.
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -761,7 +736,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -784,7 +758,7 @@ describe('', () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
@@ -815,7 +788,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -848,7 +820,7 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
@@ -943,7 +914,7 @@ describe('', () => {
const toolCalls = [visibleTool, ...hiddenTools];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('visible-tool');
expect(output).not.toContain('hidden-error-0');
@@ -969,7 +939,7 @@ describe('', () => {
const toolCalls: IndividualToolCallDisplay[] = [];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
},
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).not.toBe('');
unmount();
});
@@ -1016,17 +985,10 @@ describe('', () => {
];
const item = createItem(toolCalls);
- const { lastFrame, unmount, waitUntilReady } =
- await renderWithProviders(
- ,
- { config: baseMockConfig, settings: fullVerbositySettings },
- );
-
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(
+ ,
+ { config: baseMockConfig, settings: fullVerbositySettings },
+ );
if (visible) {
expect(lastFrame()).toContain(name);
diff --git a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx
index 93f64815a3..74bb47058b 100644
--- a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx
@@ -78,11 +78,10 @@ describe('', () => {
});
it('renders basic tool information', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
unmount();
@@ -91,7 +90,7 @@ describe('', () => {
describe('JSON rendering', () => {
it('pretty prints valid JSON', async () => {
const testJSONstring = '{"a": 1, "b": [2, 3]}';
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Idle,
);
- await waitUntilReady();
const output = lastFrame();
@@ -113,11 +111,10 @@ describe('', () => {
});
it('renders pretty JSON in ink frame', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
const frame = lastFrame();
@@ -127,7 +124,7 @@ describe('', () => {
it('uses JSON renderer even when renderOutputAsMarkdown=true is true', async () => {
const testJSONstring = '{"a": 1, "b": [2, 3]}';
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Idle,
);
- await waitUntilReady();
const output = lastFrame();
@@ -149,7 +145,7 @@ describe('', () => {
});
it('falls back to plain text for malformed JSON', async () => {
const testJSONstring = 'a": 1, "b": [2, 3]}';
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Idle,
);
- await waitUntilReady();
const output = lastFrame();
@@ -168,7 +163,7 @@ describe('', () => {
it('rejects mixed text + JSON renders as plain text', async () => {
const testJSONstring = `{"result": "count": 42,"items": ["apple", "banana"]},"meta": {"timestamp": "2025-09-28T12:34:56Z"}}End.`;
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Idle,
);
- await waitUntilReady();
const output = lastFrame();
@@ -188,7 +182,7 @@ describe('', () => {
it('rejects ANSI-tained JSON renders as plain text', async () => {
const testJSONstring =
'\u001b[32mOK\u001b[0m {"status": "success", "data": {"id": 123, "values": [10, 20, 30]}}';
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Idle,
);
- await waitUntilReady();
const output = lastFrame();
@@ -207,7 +200,7 @@ describe('', () => {
it('pretty printing 10kb JSON completes in <50ms', async () => {
const large = '{"key": "' + 'x'.repeat(10000) + '"}';
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Idle,
);
- await waitUntilReady();
const start = performance.now();
lastFrame();
@@ -226,84 +218,76 @@ describe('', () => {
describe('ToolStatusIndicator rendering', () => {
it('shows ✓ for Success status', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows o for Pending status', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows ? for Confirming status', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows - for Canceled status', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows x for Error status', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows paused spinner for Executing status when streamingState is Idle', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows paused spinner for Executing status when streamingState is WaitingForConfirmation', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.WaitingForConfirmation,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('shows MockRespondingSpinner for Executing status when streamingState is Responding', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Responding, // Simulate app still responding
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -317,11 +301,10 @@ describe('', () => {
newContent: 'new',
filePath: 'file.txt',
};
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
// Check that the output contains the MockDiff content as part of the whole message
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -372,17 +355,16 @@ describe('', () => {
},
],
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
,
StreamingState.Idle,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders McpProgressIndicator with percentage and message for executing tools', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('42%');
expect(output).toContain('Working on it...');
@@ -404,7 +385,7 @@ describe('', () => {
});
it('renders only percentage when progressMessage is missing', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('75%');
expect(output).toContain('\u2588');
@@ -424,7 +404,7 @@ describe('', () => {
});
it('renders indeterminate progress when total is missing', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithContext(
+ const { lastFrame, unmount } = await renderWithContext(
', () => {
/>,
StreamingState.Responding,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('7');
expect(output).toContain('\u2588');
@@ -449,7 +428,7 @@ describe('', () => {
(_, i) => `Line ${i + 1}`,
).join('\n');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
},
);
- await waitUntilReady();
const output = lastFrame();
// Since kind=Kind.Agent and availableTerminalHeight is provided, it should truncate to SUBAGENT_MAX_LINES (15)
@@ -486,7 +464,7 @@ describe('', () => {
(_, i) => `Line ${i + 1}`,
).join('\n');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Line 1');
@@ -516,7 +493,7 @@ describe('', () => {
(_, i) => `Line ${i + 1}`,
).join('\n');
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
},
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Line 1');
diff --git a/packages/cli/src/ui/components/messages/ToolMessageFocusHint.test.tsx b/packages/cli/src/ui/components/messages/ToolMessageFocusHint.test.tsx
index b9145068a1..955a1bceab 100644
--- a/packages/cli/src/ui/components/messages/ToolMessageFocusHint.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolMessageFocusHint.test.tsx
@@ -70,7 +70,6 @@ describe('Focus Hint', () => {
,
{ uiState: { streamingState: StreamingState.Idle } },
);
- await waitUntilReady();
// Initially, no focus hint
expect(lastFrame()).toMatchSnapshot('initial-no-output');
@@ -92,7 +91,6 @@ describe('Focus Hint', () => {
,
{ uiState: { streamingState: StreamingState.Idle } },
);
- await waitUntilReady();
// Initially, no focus hint
expect(lastFrame()).toMatchSnapshot('initial-with-output');
@@ -119,7 +117,6 @@ describe('Focus Hint', () => {
/>,
{ uiState: { streamingState: StreamingState.Idle } },
);
- await waitUntilReady();
await act(async () => {
vi.advanceTimersByTime(SHELL_FOCUS_HINT_DELAY_MS + 100);
diff --git a/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx b/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx
index cf72eaaab2..10e26855e8 100644
--- a/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolMessageRawMarkdown.test.tsx
@@ -64,7 +64,7 @@ describe(' - Raw Markdown Display Snapshots', () => {
])(
'renders with renderMarkdown=$renderMarkdown, useAlternateBuffer=$useAlternateBuffer $description',
async ({ renderMarkdown, useAlternateBuffer, availableTerminalHeight }) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
- Raw Markdown Display Snapshots', () => {
settings: createMockSettings({ ui: { useAlternateBuffer } }),
},
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
},
diff --git a/packages/cli/src/ui/components/messages/ToolShared.test.tsx b/packages/cli/src/ui/components/messages/ToolShared.test.tsx
index d31e86216a..d9fa58e215 100644
--- a/packages/cli/src/ui/components/messages/ToolShared.test.tsx
+++ b/packages/cli/src/ui/components/messages/ToolShared.test.tsx
@@ -15,30 +15,27 @@ vi.mock('../GeminiRespondingSpinner.js', () => ({
describe('McpProgressIndicator', () => {
it('renders determinate progress at 50%', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
expect(output).toContain('50%');
});
it('renders complete progress at 100%', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
expect(output).toContain('100%');
});
it('renders indeterminate progress with raw count', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
expect(output).toContain('7');
@@ -46,7 +43,7 @@ describe('McpProgressIndicator', () => {
});
it('renders progress with a message', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
{
barWidth={20}
/>,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
expect(output).toContain('Downloading...');
});
it('clamps progress exceeding total to 100%', async () => {
- const { lastFrame, waitUntilReady } = render(
+ const { lastFrame } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('100%');
expect(output).not.toContain('150%');
diff --git a/packages/cli/src/ui/components/messages/UserMessage.test.tsx b/packages/cli/src/ui/components/messages/UserMessage.test.tsx
index 2f24a9feb0..f0efd90949 100644
--- a/packages/cli/src/ui/components/messages/UserMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/UserMessage.test.tsx
@@ -15,11 +15,10 @@ vi.mock('../../utils/commandUtils.js', () => ({
describe('UserMessage', () => {
it('renders normal user message with correct prefix', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width: 80 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -27,11 +26,10 @@ describe('UserMessage', () => {
});
it('renders slash command message', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width: 80 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -40,11 +38,10 @@ describe('UserMessage', () => {
it('renders multiline user message', async () => {
const message = 'Line 1\nLine 2';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width: 80 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -53,11 +50,10 @@ describe('UserMessage', () => {
it('transforms image paths in user message', async () => {
const message = 'Check out this image: @/path/to/my-image.png';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width: 80 },
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('[Image my-image.png]');
diff --git a/packages/cli/src/ui/components/messages/WarningMessage.test.tsx b/packages/cli/src/ui/components/messages/WarningMessage.test.tsx
index 824c12f77a..48fe6a22fc 100644
--- a/packages/cli/src/ui/components/messages/WarningMessage.test.tsx
+++ b/packages/cli/src/ui/components/messages/WarningMessage.test.tsx
@@ -10,10 +10,9 @@ import { describe, it, expect } from 'vitest';
describe('WarningMessage', () => {
it('renders with the correct prefix and text', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
@@ -22,10 +21,9 @@ describe('WarningMessage', () => {
it('renders multiline warning messages', async () => {
const message = 'Warning line 1\nWarning line 2';
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx b/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx
index d68cc40446..0501667d1f 100644
--- a/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx
+++ b/packages/cli/src/ui/components/shared/BaseSelectionList.test.tsx
@@ -78,7 +78,6 @@ describe('BaseSelectionList', () => {
const result = await renderWithProviders(
,
);
- await result.waitUntilReady();
return result;
};
@@ -313,7 +312,6 @@ describe('BaseSelectionList', () => {
const { rerender, lastFrame, waitUntilReady, unmount } =
await renderWithProviders();
- await waitUntilReady();
// Function to simulate the activeIndex changing over time
const updateActiveIndex = async (newIndex: number) => {
diff --git a/packages/cli/src/ui/components/shared/EnumSelector.test.tsx b/packages/cli/src/ui/components/shared/EnumSelector.test.tsx
index 83f0b722b6..aeadcaa4a9 100644
--- a/packages/cli/src/ui/components/shared/EnumSelector.test.tsx
+++ b/packages/cli/src/ui/components/shared/EnumSelector.test.tsx
@@ -25,7 +25,7 @@ const NUMERIC_OPTIONS: readonly SettingEnumOption[] = [
describe('', () => {
it('renders with string options and matches snapshot', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders with numeric options and matches snapshot', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders inactive state and matches snapshot', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -70,7 +67,7 @@ describe('', () => {
const singleOption: readonly SettingEnumOption[] = [
{ label: 'Only Option', value: 'only' },
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders nothing when no options are provided', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('handles currentValue not found in options', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
// Should default to first option
expect(lastFrame()).toContain('English');
unmount();
@@ -122,7 +116,6 @@ describe('', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('English');
await act(async () => {
@@ -141,7 +134,7 @@ describe('', () => {
});
it('shows navigation arrows when multiple options available', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('←');
expect(lastFrame()).toContain('→');
unmount();
@@ -159,7 +151,7 @@ describe('', () => {
const singleOption: readonly SettingEnumOption[] = [
{ label: 'Only Option', value: 'only' },
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
onValueChange={async () => {}}
/>,
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('←');
expect(lastFrame()).not.toContain('→');
unmount();
diff --git a/packages/cli/src/ui/components/shared/ExpandableText.test.tsx b/packages/cli/src/ui/components/shared/ExpandableText.test.tsx
index 00c82a009d..d25b6b0175 100644
--- a/packages/cli/src/ui/components/shared/ExpandableText.test.tsx
+++ b/packages/cli/src/ui/components/shared/ExpandableText.test.tsx
@@ -13,7 +13,7 @@ describe('ExpandableText', () => {
const flat = (s: string | undefined) => (s ?? '').replace(/\n/g, '');
it('renders plain label when no match (short label)', async () => {
- const renderResult = render(
+ const renderResult = await render(
{
isExpanded={false}
/>,
);
- const { waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { unmount } = renderResult;
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
it('truncates long label when collapsed and no match', async () => {
const long = 'x'.repeat(MAX_WIDTH + 25);
- const renderResult = render(
+ const renderResult = await render(
{
isExpanded={false}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { lastFrame, unmount } = renderResult;
const out = lastFrame();
const f = flat(out);
expect(f.endsWith('...')).toBe(true);
@@ -50,7 +48,7 @@ describe('ExpandableText', () => {
it('shows full long label when expanded and no match', async () => {
const long = 'y'.repeat(MAX_WIDTH + 25);
- const renderResult = render(
+ const renderResult = await render(
{
isExpanded={true}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { lastFrame, unmount } = renderResult;
const out = lastFrame();
const f = flat(out);
expect(f.length).toBe(long.length);
@@ -71,7 +68,7 @@ describe('ExpandableText', () => {
const label = 'run: git commit -m "feat: add search"';
const userInput = 'commit';
const matchedIndex = label.indexOf(userInput);
- const renderResult = render(
+ const renderResult = await render(
{
/>,
100,
);
- const { waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { unmount } = renderResult;
await expect(renderResult).toMatchSvgSnapshot();
unmount();
});
@@ -93,7 +89,7 @@ describe('ExpandableText', () => {
const suffix = '/and/then/some/more/components/'.repeat(3);
const label = prefix + core + suffix;
const matchedIndex = prefix.length;
- const renderResult = render(
+ const renderResult = await render(
{
/>,
100,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { lastFrame, unmount } = renderResult;
const out = lastFrame();
const f = flat(out);
expect(f.includes(core)).toBe(true);
@@ -120,7 +115,7 @@ describe('ExpandableText', () => {
const suffix = ' in this text';
const label = prefix + core + suffix;
const matchedIndex = prefix.length;
- const renderResult = render(
+ const renderResult = await render(
{
isExpanded={false}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { lastFrame, unmount } = renderResult;
const out = lastFrame();
const f = flat(out);
expect(f.includes('...')).toBe(true);
@@ -144,7 +138,7 @@ describe('ExpandableText', () => {
it('respects custom maxWidth', async () => {
const customWidth = 50;
const long = 'z'.repeat(100);
- const renderResult = render(
+ const renderResult = await render(
{
maxWidth={customWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { lastFrame, unmount } = renderResult;
const out = lastFrame();
const f = flat(out);
expect(f.endsWith('...')).toBe(true);
diff --git a/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx b/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx
index cc299a44ad..b81294ffb2 100644
--- a/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx
+++ b/packages/cli/src/ui/components/shared/HalfLinePaddedBox.test.tsx
@@ -28,13 +28,12 @@ describe('', () => {
it('renders standard background and blocks when not iTerm2', async () => {
vi.mocked(isITerm2).mockReturnValue(false);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
Content
,
{ width: 10 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -44,13 +43,12 @@ describe('', () => {
it('renders iTerm2-specific blocks when iTerm2 is detected', async () => {
vi.mocked(isITerm2).mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
Content
,
{ width: 10 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -58,7 +56,7 @@ describe('', () => {
});
it('renders nothing when useBackgroundColor is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
', () => {
,
{ width: 10 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
@@ -78,13 +75,12 @@ describe('', () => {
it('renders nothing when screen reader is enabled', async () => {
mockUseIsScreenReaderEnabled.mockReturnValue(true);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
Content
,
{ width: 10 },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/shared/MaxSizedBox.test.tsx b/packages/cli/src/ui/components/shared/MaxSizedBox.test.tsx
index 049ba35f43..a63ae59628 100644
--- a/packages/cli/src/ui/components/shared/MaxSizedBox.test.tsx
+++ b/packages/cli/src/ui/components/shared/MaxSizedBox.test.tsx
@@ -23,7 +23,7 @@ describe('', () => {
});
it('renders children without truncation when they fit', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -42,7 +42,7 @@ describe('', () => {
});
it('hides lines when content exceeds maxHeight', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -65,7 +65,7 @@ describe('', () => {
});
it('hides lines at the end when content exceeds maxHeight and overflowDirection is bottom', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -88,7 +88,7 @@ describe('', () => {
});
it('shows plural "lines" when more than one line is hidden', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -111,7 +111,7 @@ describe('', () => {
});
it('shows singular "line" when exactly one line is hidden', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -132,7 +132,7 @@ describe('', () => {
});
it('accounts for additionalHiddenLinesCount', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -155,7 +155,7 @@ describe('', () => {
});
it('wraps text that exceeds maxWidth', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -175,7 +175,7 @@ describe('', () => {
});
it('does not truncate when maxHeight is undefined', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -195,7 +195,7 @@ describe('', () => {
});
it('renders an empty box for empty children', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
@@ -209,7 +209,7 @@ describe('', () => {
});
it('handles React.Fragment as a child', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -236,7 +236,7 @@ describe('', () => {
{ length: 30 },
(_, i) => `Line ${i + 1}`,
).join('\n');
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
@@ -262,7 +262,7 @@ describe('', () => {
{ length: 30 },
(_, i) => `Line ${i + 1}`,
).join('\n');
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
diff --git a/packages/cli/src/ui/components/shared/Scrollable.test.tsx b/packages/cli/src/ui/components/shared/Scrollable.test.tsx
index 279fa93a63..7d086e44c1 100644
--- a/packages/cli/src/ui/components/shared/Scrollable.test.tsx
+++ b/packages/cli/src/ui/components/shared/Scrollable.test.tsx
@@ -29,25 +29,23 @@ describe('', () => {
});
it('renders children', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
Hello World
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Hello World');
unmount();
});
it('renders multiple children', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
Line 1
Line 2
Line 3
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Line 1');
expect(lastFrame()).toContain('Line 2');
expect(lastFrame()).toContain('Line 3');
@@ -55,14 +53,13 @@ describe('', () => {
});
it('matches snapshot', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
Line 1
Line 2
Line 3
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -77,7 +74,7 @@ describe('', () => {
},
);
- const { waitUntilReady, unmount } = await renderWithProviders(
+ const { unmount } = await renderWithProviders(
Line 1
Line 2
@@ -91,7 +88,6 @@ describe('', () => {
Line 10
,
);
- await waitUntilReady();
expect(capturedEntry).toBeDefined();
@@ -104,22 +100,20 @@ describe('', () => {
// Initial state with scrollToBottom={true}
unmount();
- const { waitUntilReady: waitUntilReady2, unmount: unmount2 } =
- await renderWithProviders(
-
- Line 1
- Line 2
- Line 3
- Line 4
- Line 5
- Line 6
- Line 7
- Line 8
- Line 9
- Line 10
- ,
- );
- await waitUntilReady2();
+ const { unmount: unmount2 } = await renderWithProviders(
+
+ Line 1
+ Line 2
+ Line 3
+ Line 4
+ Line 5
+ Line 6
+ Line 7
+ Line 8
+ Line 9
+ Line 10
+ ,
+ );
await waitFor(() => {
expect(capturedEntry?.getScrollState().scrollTop).toBe(5);
});
@@ -197,14 +191,13 @@ describe('', () => {
},
);
- const { stdin, waitUntilReady, unmount } = await renderWithProviders(
+ const { stdin, unmount, waitUntilReady } = await renderWithProviders(
Content
,
);
- await waitUntilReady();
// Ensure initial state using existing scrollBy method
await act(async () => {
diff --git a/packages/cli/src/ui/components/shared/SearchableList.test.tsx b/packages/cli/src/ui/components/shared/SearchableList.test.tsx
index cc56edfb2b..0a24a46a84 100644
--- a/packages/cli/src/ui/components/shared/SearchableList.test.tsx
+++ b/packages/cli/src/ui/components/shared/SearchableList.test.tsx
@@ -95,8 +95,7 @@ describe('SearchableList', () => {
};
it('should render all items initially', async () => {
- const { lastFrame, waitUntilReady } = await renderList();
- await waitUntilReady();
+ const { lastFrame } = await renderList();
const frame = lastFrame();
expect(frame).toContain('Test List');
@@ -109,10 +108,9 @@ describe('SearchableList', () => {
});
it('should reset selection to top when items change if resetSelectionOnItemsChange is true', async () => {
- const { lastFrame, stdin, waitUntilReady } = await renderList({
+ const { lastFrame, stdin } = await renderList({
resetSelectionOnItemsChange: true,
});
- await waitUntilReady();
await React.act(async () => {
stdin.write('\u001B[B'); // Down arrow
@@ -218,8 +216,7 @@ describe('SearchableList', () => {
});
it('should match snapshot', async () => {
- const { lastFrame, waitUntilReady } = await renderList();
- await waitUntilReady();
+ const { lastFrame } = await renderList();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/components/shared/SectionHeader.test.tsx b/packages/cli/src/ui/components/shared/SectionHeader.test.tsx
index 8d1d791cd3..f5174d8a8b 100644
--- a/packages/cli/src/ui/components/shared/SectionHeader.test.tsx
+++ b/packages/cli/src/ui/components/shared/SectionHeader.test.tsx
@@ -37,11 +37,10 @@ describe('', () => {
width: 40,
},
])('$description', async ({ title, subtitle, width }) => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ width },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
diff --git a/packages/cli/src/ui/components/shared/SlicingMaxSizedBox.test.tsx b/packages/cli/src/ui/components/shared/SlicingMaxSizedBox.test.tsx
index 184c968836..8cb69a4c5e 100644
--- a/packages/cli/src/ui/components/shared/SlicingMaxSizedBox.test.tsx
+++ b/packages/cli/src/ui/components/shared/SlicingMaxSizedBox.test.tsx
@@ -12,21 +12,20 @@ import { describe, it, expect } from 'vitest';
describe('', () => {
it('renders string data without slicing when it fits', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
{(truncatedData) => {truncatedData}}
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Hello World');
unmount();
});
it('slices string data by characters when very long', async () => {
const veryLongString = 'A'.repeat(25000);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
);
- await waitUntilReady();
// 20000 characters + 3 for '...'
expect(lastFrame()).toContain('20003');
unmount();
@@ -45,7 +43,7 @@ describe('', () => {
it('slices string data by lines when maxLines is provided', async () => {
const multilineString = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
);
- await waitUntilReady();
// maxLines=3, so it should keep 3-1 = 2 lines
expect(lastFrame()).toContain('Line 1');
expect(lastFrame()).toContain('Line 2');
@@ -71,7 +68,7 @@ describe('', () => {
it('slices array data when maxLines is provided', async () => {
const dataArray = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
);
- await waitUntilReady();
// maxLines=3, so it should keep 3-1 = 2 items
expect(lastFrame()).toContain('Item 1');
expect(lastFrame()).toContain('Item 2');
@@ -103,7 +99,7 @@ describe('', () => {
it('does not slice when isAlternateBuffer is true', async () => {
const multilineString = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5';
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Line 5');
expect(lastFrame()).not.toContain('hidden');
unmount();
diff --git a/packages/cli/src/ui/components/shared/TabHeader.test.tsx b/packages/cli/src/ui/components/shared/TabHeader.test.tsx
index ad39b79b39..d5105255ab 100644
--- a/packages/cli/src/ui/components/shared/TabHeader.test.tsx
+++ b/packages/cli/src/ui/components/shared/TabHeader.test.tsx
@@ -17,22 +17,20 @@ const MOCK_TABS: Tab[] = [
describe('TabHeader', () => {
describe('rendering', () => {
it('renders null for single tab', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toBe('');
unmount();
});
it('renders all tab headers', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('Tab 1');
expect(frame).toContain('Tab 2');
@@ -42,10 +40,9 @@ describe('TabHeader', () => {
});
it('renders separators between tabs', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
// Should have 2 separators for 3 tabs
const separatorCount = (frame?.match(/│/g) || []).length;
@@ -57,10 +54,9 @@ describe('TabHeader', () => {
describe('arrows', () => {
it('shows arrows by default', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('←');
expect(frame).toContain('→');
@@ -69,10 +65,9 @@ describe('TabHeader', () => {
});
it('hides arrows when showArrows is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).not.toContain('←');
expect(frame).not.toContain('→');
@@ -83,10 +78,9 @@ describe('TabHeader', () => {
describe('status icons', () => {
it('shows status icons by default', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
// Default uncompleted icon is □
expect(frame).toContain('□');
@@ -95,10 +89,9 @@ describe('TabHeader', () => {
});
it('hides status icons when showStatusIcons is false', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).not.toContain('□');
expect(frame).not.toContain('✓');
@@ -107,14 +100,13 @@ describe('TabHeader', () => {
});
it('shows checkmark for completed tabs', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
// Should have 2 checkmarks and 1 box
const checkmarkCount = (frame?.match(/✓/g) || []).length;
@@ -130,10 +122,9 @@ describe('TabHeader', () => {
{ key: '0', header: 'Tab 1' },
{ key: '1', header: 'Review', isSpecial: true },
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
// Special tab shows ≡ icon
expect(frame).toContain('≡');
@@ -146,10 +137,9 @@ describe('TabHeader', () => {
{ key: '0', header: 'Tab 1', statusIcon: '★' },
{ key: '1', header: 'Tab 2' },
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('★');
expect(frame).toMatchSnapshot();
@@ -158,14 +148,13 @@ describe('TabHeader', () => {
it('uses custom renderStatusIcon when provided', async () => {
const renderStatusIcon = () => '•';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
const bulletCount = (frame?.match(/•/g) || []).length;
expect(bulletCount).toBe(3);
@@ -178,10 +167,9 @@ describe('TabHeader', () => {
{ key: '0', header: 'ThisIsAVeryLongHeaderThatShouldBeTruncated' },
{ key: '1', header: 'AnotherVeryLongHeader' },
];
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
// Current tab (index 0) should NOT be truncated
@@ -197,14 +185,13 @@ describe('TabHeader', () => {
it('falls back to default when renderStatusIcon returns undefined', async () => {
const renderStatusIcon = () => undefined;
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(frame).toContain('□');
expect(frame).toMatchSnapshot();
diff --git a/packages/cli/src/ui/components/shared/TextInput.test.tsx b/packages/cli/src/ui/components/shared/TextInput.test.tsx
index a5bc79247c..6e2a183ff2 100644
--- a/packages/cli/src/ui/components/shared/TextInput.test.tsx
+++ b/packages/cli/src/ui/components/shared/TextInput.test.tsx
@@ -129,14 +129,13 @@ describe('TextInput', () => {
handleInput: vi.fn(),
setText: vi.fn(),
};
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('test');
unmount();
});
@@ -151,7 +150,7 @@ describe('TextInput', () => {
handleInput: vi.fn(),
setText: vi.fn(),
};
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
{
onCancel={onCancel}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toContain('testing');
unmount();
});
it('handles character input', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -197,10 +194,9 @@ describe('TextInput', () => {
it('handles backspace', async () => {
mockBuffer.setText('test');
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -229,10 +225,9 @@ describe('TextInput', () => {
it('handles left arrow', async () => {
mockBuffer.setText('test');
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -255,10 +250,9 @@ describe('TextInput', () => {
it('handles right arrow', async () => {
mockBuffer.setText('test');
mockBuffer.visualCursor[1] = 2; // Set initial cursor for right arrow test
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -279,10 +273,9 @@ describe('TextInput', () => {
it('calls onSubmit on return', async () => {
mockBuffer.setText('test');
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -306,10 +299,9 @@ describe('TextInput', () => {
const realContent = 'line1\nline2\nline3\nline4\nline5\nline6';
mockBuffer.setText(placeholder);
mockBuffer.pastedContent = { [placeholder]: realContent };
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -331,10 +323,9 @@ describe('TextInput', () => {
it('submits text unchanged when pastedContent is empty', async () => {
mockBuffer.setText('normal text');
mockBuffer.pastedContent = {};
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -355,10 +346,9 @@ describe('TextInput', () => {
it('calls onCancel on escape', async () => {
vi.useFakeTimers();
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -385,17 +375,16 @@ describe('TextInput', () => {
it('renders the input value', async () => {
mockBuffer.setText('secret');
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('secret');
unmount();
});
it('does not show cursor when not focused', async () => {
mockBuffer.setText('test');
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
{
onCancel={onCancel}
/>,
);
- await waitUntilReady();
expect(lastFrame()).not.toContain('\u001b[7m'); // Inverse video chalk
unmount();
});
@@ -412,10 +400,9 @@ describe('TextInput', () => {
mockBuffer.text = 'line1\nline2';
mockBuffer.viewportVisualLines = ['line1', 'line2'];
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('line1');
expect(lastFrame()).toContain('line2');
diff --git a/packages/cli/src/ui/components/shared/VirtualizedList.test.tsx b/packages/cli/src/ui/components/shared/VirtualizedList.test.tsx
index 60b8bfc421..75fcbd4633 100644
--- a/packages/cli/src/ui/components/shared/VirtualizedList.test.tsx
+++ b/packages/cli/src/ui/components/shared/VirtualizedList.test.tsx
@@ -59,7 +59,7 @@ describe('', () => {
])(
'renders only visible items ($name)',
async ({ initialScrollIndex, visible, notVisible }) => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
/>
,
);
- await waitUntilReady();
- const frame = lastFrame();
+ const output = lastFrame();
visible.forEach((item) => {
- expect(frame).toContain(item);
+ expect(output).toContain(item);
});
notVisible.forEach((item) => {
- expect(frame).not.toContain(item);
+ expect(output).not.toContain(item);
});
- expect(frame).toMatchSnapshot();
+ expect(output).toMatchSnapshot();
unmount();
},
);
it('sticks to bottom when new items added', async () => {
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, waitUntilReady, unmount } = await render(
', () => {
/>
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Item 99');
@@ -126,7 +124,7 @@ describe('', () => {
it('scrolls down to show new items when requested via ref', async () => {
const ref = createRef>();
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
', () => {
/>
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Item 0');
@@ -180,7 +177,7 @@ describe('', () => {
(_, i) => `Item ${i}`,
);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
/>
,
);
- await waitUntilReady();
const frame = lastFrame();
expect(mountedCount).toBe(expectedMountedCount);
@@ -262,8 +258,9 @@ describe('', () => {
return null;
};
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount, waitUntilReady } = await render(
+ ,
+ );
// Initially, only Item 0 (height 10) fills the 10px viewport
expect(lastFrame()).toContain('Item 0');
@@ -295,7 +292,7 @@ describe('', () => {
);
const keyExtractor = (item: string) => item;
- const { waitUntilReady, unmount } = render(
+ const { unmount, waitUntilReady } = await render(
', () => {
/>
,
);
- await waitUntilReady();
expect(ref.current?.getScrollState().scrollTop).toBe(0);
@@ -335,7 +331,7 @@ describe('', () => {
const longData = Array.from({ length: 100 }, (_, i) => `Item ${i}`);
// Use copy mode
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
', () => {
/>
,
);
- await waitUntilReady();
// Item 50 should be visible
expect(lastFrame()).toContain('Item 50');
diff --git a/packages/cli/src/ui/components/shared/performance.test.ts b/packages/cli/src/ui/components/shared/performance.test.ts
index 7768d0b9d4..c265ccae6b 100644
--- a/packages/cli/src/ui/components/shared/performance.test.ts
+++ b/packages/cli/src/ui/components/shared/performance.test.ts
@@ -14,9 +14,9 @@ describe('text-buffer performance', () => {
vi.restoreAllMocks();
});
- it('should handle pasting large amounts of text efficiently', () => {
+ it('should handle pasting large amounts of text efficiently', async () => {
const viewport = { width: 80, height: 24 };
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
}),
@@ -39,7 +39,7 @@ describe('text-buffer performance', () => {
expect(duration).toBeLessThan(5000);
});
- it('should handle character-by-character insertion in a large buffer efficiently', () => {
+ it('should handle character-by-character insertion in a large buffer efficiently', async () => {
const lines = 5000;
const initialText = Array.from(
{ length: lines },
@@ -47,7 +47,7 @@ describe('text-buffer performance', () => {
).join('\n');
const viewport = { width: 80, height: 24 };
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText,
viewport,
diff --git a/packages/cli/src/ui/components/shared/text-buffer.test.ts b/packages/cli/src/ui/components/shared/text-buffer.test.ts
index 1030dad377..32077b736a 100644
--- a/packages/cli/src/ui/components/shared/text-buffer.test.ts
+++ b/packages/cli/src/ui/components/shared/text-buffer.test.ts
@@ -89,7 +89,7 @@ describe('textBufferReducer', () => {
vi.restoreAllMocks();
});
- it('should return the initial state if state is undefined', () => {
+ it('should return the initial state if state is undefined', async () => {
const action = { type: 'unknown_action' } as unknown as TextBufferAction;
const state = textBufferReducer(initialState, action);
expect(state).toHaveOnlyValidCharacters();
@@ -98,7 +98,7 @@ describe('textBufferReducer', () => {
describe('Big Word Navigation Helpers', () => {
describe('findNextBigWordStartInLine (W)', () => {
- it('should skip non-whitespace and then whitespace', () => {
+ it('should skip non-whitespace and then whitespace', async () => {
expect(findNextBigWordStartInLine('hello world', 0)).toBe(6);
expect(findNextBigWordStartInLine('hello.world test', 0)).toBe(12);
expect(findNextBigWordStartInLine(' test', 0)).toBe(3);
@@ -107,7 +107,7 @@ describe('textBufferReducer', () => {
});
describe('findPrevBigWordStartInLine (B)', () => {
- it('should skip whitespace backwards then non-whitespace', () => {
+ it('should skip whitespace backwards then non-whitespace', async () => {
expect(findPrevBigWordStartInLine('hello world', 6)).toBe(0);
expect(findPrevBigWordStartInLine('hello.world test', 12)).toBe(0);
expect(findPrevBigWordStartInLine(' test', 3)).toBe(null); // At start of word
@@ -117,24 +117,24 @@ describe('textBufferReducer', () => {
});
describe('findBigWordEndInLine (E)', () => {
- it('should find end of current big word', () => {
+ it('should find end of current big word', async () => {
expect(findBigWordEndInLine('hello world', 0)).toBe(4);
expect(findBigWordEndInLine('hello.world test', 0)).toBe(10);
expect(findBigWordEndInLine('hello.world test', 11)).toBe(15);
});
- it('should skip whitespace if currently on whitespace', () => {
+ it('should skip whitespace if currently on whitespace', async () => {
expect(findBigWordEndInLine('hello world', 5)).toBe(12);
});
- it('should find next big word end if at end of current', () => {
+ it('should find next big word end if at end of current', async () => {
expect(findBigWordEndInLine('hello world', 4)).toBe(10);
});
});
});
describe('set_text action', () => {
- it('should set new text and move cursor to the end', () => {
+ it('should set new text and move cursor to the end', async () => {
const action: TextBufferAction = {
type: 'set_text',
payload: 'hello\nworld',
@@ -147,7 +147,7 @@ describe('textBufferReducer', () => {
expect(state.undoStack.length).toBe(1);
});
- it('should not create an undo snapshot if pushToUndo is false', () => {
+ it('should not create an undo snapshot if pushToUndo is false', async () => {
const action: TextBufferAction = {
type: 'set_text',
payload: 'no undo',
@@ -161,7 +161,7 @@ describe('textBufferReducer', () => {
});
describe('insert action', () => {
- it('should insert a character', () => {
+ it('should insert a character', async () => {
const action: TextBufferAction = { type: 'insert', payload: 'a' };
const state = textBufferReducer(initialState, action);
expect(state).toHaveOnlyValidCharacters();
@@ -169,7 +169,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(1);
});
- it('should insert a newline', () => {
+ it('should insert a newline', async () => {
const stateWithText = { ...initialState, lines: ['hello'] };
const action: TextBufferAction = { type: 'insert', payload: '\n' };
const state = textBufferReducer(stateWithText, action);
@@ -181,7 +181,7 @@ describe('textBufferReducer', () => {
});
describe('insert action with options', () => {
- it('should filter input using inputFilter option', () => {
+ it('should filter input using inputFilter option', async () => {
const action: TextBufferAction = { type: 'insert', payload: 'a1b2c3' };
const options: TextBufferOptions = {
inputFilter: (text) => text.replace(/[0-9]/g, ''),
@@ -191,7 +191,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(3);
});
- it('should strip newlines when singleLine option is true', () => {
+ it('should strip newlines when singleLine option is true', async () => {
const action: TextBufferAction = {
type: 'insert',
payload: 'hello\nworld',
@@ -202,7 +202,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(10);
});
- it('should apply both inputFilter and singleLine options', () => {
+ it('should apply both inputFilter and singleLine options', async () => {
const action: TextBufferAction = {
type: 'insert',
payload: 'h\ne\nl\nl\no\n1\n2\n3',
@@ -218,7 +218,7 @@ describe('textBufferReducer', () => {
});
describe('add_pasted_content action', () => {
- it('should add content to pastedContent Record', () => {
+ it('should add content to pastedContent Record', async () => {
const action: TextBufferAction = {
type: 'add_pasted_content',
payload: { id: '[Pasted Text: 6 lines]', text: 'large content' },
@@ -231,7 +231,7 @@ describe('textBufferReducer', () => {
});
describe('backspace action', () => {
- it('should remove a character', () => {
+ it('should remove a character', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['a'],
@@ -245,7 +245,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(0);
});
- it('should join lines if at the beginning of a line', () => {
+ it('should join lines if at the beginning of a line', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['hello', 'world'],
@@ -263,7 +263,7 @@ describe('textBufferReducer', () => {
describe('atomic placeholder deletion', () => {
describe('paste placeholders', () => {
- it('backspace at end of paste placeholder removes entire placeholder', () => {
+ it('backspace at end of paste placeholder removes entire placeholder', async () => {
const placeholder = '[Pasted Text: 6 lines]';
const stateWithPlaceholder = createStateWithTransformations({
lines: [placeholder],
@@ -282,7 +282,7 @@ describe('textBufferReducer', () => {
expect(state.pastedContent[placeholder]).toBeUndefined();
});
- it('delete at start of paste placeholder removes entire placeholder', () => {
+ it('delete at start of paste placeholder removes entire placeholder', async () => {
const placeholder = '[Pasted Text: 6 lines]';
const stateWithPlaceholder = createStateWithTransformations({
lines: [placeholder],
@@ -301,7 +301,7 @@ describe('textBufferReducer', () => {
expect(state.pastedContent[placeholder]).toBeUndefined();
});
- it('backspace inside paste placeholder does normal deletion', () => {
+ it('backspace inside paste placeholder does normal deletion', async () => {
const placeholder = '[Pasted Text: 6 lines]';
const stateWithPlaceholder = createStateWithTransformations({
lines: [placeholder],
@@ -323,7 +323,7 @@ describe('textBufferReducer', () => {
});
describe('image placeholders', () => {
- it('backspace at end of image path removes entire path', () => {
+ it('backspace at end of image path removes entire path', async () => {
const imagePath = '@test.png';
const stateWithImage = createStateWithTransformations({
lines: [imagePath],
@@ -337,7 +337,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(0);
});
- it('delete at start of image path removes entire path', () => {
+ it('delete at start of image path removes entire path', async () => {
const imagePath = '@test.png';
const stateWithImage = createStateWithTransformations({
lines: [imagePath],
@@ -351,7 +351,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(0);
});
- it('backspace inside image path does normal deletion', () => {
+ it('backspace inside image path does normal deletion', async () => {
const imagePath = '@test.png';
const stateWithImage = createStateWithTransformations({
lines: [imagePath],
@@ -368,7 +368,7 @@ describe('textBufferReducer', () => {
});
describe('undo behavior', () => {
- it('undo after placeholder deletion restores everything', () => {
+ it('undo after placeholder deletion restores everything', async () => {
const placeholder = '[Pasted Text: 6 lines]';
const pasteContent = 'line1\nline2\nline3\nline4\nline5\nline6';
const stateWithPlaceholder = createStateWithTransformations({
@@ -398,7 +398,7 @@ describe('textBufferReducer', () => {
});
describe('undo/redo actions', () => {
- it('should undo and redo a change', () => {
+ it('should undo and redo a change', async () => {
// 1. Insert text
const insertAction: TextBufferAction = {
type: 'insert',
@@ -428,7 +428,7 @@ describe('textBufferReducer', () => {
});
describe('create_undo_snapshot action', () => {
- it('should create a snapshot without changing state', () => {
+ it('should create a snapshot without changing state', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['hello'],
@@ -494,7 +494,7 @@ describe('textBufferReducer', () => {
},
);
- it('should act like backspace at the beginning of a line', () => {
+ it('should act like backspace at the beginning of a line', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['hello', 'world'],
@@ -548,7 +548,7 @@ describe('textBufferReducer', () => {
},
);
- it('should delete path segments progressively', () => {
+ it('should delete path segments progressively', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['path/to/file'],
@@ -563,7 +563,7 @@ describe('textBufferReducer', () => {
expect(state.lines).toEqual(['to/file']);
});
- it('should act like delete at the end of a line', () => {
+ it('should act like delete at the end of a line', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['hello', 'world'],
@@ -580,7 +580,7 @@ describe('textBufferReducer', () => {
});
describe('kill_line_left action', () => {
- it('should clean up pastedContent when deleting a placeholder line-left', () => {
+ it('should clean up pastedContent when deleting a placeholder line-left', async () => {
const placeholder = '[Pasted Text: 6 lines]';
const stateWithPlaceholder = createStateWithTransformations({
lines: [placeholder],
@@ -602,7 +602,7 @@ describe('textBufferReducer', () => {
});
describe('kill_line_right action', () => {
- it('should reset preferredCol when deleting to end of line', () => {
+ it('should reset preferredCol when deleting to end of line', async () => {
const stateWithText: TextBufferState = {
...initialState,
lines: ['hello world'],
@@ -624,7 +624,7 @@ describe('textBufferReducer', () => {
const placeholder = '[Pasted Text: 6 lines]';
const content = 'line1\nline2\nline3\nline4\nline5\nline6';
- it('should expand a placeholder correctly', () => {
+ it('should expand a placeholder correctly', async () => {
const stateWithPlaceholder = createStateWithTransformations({
lines: ['prefix ' + placeholder + ' suffix'],
cursorRow: 0,
@@ -661,7 +661,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(5); // length of 'line6'
});
- it('should collapse an expanded placeholder correctly', () => {
+ it('should collapse an expanded placeholder correctly', async () => {
const expandedState = createStateWithTransformations({
lines: [
'prefix line1',
@@ -697,7 +697,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(('prefix ' + placeholder).length);
});
- it('should expand single-line content correctly', () => {
+ it('should expand single-line content correctly', async () => {
const singleLinePlaceholder = '[Pasted Text: 10 chars]';
const singleLineContent = 'some text';
const stateWithPlaceholder = createStateWithTransformations({
@@ -717,7 +717,7 @@ describe('textBufferReducer', () => {
expect(state.cursorCol).toBe(9);
});
- it('should return current state if placeholder ID not found in pastedContent', () => {
+ it('should return current state if placeholder ID not found in pastedContent', async () => {
const action: TextBufferAction = {
type: 'toggle_paste_expansion',
payload: { id: 'unknown', row: 0, col: 0 },
@@ -726,7 +726,7 @@ describe('textBufferReducer', () => {
expect(state).toBe(initialState);
});
- it('should preserve expandedPaste when lines change from edits outside the region', () => {
+ it('should preserve expandedPaste when lines change from edits outside the region', async () => {
// Start with an expanded paste at line 0 (3 lines long)
const placeholder = '[Pasted Text: 3 lines]';
const expandedState = createStateWithTransformations({
@@ -784,8 +784,8 @@ describe('useTextBuffer', () => {
});
describe('Initialization', () => {
- it('should initialize with empty text and cursor at (0,0) by default', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should initialize with empty text and cursor at (0,0) by default', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const state = getBufferState(result);
expect(state.text).toBe('');
expect(state.lines).toEqual(['']);
@@ -796,8 +796,8 @@ describe('useTextBuffer', () => {
expect(state.visualScrollRow).toBe(0);
});
- it('should initialize with provided initialText', () => {
- const { result } = renderHook(() =>
+ it('should initialize with provided initialText', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello',
viewport,
@@ -812,8 +812,8 @@ describe('useTextBuffer', () => {
expect(state.visualCursor).toEqual([0, 0]);
});
- it('should initialize with initialText and initialCursorOffset', () => {
- const { result } = renderHook(() =>
+ it('should initialize with initialText and initialCursorOffset', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello\nworld',
initialCursorOffset: 7, // Should be at 'o' in 'world'
@@ -830,8 +830,8 @@ describe('useTextBuffer', () => {
expect(state.visualCursor[1]).toBe(1); // At 'o' in "world"
});
- it('should wrap visual lines', () => {
- const { result } = renderHook(() =>
+ it('should wrap visual lines', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'The quick brown fox jumps over the lazy dog.',
initialCursorOffset: 2, // After '好'
@@ -847,8 +847,8 @@ describe('useTextBuffer', () => {
]);
});
- it('should wrap visual lines with multiple spaces', () => {
- const { result } = renderHook(() =>
+ it('should wrap visual lines with multiple spaces', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'The quick brown fox jumps over the lazy dog.',
viewport: { width: 15, height: 4 },
@@ -866,8 +866,8 @@ describe('useTextBuffer', () => {
]);
});
- it('should wrap visual lines even without spaces', () => {
- const { result } = renderHook(() =>
+ it('should wrap visual lines even without spaces', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '123456789012345ABCDEFG', // 4 chars, 12 bytes
viewport: { width: 15, height: 2 },
@@ -880,8 +880,8 @@ describe('useTextBuffer', () => {
expect(state.allVisualLines).toEqual(['123456789012345', 'ABCDEFG']);
});
- it('should initialize with multi-byte unicode characters and correct cursor offset', () => {
- const { result } = renderHook(() =>
+ it('should initialize with multi-byte unicode characters and correct cursor offset', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '你好世界', // 4 chars, 12 bytes
initialCursorOffset: 2, // After '好'
@@ -899,8 +899,8 @@ describe('useTextBuffer', () => {
});
describe('Basic Editing', () => {
- it('insert: should insert a character and update cursor', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('insert: should insert a character and update cursor', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => result.current.insert('a'));
let state = getBufferState(result);
expect(state.text).toBe('a');
@@ -914,8 +914,8 @@ describe('useTextBuffer', () => {
expect(state.visualCursor).toEqual([0, 2]);
});
- it('insert: should insert text in the middle of a line', () => {
- const { result } = renderHook(() =>
+ it('insert: should insert text in the middle of a line', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'abc',
viewport,
@@ -928,8 +928,8 @@ describe('useTextBuffer', () => {
expect(state.cursor).toEqual([0, 6]);
});
- it('insert: should use placeholder for large text paste', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('insert: should use placeholder for large text paste', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
const state = getBufferState(result);
@@ -939,16 +939,16 @@ describe('useTextBuffer', () => {
);
});
- it('insert: should NOT use placeholder for large text if NOT a paste', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('insert: should NOT use placeholder for large text if NOT a paste', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: false }));
const state = getBufferState(result);
expect(state.text).toBe(largeText);
});
- it('insert: should clean up pastedContent when placeholder is deleted', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('insert: should clean up pastedContent when placeholder is deleted', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
expect(result.current.pastedContent['[Pasted Text: 6 lines]']).toBe(
@@ -960,8 +960,8 @@ describe('useTextBuffer', () => {
expect(Object.keys(result.current.pastedContent)).toHaveLength(0);
});
- it('insert: should clean up pastedContent when placeholder is removed via atomic backspace', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('insert: should clean up pastedContent when placeholder is removed via atomic backspace', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
expect(result.current.pastedContent['[Pasted Text: 6 lines]']).toBe(
@@ -978,8 +978,8 @@ describe('useTextBuffer', () => {
expect(Object.keys(result.current.pastedContent)).toHaveLength(0);
});
- it('deleteWordLeft: should clean up pastedContent and avoid #2 suffix on repaste', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('deleteWordLeft: should clean up pastedContent and avoid #2 suffix on repaste', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
@@ -1003,8 +1003,8 @@ describe('useTextBuffer', () => {
);
});
- it('deleteWordRight: should clean up pastedContent and avoid #2 suffix on repaste', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('deleteWordRight: should clean up pastedContent and avoid #2 suffix on repaste', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
@@ -1032,8 +1032,8 @@ describe('useTextBuffer', () => {
);
});
- it('killLineLeft: should clean up pastedContent and avoid #2 suffix on repaste', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('killLineLeft: should clean up pastedContent and avoid #2 suffix on repaste', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
@@ -1053,8 +1053,8 @@ describe('useTextBuffer', () => {
);
});
- it('killLineRight: should clean up pastedContent and avoid #2 suffix on repaste', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('killLineRight: should clean up pastedContent and avoid #2 suffix on repaste', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeText = '1\n2\n3\n4\n5\n6';
act(() => result.current.insert(largeText, { paste: true }));
@@ -1079,8 +1079,8 @@ describe('useTextBuffer', () => {
);
});
- it('newline: should create a new line and move cursor', () => {
- const { result } = renderHook(() =>
+ it('newline: should create a new line and move cursor', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'ab',
viewport,
@@ -1097,8 +1097,8 @@ describe('useTextBuffer', () => {
expect(state.visualCursor).toEqual([1, 0]); // On the new visual line
});
- it('backspace: should delete char to the left or merge lines', () => {
- const { result } = renderHook(() =>
+ it('backspace: should delete char to the left or merge lines', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'a\nb',
viewport,
@@ -1124,8 +1124,8 @@ describe('useTextBuffer', () => {
expect(state.visualCursor).toEqual([0, 1]);
});
- it('del: should delete char to the right or merge lines', () => {
- const { result } = renderHook(() =>
+ it('del: should delete char to the right or merge lines', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'a\nb',
viewport,
@@ -1158,29 +1158,29 @@ describe('useTextBuffer', () => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
- it('should prepend @ to a valid file path on insert', () => {
+ it('should prepend @ to a valid file path on insert', async () => {
const filePath = path.join(tempDir, 'file.txt');
fs.writeFileSync(filePath, '');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({ viewport, escapePastedPaths: true }),
);
act(() => result.current.insert(filePath, { paste: true }));
expect(getBufferState(result).text).toBe(`@${escapePath(filePath)} `);
});
- it('should not prepend @ to an invalid file path on insert', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should not prepend @ to an invalid file path on insert', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const notAPath = path.join(tempDir, 'non_existent.txt');
act(() => result.current.insert(notAPath, { paste: true }));
expect(getBufferState(result).text).toBe(notAPath);
});
- it('should handle quoted paths', () => {
+ it('should handle quoted paths', async () => {
const filePath = path.join(tempDir, 'file.txt');
fs.writeFileSync(filePath, '');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({ viewport, escapePastedPaths: true }),
);
const quotedPath = `'${filePath}'`;
@@ -1188,8 +1188,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe(`@${escapePath(filePath)} `);
});
- it('should not prepend @ to short text that is not a path', () => {
- const { result } = renderHook(() =>
+ it('should not prepend @ to short text that is not a path', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({ viewport, escapePastedPaths: true }),
);
const shortText = 'ab';
@@ -1197,13 +1197,13 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe(shortText);
});
- it('should prepend @ to multiple valid file paths on insert', () => {
+ it('should prepend @ to multiple valid file paths on insert', async () => {
const file1 = path.join(tempDir, 'file1.txt');
const file2 = path.join(tempDir, 'file2.txt');
fs.writeFileSync(file1, '');
fs.writeFileSync(file2, '');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({ viewport, escapePastedPaths: true }),
);
const filePaths = `${escapePath(file1)} ${escapePath(file2)}`;
@@ -1213,13 +1213,13 @@ describe('useTextBuffer', () => {
);
});
- it('should handle multiple paths with escaped spaces', () => {
+ it('should handle multiple paths with escaped spaces', async () => {
const file1 = path.join(tempDir, 'my file.txt');
const file2 = path.join(tempDir, 'other.txt');
fs.writeFileSync(file1, '');
fs.writeFileSync(file2, '');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({ viewport, escapePastedPaths: true }),
);
@@ -1231,13 +1231,13 @@ describe('useTextBuffer', () => {
);
});
- it('should not prepend @ unless all paths are valid', () => {
+ it('should not prepend @ unless all paths are valid', async () => {
const validFile = path.join(tempDir, 'valid.txt');
const invalidFile = path.join(tempDir, 'invalid.jpg');
fs.writeFileSync(validFile, '');
// Do not create invalidFile
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
escapePastedPaths: true,
@@ -1250,8 +1250,8 @@ describe('useTextBuffer', () => {
});
describe('Shell Mode Behavior', () => {
- it('should not prepend @ to valid file paths when shellModeActive is true', () => {
- const { result } = renderHook(() =>
+ it('should not prepend @ to valid file paths when shellModeActive is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
escapePastedPaths: true,
@@ -1263,8 +1263,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe(filePath); // No @ prefix
});
- it('should not prepend @ to quoted paths when shellModeActive is true', () => {
- const { result } = renderHook(() =>
+ it('should not prepend @ to quoted paths when shellModeActive is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
escapePastedPaths: true,
@@ -1276,8 +1276,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe(quotedFilePath); // No @ prefix, keeps quotes
});
- it('should behave normally with invalid paths when shellModeActive is true', () => {
- const { result } = renderHook(() =>
+ it('should behave normally with invalid paths when shellModeActive is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -1289,8 +1289,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe(notAPath);
});
- it('should behave normally with short text when shellModeActive is true', () => {
- const { result } = renderHook(() =>
+ it('should behave normally with short text when shellModeActive is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
escapePastedPaths: true,
@@ -1304,14 +1304,14 @@ describe('useTextBuffer', () => {
});
describe('Cursor Movement', () => {
- it('move: left/right should work within and across visual lines (due to wrapping)', () => {
+ it('move: left/right should work within and across visual lines (due to wrapping)', async () => {
// Text: "long line1next line2" (20 chars)
// Viewport width 5. Word wrapping should produce:
// "long " (5)
// "line1" (5)
// "next " (5)
// "line2" (5)
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'long line1next line2', // Corrected: was 'long line1next line2'
viewport: { width: 5, height: 4 },
@@ -1335,9 +1335,9 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).cursor).toEqual([0, 4]); // logical cursor
});
- it('move: up/down should preserve preferred visual column', () => {
+ it('move: up/down should preserve preferred visual column', async () => {
const text = 'abcde\nxy\n12345';
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: text,
viewport,
@@ -1376,9 +1376,9 @@ describe('useTextBuffer', () => {
expect(state.preferredCol).toBe(null);
});
- it('move: home/end should go to visual line start/end', () => {
+ it('move: home/end should go to visual line start/end', async () => {
const initialText = 'line one\nsecond line';
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText,
viewport: { width: 5, height: 5 },
@@ -1405,8 +1405,8 @@ describe('useTextBuffer', () => {
});
describe('Visual Layout & Viewport', () => {
- it('should wrap long lines correctly into visualLines', () => {
- const { result } = renderHook(() =>
+ it('should wrap long lines correctly into visualLines', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'This is a very long line of text.', // 33 chars
viewport: { width: 10, height: 5 },
@@ -1425,8 +1425,8 @@ describe('useTextBuffer', () => {
expect(state.allVisualLines[3]).toBe('text.');
});
- it('should update visualScrollRow when visualCursor moves out of viewport', () => {
- const { result } = renderHook(() =>
+ it('should update visualScrollRow when visualCursor moves out of viewport', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'l1\nl2\nl3\nl4\nl5',
viewport: { width: 5, height: 3 }, // Can show 3 visual lines
@@ -1474,8 +1474,8 @@ describe('useTextBuffer', () => {
});
describe('Undo/Redo', () => {
- it('should undo and redo an insert operation', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should undo and redo an insert operation', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => result.current.insert('a'));
expect(getBufferState(result).text).toBe('a');
@@ -1488,8 +1488,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).cursor).toEqual([0, 1]);
});
- it('should undo and redo a newline operation', () => {
- const { result } = renderHook(() =>
+ it('should undo and redo a newline operation', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'test',
viewport,
@@ -1510,8 +1510,8 @@ describe('useTextBuffer', () => {
});
describe('Unicode Handling', () => {
- it('insert: should correctly handle multi-byte unicode characters', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('insert: should correctly handle multi-byte unicode characters', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => result.current.insert('你好'));
const state = getBufferState(result);
expect(state.text).toBe('你好');
@@ -1519,8 +1519,8 @@ describe('useTextBuffer', () => {
expect(state.visualCursor).toEqual([0, 2]);
});
- it('backspace: should correctly delete multi-byte unicode characters', () => {
- const { result } = renderHook(() =>
+ it('backspace: should correctly delete multi-byte unicode characters', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '你好',
viewport,
@@ -1538,8 +1538,8 @@ describe('useTextBuffer', () => {
expect(state.cursor).toEqual([0, 0]);
});
- it('move: left/right should treat multi-byte chars as single units for visual cursor', () => {
- const { result } = renderHook(() =>
+ it('move: left/right should treat multi-byte chars as single units for visual cursor', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '🐶🐱',
viewport: { width: 5, height: 1 },
@@ -1562,12 +1562,12 @@ describe('useTextBuffer', () => {
expect(state.visualCursor).toEqual([0, 1]);
});
- it('move: up/down should work on wrapped lines (regression test)', () => {
+ it('move: up/down should work on wrapped lines (regression test)', async () => {
// Line that wraps into two visual lines
// Viewport width 10. "0123456789ABCDE" (15 chars)
// Visual Line 0: "0123456789"
// Visual Line 1: "ABCDE"
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport: { width: 10, height: 5 },
}),
@@ -1616,8 +1616,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).visualCursor[0]).toBe(1);
});
- it('moveToVisualPosition: should correctly handle wide characters (Chinese)', () => {
- const { result } = renderHook(() =>
+ it('moveToVisualPosition: should correctly handle wide characters (Chinese)', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '你好', // 2 chars, width 4
viewport: { width: 10, height: 1 },
@@ -1645,8 +1645,8 @@ describe('useTextBuffer', () => {
});
describe('handleInput', () => {
- it('should insert printable characters', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should insert printable characters', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput({
name: 'h',
@@ -1672,8 +1672,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe('hi');
});
- it('should handle "Enter" key as newline', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should handle "Enter" key as newline', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput({
name: 'enter',
@@ -1688,8 +1688,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).lines).toEqual(['', '']);
});
- it('should handle Ctrl+J as newline', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should handle Ctrl+J as newline', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput({
name: 'j',
@@ -1704,8 +1704,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).lines).toEqual(['', '']);
});
- it('should do nothing for a tab key press', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should do nothing for a tab key press', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput({
name: 'tab',
@@ -1720,8 +1720,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe('');
});
- it('should do nothing for a shift tab key press', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should do nothing for a shift tab key press', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput({
name: 'tab',
@@ -1736,8 +1736,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe('');
});
- it('should handle CLEAR_INPUT (Ctrl+C)', () => {
- const { result } = renderHook(() =>
+ it('should handle CLEAR_INPUT (Ctrl+C)', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello',
viewport,
@@ -1760,8 +1760,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe('');
});
- it('should NOT handle CLEAR_INPUT if buffer is empty', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should NOT handle CLEAR_INPUT if buffer is empty', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
let handled = true;
act(() => {
handled = result.current.handleInput({
@@ -1777,8 +1777,8 @@ describe('useTextBuffer', () => {
expect(handled).toBe(false);
});
- it('should handle "Backspace" key', () => {
- const { result } = renderHook(() =>
+ it('should handle "Backspace" key', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'a',
viewport,
@@ -1799,8 +1799,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe('');
});
- it('should handle multiple delete characters in one input', () => {
- const { result } = renderHook(() =>
+ it('should handle multiple delete characters in one input', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'abcde',
viewport,
@@ -1842,8 +1842,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).cursor).toEqual([0, 2]);
});
- it('should handle inserts that contain delete characters', () => {
- const { result } = renderHook(() =>
+ it('should handle inserts that contain delete characters', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'abcde',
viewport,
@@ -1859,8 +1859,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).cursor).toEqual([0, 2]);
});
- it('should handle inserts with a mix of regular and delete characters', () => {
- const { result } = renderHook(() =>
+ it('should handle inserts with a mix of regular and delete characters', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'abcde',
viewport,
@@ -1876,8 +1876,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).cursor).toEqual([0, 6]);
});
- it('should handle arrow keys for movement', () => {
- const { result } = renderHook(() =>
+ it('should handle arrow keys for movement', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'ab',
viewport,
@@ -1910,8 +1910,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).cursor).toEqual([0, 2]);
});
- it('should strip ANSI escape codes when pasting text', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should strip ANSI escape codes when pasting text', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const textWithAnsi = '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m';
// Simulate pasting by calling handleInput with a string longer than 1 char
act(() => {
@@ -1928,8 +1928,8 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).text).toBe('Hello World');
});
- it('should handle VSCode terminal Shift+Enter as newline', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should handle VSCode terminal Shift+Enter as newline', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput({
name: 'enter',
@@ -1944,7 +1944,7 @@ describe('useTextBuffer', () => {
expect(getBufferState(result).lines).toEqual(['', '']);
});
- it('should correctly handle repeated pasting of long text', () => {
+ it('should correctly handle repeated pasting of long text', async () => {
const longText = `not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
Why do we use it?
@@ -1953,7 +1953,7 @@ It is a long established fact that a reader will be distracted by the readable c
Where does it come from?
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lore
`;
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
// Simulate pasting the long text multiple times
act(() => {
@@ -1994,8 +1994,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
// - All edge cases for visual scrolling and wrapping with different viewport sizes and text content.
describe('replaceRange', () => {
- it('should replace a single-line range with single-line text', () => {
- const { result } = renderHook(() =>
+ it('should replace a single-line range with single-line text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '@pac',
viewport,
@@ -2007,8 +2007,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 9]); // cursor after 'typescript'
});
- it('should replace a multi-line range with single-line text', () => {
- const { result } = renderHook(() =>
+ it('should replace a multi-line range with single-line text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello\nworld\nagain',
viewport,
@@ -2020,8 +2020,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 7]); // cursor after ' new '
});
- it('should delete a range when replacing with an empty string', () => {
- const { result } = renderHook(() =>
+ it('should delete a range when replacing with an empty string', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello world',
viewport,
@@ -2033,8 +2033,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 5]);
});
- it('should handle replacing at the beginning of the text', () => {
- const { result } = renderHook(() =>
+ it('should handle replacing at the beginning of the text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'world',
viewport,
@@ -2046,8 +2046,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 6]);
});
- it('should handle replacing at the end of the text', () => {
- const { result } = renderHook(() =>
+ it('should handle replacing at the end of the text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello',
viewport,
@@ -2059,8 +2059,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 11]);
});
- it('should handle replacing the entire buffer content', () => {
- const { result } = renderHook(() =>
+ it('should handle replacing the entire buffer content', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'old text',
viewport,
@@ -2072,8 +2072,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 8]);
});
- it('should correctly replace with unicode characters', () => {
- const { result } = renderHook(() =>
+ it('should correctly replace with unicode characters', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'hello *** world',
viewport,
@@ -2085,8 +2085,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 8]); // after '你好'
});
- it('should handle invalid range by returning false and not changing text', () => {
- const { result } = renderHook(() =>
+ it('should handle invalid range by returning false and not changing text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'test',
viewport,
@@ -2104,8 +2104,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).text).toBe('test');
});
- it('replaceRange: multiple lines with a single character', () => {
- const { result } = renderHook(() =>
+ it('replaceRange: multiple lines with a single character', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'first\nsecond\nthird',
viewport,
@@ -2117,8 +2117,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 3]); // After 'X'
});
- it('should replace a single-line range with multi-line text', () => {
- const { result } = renderHook(() =>
+ it('should replace a single-line range with multi-line text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'one two three',
viewport,
@@ -2164,16 +2164,16 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expected: 'Pasted Text',
desc: 'pasted text with ANSI',
},
- ])('should strip $desc from input', ({ input, expected }) => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ ])('should strip $desc from input', async ({ input, expected }) => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
act(() => {
result.current.handleInput(createInput(input));
});
expect(getBufferState(result).text).toBe(expected);
});
- it('should not strip standard characters or newlines', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should not strip standard characters or newlines', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const validText = 'Hello World\nThis is a test.';
act(() => {
result.current.handleInput(createInput(validText));
@@ -2181,8 +2181,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).text).toBe(validText);
});
- it('should sanitize large text (>5000 chars) and strip unsafe characters', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should sanitize large text (>5000 chars) and strip unsafe characters', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const unsafeChars = '\x07\x08\x0B\x0C';
const largeTextWithUnsafe =
'safe text'.repeat(600) + unsafeChars + 'more safe text';
@@ -2210,8 +2210,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(resultText).toContain('more safe text');
});
- it('should sanitize large ANSI text (>5000 chars) and strip escape codes', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should sanitize large ANSI text (>5000 chars) and strip escape codes', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const largeTextWithAnsi =
'\x1B[31m' +
'red text'.repeat(800) +
@@ -2242,8 +2242,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(resultText).toContain('green text');
});
- it('should not strip popular emojis', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should not strip popular emojis', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
const emojis = '🐍🐳🦀🦄';
act(() => {
result.current.handleInput({
@@ -2261,8 +2261,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
});
describe('inputFilter', () => {
- it('should filter input based on the provided filter function', () => {
- const { result } = renderHook(() =>
+ it('should filter input based on the provided filter function', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2274,8 +2274,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).text).toBe('123');
});
- it('should handle empty result from filter', () => {
- const { result } = renderHook(() =>
+ it('should handle empty result from filter', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2287,8 +2287,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).text).toBe('');
});
- it('should filter pasted text', () => {
- const { result } = renderHook(() =>
+ it('should filter pasted text', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2300,8 +2300,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).text).toBe('HELLO');
});
- it('should not filter newlines if they are allowed by the filter', () => {
- const { result } = renderHook(() =>
+ it('should not filter newlines if they are allowed by the filter', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2315,8 +2315,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).text).toBe('a\nb');
});
- it('should filter before newline check in insert', () => {
- const { result } = renderHook(() =>
+ it('should filter before newline check in insert', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2330,29 +2330,29 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
});
describe('stripAnsi', () => {
- it('should correctly strip ANSI escape codes', () => {
+ it('should correctly strip ANSI escape codes', async () => {
const textWithAnsi = '\x1B[31mHello\x1B[0m World';
expect(stripAnsi(textWithAnsi)).toBe('Hello World');
});
- it('should handle multiple ANSI codes', () => {
+ it('should handle multiple ANSI codes', async () => {
const textWithMultipleAnsi = '\x1B[1m\x1B[34mBold Blue\x1B[0m Text';
expect(stripAnsi(textWithMultipleAnsi)).toBe('Bold Blue Text');
});
- it('should not modify text without ANSI codes', () => {
+ it('should not modify text without ANSI codes', async () => {
const plainText = 'Plain text';
expect(stripAnsi(plainText)).toBe('Plain text');
});
- it('should handle empty string', () => {
+ it('should handle empty string', async () => {
expect(stripAnsi('')).toBe('');
});
});
describe('Memoization', () => {
- it('should keep action references stable across re-renders', () => {
- const { result, rerender } = renderHook(() =>
+ it('should keep action references stable across re-renders', async () => {
+ const { result, rerender } = await renderHook(() =>
useTextBuffer({ viewport }),
);
@@ -2369,8 +2369,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(result.current.handleInput).toBe(initialHandleInput);
});
- it('should have memoized actions that operate on the latest state', () => {
- const { result } = renderHook(() => useTextBuffer({ viewport }));
+ it('should have memoized actions that operate on the latest state', async () => {
+ const { result } = await renderHook(() => useTextBuffer({ viewport }));
// Store a reference to the memoized insert function.
const memoizedInsert = result.current.insert;
@@ -2392,8 +2392,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
});
describe('singleLine mode', () => {
- it('should not insert a newline character when singleLine is true', () => {
- const { result } = renderHook(() =>
+ it('should not insert a newline character when singleLine is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2406,8 +2406,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.lines).toEqual(['']);
});
- it('should not create a new line when newline() is called and singleLine is true', () => {
- const { result } = renderHook(() =>
+ it('should not create a new line when newline() is called and singleLine is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'ab',
viewport,
@@ -2423,8 +2423,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(state.cursor).toEqual([0, 2]);
});
- it('should not handle "Enter" key as newline when singleLine is true', () => {
- const { result } = renderHook(() =>
+ it('should not handle "Enter" key as newline when singleLine is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2445,8 +2445,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).lines).toEqual(['']);
});
- it('should not print anything for function keys when singleLine is true', () => {
- const { result } = renderHook(() =>
+ it('should not print anything for function keys when singleLine is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2467,8 +2467,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
expect(getBufferState(result).lines).toEqual(['']);
});
- it('should strip newlines from pasted text when singleLine is true', () => {
- const { result } = renderHook(() =>
+ it('should strip newlines from pasted text when singleLine is true', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
viewport,
@@ -2615,7 +2615,7 @@ describe('offsetToLogicalPos', () => {
{ text: '🐶🐱', offset: 0, expected: [0, 0], desc: 'emoji - start' },
{ text: '🐶🐱', offset: 1, expected: [0, 1], desc: 'emoji - middle' },
{ text: '🐶🐱', offset: 2, expected: [0, 2], desc: 'emoji - end' },
- ])('should handle $desc', ({ text, offset, expected }) => {
+ ])('should handle $desc', async ({ text, offset, expected }) => {
expect(offsetToLogicalPos(text, offset)).toEqual(expected);
});
@@ -2643,7 +2643,7 @@ describe('offsetToLogicalPos', () => {
});
describe('logicalPosToOffset', () => {
- it('should convert row/col position to offset correctly', () => {
+ it('should convert row/col position to offset correctly', async () => {
const lines = ['hello', 'world', '123'];
// Line 0: "hello" (5 chars)
@@ -2662,7 +2662,7 @@ describe('logicalPosToOffset', () => {
expect(logicalPosToOffset(lines, 2, 3)).toBe(15); // End of '123'
});
- it('should handle empty lines', () => {
+ it('should handle empty lines', async () => {
const lines = ['a', '', 'c'];
expect(logicalPosToOffset(lines, 0, 0)).toBe(0); // 'a'
@@ -2672,13 +2672,13 @@ describe('logicalPosToOffset', () => {
expect(logicalPosToOffset(lines, 2, 1)).toBe(4); // End of 'c'
});
- it('should handle single empty line', () => {
+ it('should handle single empty line', async () => {
const lines = [''];
expect(logicalPosToOffset(lines, 0, 0)).toBe(0);
});
- it('should be inverse of offsetToLogicalPos', () => {
+ it('should be inverse of offsetToLogicalPos', async () => {
const lines = ['hello', 'world', '123'];
const text = lines.join('\n');
@@ -2690,7 +2690,7 @@ describe('logicalPosToOffset', () => {
}
});
- it('should handle out-of-bounds positions', () => {
+ it('should handle out-of-bounds positions', async () => {
const lines = ['hello'];
// Beyond end of line
@@ -2726,7 +2726,7 @@ const createTestState = (
describe('textBufferReducer vim operations', () => {
describe('vim_delete_line', () => {
- it('should delete a single line including newline in multi-line text', () => {
+ it('should delete a single line including newline in multi-line text', async () => {
const state = createTestState(['line1', 'line2', 'line3'], 1, 2);
const action: TextBufferAction = {
@@ -2743,7 +2743,7 @@ describe('textBufferReducer vim operations', () => {
expect(result.cursorCol).toBe(0);
});
- it('should delete multiple lines when count > 1', () => {
+ it('should delete multiple lines when count > 1', async () => {
const state = createTestState(['line1', 'line2', 'line3', 'line4'], 1, 0);
const action: TextBufferAction = {
@@ -2760,7 +2760,7 @@ describe('textBufferReducer vim operations', () => {
expect(result.cursorCol).toBe(0);
});
- it('should clear single line content when only one line exists', () => {
+ it('should clear single line content when only one line exists', async () => {
const state = createTestState(['only line'], 0, 5);
const action: TextBufferAction = {
@@ -2777,7 +2777,7 @@ describe('textBufferReducer vim operations', () => {
expect(result.cursorCol).toBe(0);
});
- it('should handle deleting the last line properly', () => {
+ it('should handle deleting the last line properly', async () => {
const state = createTestState(['line1', 'line2'], 1, 0);
const action: TextBufferAction = {
@@ -2794,7 +2794,7 @@ describe('textBufferReducer vim operations', () => {
expect(result.cursorCol).toBe(0);
});
- it('should handle deleting all lines and maintain valid state for subsequent paste', () => {
+ it('should handle deleting all lines and maintain valid state for subsequent paste', async () => {
const state = createTestState(['line1', 'line2', 'line3', 'line4'], 0, 0);
// Delete all 4 lines with 4dd
@@ -2830,50 +2830,50 @@ describe('textBufferReducer vim operations', () => {
describe('Unicode helper functions', () => {
describe('findWordEndInLine with Unicode', () => {
- it('should handle combining characters', () => {
+ it('should handle combining characters', async () => {
// café with combining accent
const cafeWithCombining = 'cafe\u0301';
const result = findWordEndInLine(cafeWithCombining + ' test', 0);
expect(result).toBe(3); // End of 'café' at base character 'e', not combining accent
});
- it('should handle precomposed characters with diacritics', () => {
+ it('should handle precomposed characters with diacritics', async () => {
// café with precomposed é (U+00E9)
const cafePrecomposed = 'café';
const result = findWordEndInLine(cafePrecomposed + ' test', 0);
expect(result).toBe(3); // End of 'café' at precomposed character 'é'
});
- it('should return null when no word end found', () => {
+ it('should return null when no word end found', async () => {
const result = findWordEndInLine(' ', 0);
expect(result).toBeNull(); // No word end found in whitespace-only string string
});
});
describe('findNextWordStartInLine with Unicode', () => {
- it('should handle right-to-left text', () => {
+ it('should handle right-to-left text', async () => {
const result = findNextWordStartInLine('hello مرحبا world', 0);
expect(result).toBe(6); // Start of Arabic word
});
- it('should handle Chinese characters', () => {
+ it('should handle Chinese characters', async () => {
const result = findNextWordStartInLine('hello 你好 world', 0);
expect(result).toBe(6); // Start of Chinese word
});
- it('should return null at end of line', () => {
+ it('should return null at end of line', async () => {
const result = findNextWordStartInLine('hello', 10);
expect(result).toBeNull();
});
- it('should handle combining characters', () => {
+ it('should handle combining characters', async () => {
// café with combining accent + next word
const textWithCombining = 'cafe\u0301 test';
const result = findNextWordStartInLine(textWithCombining, 0);
expect(result).toBe(6); // Start of 'test' after 'café ' (combining char makes string longer)
});
- it('should handle precomposed characters with diacritics', () => {
+ it('should handle precomposed characters with diacritics', async () => {
// café with precomposed é + next word
const textPrecomposed = 'café test';
const result = findNextWordStartInLine(textPrecomposed, 0);
@@ -2882,37 +2882,37 @@ describe('Unicode helper functions', () => {
});
describe('isWordCharStrict with Unicode', () => {
- it('should return true for ASCII word characters', () => {
+ it('should return true for ASCII word characters', async () => {
expect(isWordCharStrict('a')).toBe(true);
expect(isWordCharStrict('Z')).toBe(true);
expect(isWordCharStrict('0')).toBe(true);
expect(isWordCharStrict('_')).toBe(true);
});
- it('should return false for punctuation', () => {
+ it('should return false for punctuation', async () => {
expect(isWordCharStrict('.')).toBe(false);
expect(isWordCharStrict(',')).toBe(false);
expect(isWordCharStrict('!')).toBe(false);
});
- it('should return true for non-Latin scripts', () => {
+ it('should return true for non-Latin scripts', async () => {
expect(isWordCharStrict('你')).toBe(true); // Chinese character
expect(isWordCharStrict('م')).toBe(true); // Arabic character
});
- it('should return false for whitespace', () => {
+ it('should return false for whitespace', async () => {
expect(isWordCharStrict(' ')).toBe(false);
expect(isWordCharStrict('\t')).toBe(false);
});
});
describe('cpLen with Unicode', () => {
- it('should handle combining characters', () => {
+ it('should handle combining characters', async () => {
expect(cpLen('é')).toBe(1); // Precomposed
expect(cpLen('e\u0301')).toBe(2); // e + combining acute
});
- it('should handle Chinese and Arabic text', () => {
+ it('should handle Chinese and Arabic text', async () => {
expect(cpLen('hello 你好 world')).toBe(14); // 5 + 1 + 2 + 1 + 5 = 14
expect(cpLen('hello مرحبا world')).toBe(17);
});
@@ -2921,8 +2921,8 @@ describe('Unicode helper functions', () => {
describe('useTextBuffer CJK Navigation', () => {
const viewport = { width: 80, height: 24 };
- it('should navigate by word in Chinese', () => {
- const { result } = renderHook(() =>
+ it('should navigate by word in Chinese', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: '你好世界',
initialCursorOffset: 4, // End of string
@@ -2979,8 +2979,8 @@ describe('Unicode helper functions', () => {
expect(result.current.cursor[1]).toBe(4);
});
- it('should navigate mixed English and Chinese', () => {
- const { result } = renderHook(() =>
+ it('should navigate mixed English and Chinese', async () => {
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'Hello你好World',
initialCursorOffset: 10, // End
@@ -3036,23 +3036,23 @@ describe('Transformation Utilities', () => {
describe('getTransformedImagePath', () => {
beforeEach(() => mockPlatform('linux'));
- it('should transform a simple image path', () => {
+ it('should transform a simple image path', async () => {
expect(getTransformedImagePath('@test.png')).toBe('[Image test.png]');
});
- it('should handle paths with directories', () => {
+ it('should handle paths with directories', async () => {
expect(getTransformedImagePath('@path/to/image.jpg')).toBe(
'[Image image.jpg]',
);
});
- it('should truncate long filenames', () => {
+ it('should truncate long filenames', async () => {
expect(getTransformedImagePath('@verylongfilename1234567890.png')).toBe(
'[Image ...1234567890.png]',
);
});
- it('should handle different image extensions', () => {
+ it('should handle different image extensions', async () => {
expect(getTransformedImagePath('@test.jpg')).toBe('[Image test.jpg]');
expect(getTransformedImagePath('@test.jpeg')).toBe('[Image test.jpeg]');
expect(getTransformedImagePath('@test.gif')).toBe('[Image test.gif]');
@@ -3061,19 +3061,19 @@ describe('Transformation Utilities', () => {
expect(getTransformedImagePath('@test.bmp')).toBe('[Image test.bmp]');
});
- it('should handle POSIX-style forward-slash paths on any platform', () => {
+ it('should handle POSIX-style forward-slash paths on any platform', async () => {
const input = '@C:/Users/foo/screenshots/image2x.png';
expect(getTransformedImagePath(input)).toBe('[Image image2x.png]');
});
- it('should handle escaped spaces in paths', () => {
+ it('should handle escaped spaces in paths', async () => {
const input = '@path/to/my\\ file.png';
expect(getTransformedImagePath(input)).toBe('[Image my file.png]');
});
});
describe('getTransformationsForLine', () => {
- it('should find transformations in a line', () => {
+ it('should find transformations in a line', async () => {
const line = 'Check out @test.png and @another.jpg';
const result = calculateTransformationsForLine(line);
@@ -3088,18 +3088,18 @@ describe('Transformation Utilities', () => {
});
});
- it('should handle no transformations', () => {
+ it('should handle no transformations', async () => {
const line = 'Just some regular text';
const result = calculateTransformationsForLine(line);
expect(result).toEqual([]);
});
- it('should handle empty line', () => {
+ it('should handle empty line', async () => {
const result = calculateTransformationsForLine('');
expect(result).toEqual([]);
});
- it('should keep adjacent image paths as separate transformations', () => {
+ it('should keep adjacent image paths as separate transformations', async () => {
const line = '@a.png@b.png@c.png';
const result = calculateTransformationsForLine(line);
expect(result).toHaveLength(3);
@@ -3108,7 +3108,7 @@ describe('Transformation Utilities', () => {
expect(result[2].logicalText).toBe('@c.png');
});
- it('should handle multiple transformations in a row', () => {
+ it('should handle multiple transformations in a row', async () => {
const line = '@a.png @b.png @c.png';
const result = calculateTransformationsForLine(line);
expect(result).toHaveLength(3);
@@ -3133,32 +3133,32 @@ describe('Transformation Utilities', () => {
},
];
- it('should find transformation when cursor is inside it', () => {
+ it('should find transformation when cursor is inside it', async () => {
const result = getTransformUnderCursor(0, 7, [transformations]);
expect(result).toEqual(transformations[0]);
});
- it('should find transformation when cursor is at start', () => {
+ it('should find transformation when cursor is at start', async () => {
const result = getTransformUnderCursor(0, 5, [transformations]);
expect(result).toEqual(transformations[0]);
});
- it('should NOT find transformation when cursor is at end', () => {
+ it('should NOT find transformation when cursor is at end', async () => {
const result = getTransformUnderCursor(0, 14, [transformations]);
expect(result).toBeNull();
});
- it('should return null when cursor is not on a transformation', () => {
+ it('should return null when cursor is not on a transformation', async () => {
const result = getTransformUnderCursor(0, 2, [transformations]);
expect(result).toBeNull();
});
- it('should handle empty transformations array', () => {
+ it('should handle empty transformations array', async () => {
const result = getTransformUnderCursor(0, 5, []);
expect(result).toBeNull();
});
- it('regression: should not find paste transformation when clicking one character after it', () => {
+ it('regression: should not find paste transformation when clicking one character after it', async () => {
const pasteId = '[Pasted Text: 5 lines]';
const line = pasteId + ' suffix';
const transformations = calculateTransformationsForLine(line);
@@ -3176,7 +3176,7 @@ describe('Transformation Utilities', () => {
});
describe('calculateTransformedLine', () => {
- it('should transform a line with one transformation', () => {
+ it('should transform a line with one transformation', async () => {
const line = 'Check out @test.png';
const transformations = calculateTransformationsForLine(line);
const result = calculateTransformedLine(line, 0, [0, 0], transformations);
@@ -3189,7 +3189,7 @@ describe('Transformation Utilities', () => {
expect(result.transformedToLogMap[9]).toBe(9); // ' ' before transformation
});
- it('should handle cursor inside transformation', () => {
+ it('should handle cursor inside transformation', async () => {
const line = 'Check out @test.png';
const transformations = calculateTransformationsForLine(line);
// Cursor at '@' (position 10 in the line)
@@ -3206,7 +3206,7 @@ describe('Transformation Utilities', () => {
expect(result.transformedToLogMap[10]).toBe(10); // '@'
});
- it('should handle line with no transformations', () => {
+ it('should handle line with no transformations', async () => {
const line = 'Just some text';
const result = calculateTransformedLine(line, 0, [0, 0], []);
@@ -3218,7 +3218,7 @@ describe('Transformation Utilities', () => {
expect(result.transformedToLogMap[14]).toBe(14); // Trailing position
});
- it('should handle empty line', () => {
+ it('should handle empty line', async () => {
const result = calculateTransformedLine('', 0, [0, 0], []);
expect(result.transformedLine).toBe('');
expect(result.transformedToLogMap).toEqual([0]); // Just the trailing position
@@ -3349,12 +3349,12 @@ describe('Transformation Utilities', () => {
describe('Scroll Regressions', () => {
const scrollViewport: Viewport = { width: 80, height: 5 };
- it('should not show empty viewport when collapsing a large paste that was scrolled', () => {
+ it('should not show empty viewport when collapsing a large paste that was scrolled', async () => {
const largeContent =
'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10';
const placeholder = '[Pasted Text: 10 lines]';
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTextBuffer({
initialText: placeholder,
viewport: scrollViewport,
diff --git a/packages/cli/src/ui/components/views/ChatList.test.tsx b/packages/cli/src/ui/components/views/ChatList.test.tsx
index 28f546d08d..0e8f711264 100644
--- a/packages/cli/src/ui/components/views/ChatList.test.tsx
+++ b/packages/cli/src/ui/components/views/ChatList.test.tsx
@@ -22,19 +22,13 @@ const mockChats: ChatDetail[] = [
describe('', () => {
it('renders correctly with a list of chats', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with no chats', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toContain('No saved conversation checkpoints found.');
expect(lastFrame()).toMatchSnapshot();
unmount();
@@ -47,10 +41,9 @@ describe('', () => {
mtime: 'an-invalid-date-string',
},
];
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('(Invalid Date)');
expect(lastFrame()).toMatchSnapshot();
unmount();
diff --git a/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx b/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx
index 239f728472..c0abdda2a5 100644
--- a/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx
+++ b/packages/cli/src/ui/components/views/ExtensionDetails.test.tsx
@@ -127,7 +127,7 @@ describe('ExtensionDetails', () => {
});
it('should call onLink when "l" is pressed and is linkable', async () => {
- const { stdin, waitUntilReady } = await renderWithProviders(
+ const { stdin } = await renderWithProviders(
{
isInstalled={false}
/>,
);
- await waitUntilReady();
await React.act(async () => {
stdin.write('l');
});
@@ -146,15 +145,14 @@ describe('ExtensionDetails', () => {
});
it('should NOT show "Link" button for GitHub extensions', async () => {
- const { lastFrame, waitUntilReady } = await renderDetails(false);
- await waitUntilReady();
+ const { lastFrame } = await renderDetails(true);
await waitFor(() => {
expect(lastFrame()).not.toContain('[L] Link');
});
});
it('should show "Link" button for local extensions', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
{
isInstalled={false}
/>,
);
- await waitUntilReady();
await waitFor(() => {
expect(lastFrame()).toContain('[L] Link');
});
diff --git a/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx b/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx
index da8d8ba2a4..c66bbbc8cf 100644
--- a/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx
+++ b/packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx
@@ -139,8 +139,7 @@ describe('ExtensionRegistryView', () => {
);
it('should render extensions', async () => {
- const { lastFrame, waitUntilReady } = await renderView();
- await waitUntilReady();
+ const { lastFrame } = await renderView();
await waitFor(() => {
expect(lastFrame()).toContain('Test Extension 1');
diff --git a/packages/cli/src/ui/components/views/ExtensionsList.test.tsx b/packages/cli/src/ui/components/views/ExtensionsList.test.tsx
index e9da6e8b0c..55103da056 100644
--- a/packages/cli/src/ui/components/views/ExtensionsList.test.tsx
+++ b/packages/cli/src/ui/components/views/ExtensionsList.test.tsx
@@ -57,20 +57,18 @@ describe('', () => {
it('should render "No extensions installed." if there are no extensions', async () => {
mockUIState(new Map());
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('No extensions installed.');
unmount();
});
it('should render a list of extensions with their version and status', async () => {
mockUIState(new Map());
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('ext-one (v1.0.0) - active');
expect(output).toContain('ext-two (v2.1.0) - active');
@@ -80,10 +78,9 @@ describe('', () => {
it('should display "unknown state" if an extension has no update state', async () => {
mockUIState(new Map());
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('(unknown state)');
unmount();
});
@@ -122,10 +119,9 @@ describe('', () => {
async ({ state, expectedText }) => {
const updateState = new Map([[mockExtensions[0].name, state]]);
mockUIState(updateState);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(expectedText);
unmount();
},
@@ -160,10 +156,9 @@ describe('', () => {
},
],
};
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('settings:');
expect(output).toContain('- sensitiveApiKey: ***');
diff --git a/packages/cli/src/ui/components/views/McpStatus.test.tsx b/packages/cli/src/ui/components/views/McpStatus.test.tsx
index e4808f31c4..3df80360ab 100644
--- a/packages/cli/src/ui/components/views/McpStatus.test.tsx
+++ b/packages/cli/src/ui/components/views/McpStatus.test.tsx
@@ -54,40 +54,34 @@ describe('McpStatus', () => {
};
it('renders correctly with a connected server', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with authenticated OAuth status', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with expired OAuth status', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with unauthenticated OAuth status', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -97,34 +91,29 @@ describe('McpStatus', () => {
await import('@google/gemini-cli-core'),
'getMCPServerStatus',
).mockReturnValue(MCPServerStatus.DISCONNECTED);
- const { lastFrame, unmount, waitUntilReady } = render(
- ,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly when discovery is in progress', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with schema enabled', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with parametersJsonSchema', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
{
showSchema={true}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with prompts', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
{
]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with resources', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
{
]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with a blocked server', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with both blocked and unblocked servers', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
{
blockedServers={[{ name: 'server-2', extensionName: 'test-extension' }]}
/>,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders only blocked servers when no configured servers exist', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with a connecting server', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders correctly with a server error', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -259,10 +240,9 @@ describe('McpStatus', () => {
uri: `file:///tmp/resource-${i + 1}.txt`,
}));
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('15 resources hidden');
unmount();
});
diff --git a/packages/cli/src/ui/components/views/SkillsList.test.tsx b/packages/cli/src/ui/components/views/SkillsList.test.tsx
index 77b6fee4bc..e6c85cc94d 100644
--- a/packages/cli/src/ui/components/views/SkillsList.test.tsx
+++ b/packages/cli/src/ui/components/views/SkillsList.test.tsx
@@ -35,10 +35,9 @@ describe('SkillsList Component', () => {
];
it('should render enabled and disabled skills separately', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Available Agent Skills:');
@@ -55,10 +54,9 @@ describe('SkillsList Component', () => {
});
it('should not render descriptions when showDescriptions is false', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('skill1');
@@ -72,10 +70,9 @@ describe('SkillsList Component', () => {
});
it('should render "No skills available" when skills list is empty', async () => {
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('No skills available');
@@ -85,10 +82,9 @@ describe('SkillsList Component', () => {
it('should only render Available Agent Skills section when all skills are enabled', async () => {
const enabledOnly = mockSkills.filter((s) => !s.disabled);
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('Available Agent Skills:');
@@ -99,10 +95,9 @@ describe('SkillsList Component', () => {
it('should only render Disabled Skills section when all skills are disabled', async () => {
const disabledOnly = mockSkills.filter((s) => s.disabled);
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).not.toContain('Available Agent Skills:');
@@ -121,10 +116,9 @@ describe('SkillsList Component', () => {
isBuiltin: true,
};
- const { lastFrame, unmount, waitUntilReady } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
const output = lastFrame();
expect(output).toContain('builtin-skill');
diff --git a/packages/cli/src/ui/components/views/ToolsList.test.tsx b/packages/cli/src/ui/components/views/ToolsList.test.tsx
index 1816d8ea70..55841296a1 100644
--- a/packages/cli/src/ui/components/views/ToolsList.test.tsx
+++ b/packages/cli/src/ui/components/views/ToolsList.test.tsx
@@ -32,34 +32,31 @@ const mockTools: ToolDefinition[] = [
describe('', () => {
it('renders correctly with descriptions', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly without descriptions', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
it('renders correctly with no tools', async () => {
- const { lastFrame, waitUntilReady } = await renderWithProviders(
+ const { lastFrame } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
});
});
diff --git a/packages/cli/src/ui/contexts/ScrollProvider.drag.test.tsx b/packages/cli/src/ui/contexts/ScrollProvider.drag.test.tsx
index 2ef66df10d..c1a58bef02 100644
--- a/packages/cli/src/ui/contexts/ScrollProvider.drag.test.tsx
+++ b/packages/cli/src/ui/contexts/ScrollProvider.drag.test.tsx
@@ -87,7 +87,7 @@ describe('ScrollProvider Drag', () => {
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
);
TestScrollableWithScrollTo.displayName = 'TestScrollableWithScrollTo';
- render(
+ await render(
{
});
describe('Event Handling Status', () => {
- it('returns true when scroll event is handled', () => {
+ it('returns true when scroll event is handled', async () => {
const scrollBy = vi.fn();
const getScrollState = vi.fn(() => ({
scrollTop: 0,
@@ -90,7 +90,7 @@ describe('ScrollProvider', () => {
innerHeight: 10,
}));
- render(
+ await render(
{
expect(handled).toBe(true);
});
- it('returns false when scroll event is ignored (cannot scroll further)', () => {
+ it('returns false when scroll event is ignored (cannot scroll further)', async () => {
const scrollBy = vi.fn();
// Already at bottom
const getScrollState = vi.fn(() => ({
@@ -128,7 +128,7 @@ describe('ScrollProvider', () => {
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
innerHeight: 10,
}));
- render(
+ await render(
{
- it('should provide the correct initial state', () => {
+ it('should provide the correct initial state', async () => {
const contextRef: MutableRefObject<
ReturnType | undefined
> = { current: undefined };
- const { unmount } = render(
+ const { unmount } = await render(
,
@@ -73,12 +73,12 @@ describe('SessionStatsContext', () => {
unmount();
});
- it('should update metrics when the uiTelemetryService emits an update', () => {
+ it('should update metrics when the uiTelemetryService emits an update', async () => {
const contextRef: MutableRefObject<
ReturnType | undefined
> = { current: undefined };
- const { unmount } = render(
+ const { unmount } = await render(
,
@@ -149,7 +149,7 @@ describe('SessionStatsContext', () => {
unmount();
});
- it('should not update metrics if the data is the same', () => {
+ it('should not update metrics if the data is the same', async () => {
const contextRef: MutableRefObject<
ReturnType | undefined
> = { current: undefined };
@@ -161,7 +161,7 @@ describe('SessionStatsContext', () => {
return null;
};
- const { unmount } = render(
+ const { unmount } = await render(
,
@@ -239,12 +239,12 @@ describe('SessionStatsContext', () => {
unmount();
});
- it('should update session ID and reset stats when the uiTelemetryService emits a clear event', () => {
+ it('should update session ID and reset stats when the uiTelemetryService emits a clear event', async () => {
const contextRef: MutableRefObject<
ReturnType | undefined
> = { current: undefined };
- const { unmount } = render(
+ const { unmount } = await render(
,
@@ -267,12 +267,12 @@ describe('SessionStatsContext', () => {
unmount();
});
- it('should throw an error when useSessionStats is used outside of a provider', () => {
+ it('should throw an error when useSessionStats is used outside of a provider', async () => {
const onError = vi.fn();
// Suppress console.error from React for this test
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
- const { unmount } = render(
+ const { unmount } = await render(
,
diff --git a/packages/cli/src/ui/contexts/SettingsContext.test.tsx b/packages/cli/src/ui/contexts/SettingsContext.test.tsx
index 3d14c3505b..491daa8200 100644
--- a/packages/cli/src/ui/contexts/SettingsContext.test.tsx
+++ b/packages/cli/src/ui/contexts/SettingsContext.test.tsx
@@ -90,15 +90,15 @@ describe('SettingsContext', () => {
);
- it('should provide the correct initial state', () => {
- const { result } = renderHook(() => useSettingsStore(), { wrapper });
+ it('should provide the correct initial state', async () => {
+ const { result } = await renderHook(() => useSettingsStore(), { wrapper });
expect(result.current.settings.merged).toEqual(mockSnapshot.merged);
expect(result.current.settings.isTrusted).toBe(true);
});
- it('should allow accessing settings for a specific scope', () => {
- const { result } = renderHook(() => useSettingsStore(), { wrapper });
+ it('should allow accessing settings for a specific scope', async () => {
+ const { result } = await renderHook(() => useSettingsStore(), { wrapper });
const userSettings = result.current.settings.forScope(SettingScope.User);
expect(userSettings).toBe(mockSnapshot.user);
@@ -109,8 +109,8 @@ describe('SettingsContext', () => {
expect(workspaceSettings).toBe(mockSnapshot.workspace);
});
- it('should trigger re-renders when settings change (external event)', () => {
- const { result } = renderHook(() => useSettingsStore(), { wrapper });
+ it('should trigger re-renders when settings change (external event)', async () => {
+ const { result } = await renderHook(() => useSettingsStore(), { wrapper });
expect(result.current.settings.merged.ui?.theme).toBe('default-theme');
@@ -130,8 +130,8 @@ describe('SettingsContext', () => {
expect(result.current.settings.merged.ui?.theme).toBe('new-theme');
});
- it('should call store.setValue when setSetting is called', () => {
- const { result } = renderHook(() => useSettingsStore(), { wrapper });
+ it('should call store.setValue when setSetting is called', async () => {
+ const { result } = await renderHook(() => useSettingsStore(), { wrapper });
act(() => {
result.current.setSetting(SettingScope.User, 'ui.theme', 'dark');
@@ -144,12 +144,12 @@ describe('SettingsContext', () => {
);
});
- it('should throw error if used outside provider', () => {
+ it('should throw error if used outside provider', async () => {
const onError = vi.fn();
// Suppress console.error (React logs error boundary info)
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
- render(
+ await render(
,
diff --git a/packages/cli/src/ui/contexts/TerminalContext.test.tsx b/packages/cli/src/ui/contexts/TerminalContext.test.tsx
index 31ee293841..15325b76ba 100644
--- a/packages/cli/src/ui/contexts/TerminalContext.test.tsx
+++ b/packages/cli/src/ui/contexts/TerminalContext.test.tsx
@@ -51,12 +51,11 @@ const TestComponent = ({ onColor }: { onColor: (c: string) => void }) => {
describe('TerminalContext', () => {
it('should parse OSC 11 response', async () => {
const handleColor = vi.fn();
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
await act(async () => {
mockStdin.emit('data', '\x1b]11;rgb:ffff/ffff/ffff\x1b\\');
@@ -71,12 +70,11 @@ describe('TerminalContext', () => {
it('should handle partial chunks', async () => {
const handleColor = vi.fn();
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
await act(async () => {
mockStdin.emit('data', '\x1b]11;rgb:0000/');
diff --git a/packages/cli/src/ui/contexts/ToolActionsContext.test.tsx b/packages/cli/src/ui/contexts/ToolActionsContext.test.tsx
index 8a75bf7d57..642eec0cde 100644
--- a/packages/cli/src/ui/contexts/ToolActionsContext.test.tsx
+++ b/packages/cli/src/ui/contexts/ToolActionsContext.test.tsx
@@ -7,7 +7,6 @@
import { act } from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '../../test-utils/render.js';
-import { waitFor } from '../../test-utils/async.js';
import { ToolActionsProvider, useToolActions } from './ToolActionsContext.js';
import {
type Config,
@@ -81,7 +80,7 @@ describe('ToolActionsContext', () => {
);
it('publishes to MessageBus for tools with correlationId', async () => {
- const { result } = renderHook(() => useToolActions(), { wrapper });
+ const { result } = await renderHook(() => useToolActions(), { wrapper });
await result.current.confirm(
'modern-call',
@@ -99,7 +98,7 @@ describe('ToolActionsContext', () => {
});
it('handles cancel by calling confirm with Cancel outcome', async () => {
- const { result } = renderHook(() => useToolActions(), { wrapper });
+ const { result } = await renderHook(() => useToolActions(), { wrapper });
await result.current.cancel('modern-call');
@@ -112,20 +111,26 @@ describe('ToolActionsContext', () => {
});
it('resolves IDE diffs for edit tools when in IDE mode', async () => {
+ let deferredIdeClient: { resolve: (c: IdeClient) => void };
const mockIdeClient = {
isDiffingEnabled: vi.fn().mockReturnValue(true),
resolveDiffFromCli: vi.fn(),
+ addStatusChangeListener: vi.fn(),
+ removeStatusChangeListener: vi.fn(),
} as unknown as IdeClient;
- vi.mocked(IdeClient.getInstance).mockResolvedValue(mockIdeClient);
+
+ vi.mocked(IdeClient.getInstance).mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredIdeClient = { resolve };
+ }),
+ );
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
- const { result } = renderHook(() => useToolActions(), { wrapper });
+ const { result } = await renderHook(() => useToolActions(), { wrapper });
- // Wait for IdeClient initialization in useEffect
await act(async () => {
- await waitFor(() => expect(IdeClient.getInstance).toHaveBeenCalled());
- // Give React a chance to update state
- await new Promise((resolve) => setTimeout(resolve, 0));
+ deferredIdeClient.resolve(mockIdeClient);
});
await result.current.confirm(
@@ -146,6 +151,8 @@ describe('ToolActionsContext', () => {
it('updates isDiffingEnabled when IdeClient status changes', async () => {
let statusListener: () => void = () => {};
+ let deferredIdeClient: { resolve: (c: IdeClient) => void };
+
const mockIdeClient = {
isDiffingEnabled: vi.fn().mockReturnValue(false),
addStatusChangeListener: vi.fn().mockImplementation((listener) => {
@@ -154,15 +161,18 @@ describe('ToolActionsContext', () => {
removeStatusChangeListener: vi.fn(),
} as unknown as IdeClient;
- vi.mocked(IdeClient.getInstance).mockResolvedValue(mockIdeClient);
+ vi.mocked(IdeClient.getInstance).mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredIdeClient = { resolve };
+ }),
+ );
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
- const { result } = renderHook(() => useToolActions(), { wrapper });
+ const { result } = await renderHook(() => useToolActions(), { wrapper });
- // Wait for initialization
await act(async () => {
- await waitFor(() => expect(IdeClient.getInstance).toHaveBeenCalled());
- await new Promise((resolve) => setTimeout(resolve, 0));
+ deferredIdeClient.resolve(mockIdeClient);
});
expect(result.current.isDiffingEnabled).toBe(false);
@@ -202,7 +212,7 @@ describe('ToolActionsContext', () => {
} as unknown as SerializableConfirmationDetails,
};
- const { result } = renderHook(() => useToolActions(), {
+ const { result } = await renderHook(() => useToolActions(), {
wrapper: ({ children }) => (
{children}
diff --git a/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx b/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx
index f5e3b61e2b..f9416d379f 100644
--- a/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx
+++ b/packages/cli/src/ui/hooks/shellCommandProcessor.test.tsx
@@ -139,7 +139,7 @@ describe('useShellCommandProcessor', () => {
});
});
- const renderProcessorHook = () => {
+ const renderProcessorHook = async () => {
let hookResult: ReturnType;
let renderCount = 0;
function TestComponent({
@@ -163,7 +163,7 @@ describe('useShellCommandProcessor', () => {
);
return null;
}
- const { rerender } = render();
+ const { rerender } = await render();
return {
result: {
get current() {
@@ -193,7 +193,7 @@ describe('useShellCommandProcessor', () => {
});
it('should initiate command execution and set pending state', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('ls -l', new AbortController().signal);
@@ -226,7 +226,7 @@ describe('useShellCommandProcessor', () => {
});
it('should handle successful execution and update history correctly', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand(
@@ -258,7 +258,7 @@ describe('useShellCommandProcessor', () => {
});
it('should handle command failure and display error status', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand(
@@ -293,7 +293,7 @@ describe('useShellCommandProcessor', () => {
});
it('should update UI for text streams (non-interactive)', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand(
'stream',
@@ -356,7 +356,7 @@ describe('useShellCommandProcessor', () => {
});
it('should show binary progress messages correctly', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand(
'cat img',
@@ -424,7 +424,7 @@ describe('useShellCommandProcessor', () => {
it('should not wrap the command on Windows', async () => {
vi.mocked(os.platform).mockReturnValue('win32');
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('dir', new AbortController().signal);
@@ -446,7 +446,7 @@ describe('useShellCommandProcessor', () => {
});
it('should handle command abort and display cancelled status', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
const abortController = new AbortController();
act(() => {
@@ -470,7 +470,7 @@ describe('useShellCommandProcessor', () => {
});
it('should handle binary output result correctly', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
const binaryBuffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
mockIsBinary.mockReturnValue(true);
@@ -497,7 +497,7 @@ describe('useShellCommandProcessor', () => {
});
it('should handle promise rejection and show an error', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
const testError = new Error('Unexpected failure');
mockShellExecutionService.mockImplementation(() => ({
pid: 12345,
@@ -531,7 +531,7 @@ describe('useShellCommandProcessor', () => {
// Mock that the temp file was created before the error was thrown
vi.mocked(fs.existsSync).mockReturnValue(true);
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand(
@@ -561,7 +561,7 @@ describe('useShellCommandProcessor', () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue('/test/dir/new'); // A different directory
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand(
'cd new',
@@ -586,7 +586,7 @@ describe('useShellCommandProcessor', () => {
vi.mocked(fs.existsSync).mockReturnValue(true);
vi.mocked(fs.readFileSync).mockReturnValue('/test/dir'); // The same directory
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand('ls', new AbortController().signal);
});
@@ -616,13 +616,13 @@ describe('useShellCommandProcessor', () => {
});
});
- it('should have activeShellPtyId as null initially', () => {
- const { result } = renderProcessorHook();
+ it('should have activeShellPtyId as null initially', async () => {
+ const { result } = await renderProcessorHook();
expect(result.current.activeShellPtyId).toBeNull();
});
it('should set activeShellPtyId when a command with a PID starts', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('ls', new AbortController().signal);
@@ -632,7 +632,7 @@ describe('useShellCommandProcessor', () => {
});
it('should update the pending history item with the ptyId', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('ls', new AbortController().signal);
@@ -655,7 +655,7 @@ describe('useShellCommandProcessor', () => {
});
it('should reset activeShellPtyId to null after successful execution', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('ls', new AbortController().signal);
@@ -673,7 +673,7 @@ describe('useShellCommandProcessor', () => {
});
it('should reset activeShellPtyId to null after failed execution', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand(
@@ -703,7 +703,7 @@ describe('useShellCommandProcessor', () => {
}),
}),
);
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('cmd', new AbortController().signal);
@@ -725,7 +725,7 @@ describe('useShellCommandProcessor', () => {
mockShellExecutionService.mockImplementation(() => {
throw new Error('Sync Error');
});
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
expect(result.current.activeShellPtyId).toBeNull(); // Pre-condition
@@ -754,7 +754,7 @@ describe('useShellCommandProcessor', () => {
});
});
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.handleShellCommand('ls', new AbortController().signal);
@@ -769,7 +769,7 @@ describe('useShellCommandProcessor', () => {
describe('Background Shell Management', () => {
it('should register a background shell and update count', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(1001, 'bg-cmd', 'initial');
@@ -792,7 +792,7 @@ describe('useShellCommandProcessor', () => {
});
it('should toggle background shell visibility', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(1001, 'bg-cmd', 'initial');
@@ -814,7 +814,7 @@ describe('useShellCommandProcessor', () => {
});
it('should show info message when toggling background shells if none are active', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.toggleBackgroundShell();
@@ -831,7 +831,7 @@ describe('useShellCommandProcessor', () => {
});
it('should dismiss a background shell and remove it from state', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(1001, 'bg-cmd', 'initial');
@@ -858,7 +858,7 @@ describe('useShellCommandProcessor', () => {
});
});
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
await act(async () => {
result.current.handleShellCommand('top', new AbortController().signal);
@@ -892,7 +892,7 @@ describe('useShellCommandProcessor', () => {
});
it('should persist background shell on successful exit and mark as exited', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(888, 'auto-exit', '');
@@ -919,7 +919,7 @@ describe('useShellCommandProcessor', () => {
});
it('should persist background shell on failed exit', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(999, 'fail-exit', '');
@@ -950,7 +950,7 @@ describe('useShellCommandProcessor', () => {
});
it('should NOT trigger re-render on background shell output when visible', async () => {
- const { result, getRenderCount } = renderProcessorHook();
+ const { result, getRenderCount } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(1001, 'bg-cmd', 'initial');
@@ -980,7 +980,7 @@ describe('useShellCommandProcessor', () => {
});
it('should NOT trigger re-render on background shell output when hidden', async () => {
- const { result, getRenderCount } = renderProcessorHook();
+ const { result, getRenderCount } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(1001, 'bg-cmd', 'initial');
@@ -1006,7 +1006,7 @@ describe('useShellCommandProcessor', () => {
});
it('should trigger re-render on binary progress when visible', async () => {
- const { result, getRenderCount } = renderProcessorHook();
+ const { result, getRenderCount } = await renderProcessorHook();
act(() => {
result.current.registerBackgroundShell(1001, 'bg-cmd', 'initial');
@@ -1037,7 +1037,7 @@ describe('useShellCommandProcessor', () => {
});
it('should NOT hide background shell when model is responding without confirmation', async () => {
- const { result, rerender } = renderProcessorHook();
+ const { result, rerender } = await renderProcessorHook();
// 1. Register and show background shell
act(() => {
@@ -1058,7 +1058,7 @@ describe('useShellCommandProcessor', () => {
});
it('should hide background shell when waiting for confirmation and restore after delay', async () => {
- const { result, rerender } = renderProcessorHook();
+ const { result, rerender } = await renderProcessorHook();
// 1. Register and show background shell
act(() => {
@@ -1092,7 +1092,7 @@ describe('useShellCommandProcessor', () => {
});
it('should auto-hide background shell when foreground shell starts and restore when it ends', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
// 1. Register and show background shell
act(() => {
@@ -1128,7 +1128,7 @@ describe('useShellCommandProcessor', () => {
});
it('should NOT restore background shell if it was manually hidden during foreground execution', async () => {
- const { result } = renderProcessorHook();
+ const { result } = await renderProcessorHook();
// 1. Register and show background shell
act(() => {
diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx b/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx
index 04b521e6a6..33df14dcce 100644
--- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx
+++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.tsx
@@ -187,7 +187,7 @@ describe('useSlashCommandProcessor', () => {
let rerender!: (props?: unknown) => void;
await act(async () => {
- const hook = renderHook(() =>
+ const hook = await renderHook(() =>
useSlashCommandProcessor(
mockConfig,
mockSettings,
diff --git a/packages/cli/src/ui/hooks/useAlternateBuffer.test.ts b/packages/cli/src/ui/hooks/useAlternateBuffer.test.ts
index bf0e27aa37..23e5a8b444 100644
--- a/packages/cli/src/ui/hooks/useAlternateBuffer.test.ts
+++ b/packages/cli/src/ui/hooks/useAlternateBuffer.test.ts
@@ -25,32 +25,32 @@ describe('useAlternateBuffer', () => {
vi.clearAllMocks();
});
- it('should return false when config.getUseAlternateBuffer returns false', () => {
+ it('should return false when config.getUseAlternateBuffer returns false', async () => {
mockUseConfig.mockReturnValue({
getUseAlternateBuffer: () => false,
} as unknown as ReturnType);
- const { result } = renderHook(() => useAlternateBuffer());
+ const { result } = await renderHook(() => useAlternateBuffer());
expect(result.current).toBe(false);
});
- it('should return true when config.getUseAlternateBuffer returns true', () => {
+ it('should return true when config.getUseAlternateBuffer returns true', async () => {
mockUseConfig.mockReturnValue({
getUseAlternateBuffer: () => true,
} as unknown as ReturnType);
- const { result } = renderHook(() => useAlternateBuffer());
+ const { result } = await renderHook(() => useAlternateBuffer());
expect(result.current).toBe(true);
});
- it('should return the immutable config value, not react to settings changes', () => {
+ it('should return the immutable config value, not react to settings changes', async () => {
const mockConfig = {
getUseAlternateBuffer: () => true,
} as unknown as ReturnType;
mockUseConfig.mockReturnValue(mockConfig);
- const { result, rerender } = renderHook(() => useAlternateBuffer());
+ const { result, rerender } = await renderHook(() => useAlternateBuffer());
// Value should remain true even after rerender
expect(result.current).toBe(true);
diff --git a/packages/cli/src/ui/hooks/useAnimatedScrollbar.test.tsx b/packages/cli/src/ui/hooks/useAnimatedScrollbar.test.tsx
index 32f4c0cedf..2c6959d71b 100644
--- a/packages/cli/src/ui/hooks/useAnimatedScrollbar.test.tsx
+++ b/packages/cli/src/ui/hooks/useAnimatedScrollbar.test.tsx
@@ -25,33 +25,35 @@ describe('useAnimatedScrollbar', () => {
vi.useRealTimers();
});
- it('should not increment debugNumAnimatedComponents when not focused', () => {
- render();
+ it('should not increment debugNumAnimatedComponents when not focused', async () => {
+ await render();
expect(debugState.debugNumAnimatedComponents).toBe(0);
});
- it('should not increment debugNumAnimatedComponents on initial mount even if focused', () => {
- render();
+ it('should not increment debugNumAnimatedComponents on initial mount even if focused', async () => {
+ await render();
expect(debugState.debugNumAnimatedComponents).toBe(0);
});
- it('should increment debugNumAnimatedComponents when becoming focused', () => {
- const { rerender } = render();
+ it('should increment debugNumAnimatedComponents when becoming focused', async () => {
+ const { rerender } = await render();
expect(debugState.debugNumAnimatedComponents).toBe(0);
rerender();
expect(debugState.debugNumAnimatedComponents).toBe(1);
});
- it('should decrement debugNumAnimatedComponents when becoming unfocused', () => {
- const { rerender } = render();
+ it('should decrement debugNumAnimatedComponents when becoming unfocused', async () => {
+ const { rerender } = await render();
rerender();
expect(debugState.debugNumAnimatedComponents).toBe(1);
rerender();
expect(debugState.debugNumAnimatedComponents).toBe(0);
});
- it('should decrement debugNumAnimatedComponents on unmount', () => {
- const { rerender, unmount } = render();
+ it('should decrement debugNumAnimatedComponents on unmount', async () => {
+ const { rerender, unmount } = await render(
+ ,
+ );
rerender();
expect(debugState.debugNumAnimatedComponents).toBe(1);
unmount();
@@ -59,7 +61,7 @@ describe('useAnimatedScrollbar', () => {
});
it('should decrement debugNumAnimatedComponents after animation finishes', async () => {
- const { rerender } = render();
+ const { rerender } = await render();
rerender();
expect(debugState.debugNumAnimatedComponents).toBe(1);
@@ -80,7 +82,7 @@ describe('useAnimatedScrollbar', () => {
let currentTime = 1000;
dateSpy.mockImplementation(() => currentTime);
- const { rerender } = render();
+ const { rerender } = await render();
// Start animation. This captures start = 1000.
rerender();
diff --git a/packages/cli/src/ui/hooks/useApprovalModeIndicator.test.ts b/packages/cli/src/ui/hooks/useApprovalModeIndicator.test.ts
index 34802ad495..9771d10d83 100644
--- a/packages/cli/src/ui/hooks/useApprovalModeIndicator.test.ts
+++ b/packages/cli/src/ui/hooks/useApprovalModeIndicator.test.ts
@@ -138,9 +138,9 @@ describe('useApprovalModeIndicator', () => {
mockConfigInstance = new (Config as any)() as MockConfigInstanceShape;
});
- it('should initialize with ApprovalMode.AUTO_EDIT if config.getApprovalMode returns ApprovalMode.AUTO_EDIT', () => {
+ it('should initialize with ApprovalMode.AUTO_EDIT if config.getApprovalMode returns ApprovalMode.AUTO_EDIT', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.AUTO_EDIT);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
@@ -150,9 +150,9 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode).toHaveBeenCalledTimes(1);
});
- it('should initialize with ApprovalMode.DEFAULT if config.getApprovalMode returns ApprovalMode.DEFAULT', () => {
+ it('should initialize with ApprovalMode.DEFAULT if config.getApprovalMode returns ApprovalMode.DEFAULT', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
@@ -162,9 +162,9 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode).toHaveBeenCalledTimes(1);
});
- it('should initialize with ApprovalMode.YOLO if config.getApprovalMode returns ApprovalMode.YOLO', () => {
+ it('should initialize with ApprovalMode.YOLO if config.getApprovalMode returns ApprovalMode.YOLO', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.YOLO);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
@@ -174,9 +174,9 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode).toHaveBeenCalledTimes(1);
});
- it('should cycle the indicator and update config when Shift+Tab or Ctrl+Y is pressed', () => {
+ it('should cycle the indicator and update config when Shift+Tab or Ctrl+Y is pressed', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
@@ -238,9 +238,9 @@ describe('useApprovalModeIndicator', () => {
expect(result.current).toBe(ApprovalMode.AUTO_EDIT);
});
- it('should not toggle if only one key or other keys combinations are pressed', () => {
+ it('should not toggle if only one key or other keys combinations are pressed', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
@@ -297,9 +297,9 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.setApprovalMode).not.toHaveBeenCalled();
});
- it('should update indicator when config value changes externally (useEffect dependency)', () => {
+ it('should update indicator when config value changes externally (useEffect dependency)', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
(props: { config: ActualConfigType; addItem: () => void }) =>
useApprovalModeIndicator(props),
{
@@ -326,7 +326,7 @@ describe('useApprovalModeIndicator', () => {
mockConfigInstance.isTrustedFolder.mockReturnValue(false);
});
- it('should not enable YOLO mode when Ctrl+Y is pressed', () => {
+ it('should not enable YOLO mode when Ctrl+Y is pressed', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
mockConfigInstance.setApprovalMode.mockImplementation(() => {
throw new Error(
@@ -334,7 +334,7 @@ describe('useApprovalModeIndicator', () => {
);
});
const mockAddItem = vi.fn();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -356,7 +356,7 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
});
- it('should not enable AUTO_EDIT mode when Shift+Tab is pressed', () => {
+ it('should not enable AUTO_EDIT mode when Shift+Tab is pressed', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
mockConfigInstance.setApprovalMode.mockImplementation(() => {
throw new Error(
@@ -364,7 +364,7 @@ describe('useApprovalModeIndicator', () => {
);
});
const mockAddItem = vi.fn();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -389,10 +389,10 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
});
- it('should disable YOLO mode when Ctrl+Y is pressed', () => {
+ it('should disable YOLO mode when Ctrl+Y is pressed', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.YOLO);
const mockAddItem = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -409,12 +409,12 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
});
- it('should disable AUTO_EDIT mode when Shift+Tab is pressed', () => {
+ it('should disable AUTO_EDIT mode when Shift+Tab is pressed', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(
ApprovalMode.AUTO_EDIT,
);
const mockAddItem = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -434,7 +434,7 @@ describe('useApprovalModeIndicator', () => {
expect(mockConfigInstance.getApprovalMode()).toBe(ApprovalMode.DEFAULT);
});
- it('should show a warning when trying to enable privileged modes', () => {
+ it('should show a warning when trying to enable privileged modes', async () => {
// Mock the error thrown by setApprovalMode
const errorMessage =
'Cannot enable privileged approval modes in an untrusted folder.';
@@ -443,7 +443,7 @@ describe('useApprovalModeIndicator', () => {
});
const mockAddItem = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -491,13 +491,13 @@ describe('useApprovalModeIndicator', () => {
}
});
- it('should not enable YOLO mode when Ctrl+Y is pressed and add an info message', () => {
+ it('should not enable YOLO mode when Ctrl+Y is pressed and add an info message', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
mockConfigInstance.getRemoteAdminSettings.mockReturnValue({
strictModeDisabled: true,
});
const mockAddItem = vi.fn();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -524,14 +524,14 @@ describe('useApprovalModeIndicator', () => {
expect(result.current).toBe(ApprovalMode.DEFAULT);
});
- it('should show admin error message when YOLO mode is disabled by admin', () => {
+ it('should show admin error message when YOLO mode is disabled by admin', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
mockConfigInstance.getRemoteAdminSettings.mockReturnValue({
mcpEnabled: true,
});
const mockAddItem = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -551,12 +551,12 @@ describe('useApprovalModeIndicator', () => {
);
});
- it('should show default error message when admin settings are empty', () => {
+ it('should show default error message when admin settings are empty', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
mockConfigInstance.getRemoteAdminSettings.mockReturnValue({});
const mockAddItem = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: mockAddItem,
@@ -577,12 +577,12 @@ describe('useApprovalModeIndicator', () => {
});
});
- it('should call onApprovalModeChange when switching to YOLO mode', () => {
+ it('should call onApprovalModeChange when switching to YOLO mode', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
const mockOnApprovalModeChange = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
onApprovalModeChange: mockOnApprovalModeChange,
@@ -599,12 +599,12 @@ describe('useApprovalModeIndicator', () => {
expect(mockOnApprovalModeChange).toHaveBeenCalledWith(ApprovalMode.YOLO);
});
- it('should call onApprovalModeChange when switching to AUTO_EDIT mode', () => {
+ it('should call onApprovalModeChange when switching to AUTO_EDIT mode', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
const mockOnApprovalModeChange = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
onApprovalModeChange: mockOnApprovalModeChange,
@@ -623,12 +623,12 @@ describe('useApprovalModeIndicator', () => {
);
});
- it('should call onApprovalModeChange when switching to DEFAULT mode', () => {
+ it('should call onApprovalModeChange when switching to DEFAULT mode', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.YOLO);
const mockOnApprovalModeChange = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
onApprovalModeChange: mockOnApprovalModeChange,
@@ -645,10 +645,10 @@ describe('useApprovalModeIndicator', () => {
expect(mockOnApprovalModeChange).toHaveBeenCalledWith(ApprovalMode.DEFAULT);
});
- it('should not call onApprovalModeChange when callback is not provided', () => {
+ it('should not call onApprovalModeChange when callback is not provided', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
}),
@@ -664,12 +664,12 @@ describe('useApprovalModeIndicator', () => {
// Should not throw an error when callback is not provided
});
- it('should handle multiple mode changes correctly', () => {
+ it('should handle multiple mode changes correctly', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.DEFAULT);
const mockOnApprovalModeChange = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
onApprovalModeChange: mockOnApprovalModeChange,
@@ -697,10 +697,10 @@ describe('useApprovalModeIndicator', () => {
);
});
- it('should cycle to PLAN when allowPlanMode is true', () => {
+ it('should cycle to PLAN when allowPlanMode is true', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.AUTO_EDIT);
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
@@ -717,10 +717,10 @@ describe('useApprovalModeIndicator', () => {
);
});
- it('should cycle to DEFAULT when allowPlanMode is false', () => {
+ it('should cycle to DEFAULT when allowPlanMode is false', async () => {
mockConfigInstance.getApprovalMode.mockReturnValue(ApprovalMode.AUTO_EDIT);
- renderHook(() =>
+ await renderHook(() =>
useApprovalModeIndicator({
config: mockConfigInstance as unknown as ActualConfigType,
addItem: vi.fn(),
diff --git a/packages/cli/src/ui/hooks/useAtCompletion.test.ts b/packages/cli/src/ui/hooks/useAtCompletion.test.ts
index 6821f3489a..381849a1d2 100644
--- a/packages/cli/src/ui/hooks/useAtCompletion.test.ts
+++ b/packages/cli/src/ui/hooks/useAtCompletion.test.ts
@@ -83,7 +83,7 @@ describe('useAtCompletion', () => {
};
testRootDir = await createTmpDir(structure);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
);
@@ -114,7 +114,7 @@ describe('useAtCompletion', () => {
};
testRootDir = await createTmpDir(structure);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, 'src/', mockConfig, testRootDir),
);
@@ -137,7 +137,7 @@ describe('useAtCompletion', () => {
};
testRootDir = await createTmpDir(structure);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
);
@@ -170,7 +170,7 @@ describe('useAtCompletion', () => {
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(fileSearch);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(
true,
'CrAzYCaSe',
@@ -201,7 +201,7 @@ describe('useAtCompletion', () => {
],
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, 'logs', mockConfig, '/tmp'),
);
@@ -220,24 +220,31 @@ describe('useAtCompletion', () => {
it('should be in a loading state during initial file system crawl', async () => {
testRootDir = await createTmpDir({});
- // Mock FileSearch to be slow to catch the loading state
+ let deferredInit: { resolve: (value?: unknown) => void };
+ // Mock FileSearch to control when initialization finishes
const mockFileSearch = {
- initialize: vi.fn().mockImplementation(async () => {
- await new Promise((resolve) => setTimeout(resolve, 50));
- }),
+ initialize: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredInit = { resolve };
+ }),
+ ),
search: vi.fn().mockResolvedValue([]),
};
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(
mockFileSearch as unknown as FileSearch,
);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
);
- // It's initially true because the effect runs synchronously.
- await waitFor(() => {
- expect(result.current.isLoadingSuggestions).toBe(true);
+ // It's true because the promise hasn't resolved yet
+ expect(result.current.isLoadingSuggestions).toBe(true);
+
+ // Resolve the initialization
+ await act(async () => {
+ deferredInit.resolve();
});
// Wait for the loading to complete.
@@ -250,7 +257,7 @@ describe('useAtCompletion', () => {
const structure: FileSystemStructure = { 'a.txt': '', 'b.txt': '' };
testRootDir = await createTmpDir(structure);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ pattern }) =>
useTestHarnessForAtCompletion(true, pattern, mockConfig, testRootDir),
{ initialProps: { pattern: 'a' } },
@@ -294,8 +301,17 @@ describe('useAtCompletion', () => {
await realFileSearch.initialize();
// Mock that returns results immediately but we'll control timing with fake timers
+ let deferredInit: {
+ resolve: (value?: unknown) => void;
+ reject: (e: Error) => void;
+ };
const mockFileSearch: FileSearch = {
- initialize: vi.fn().mockResolvedValue(undefined),
+ initialize: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve, reject) => {
+ deferredInit = { resolve, reject };
+ }),
+ ),
search: vi
.fn()
.mockImplementation(async (pattern, options) =>
@@ -304,12 +320,16 @@ describe('useAtCompletion', () => {
};
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ pattern }) =>
useTestHarnessForAtCompletion(true, pattern, mockConfig, testRootDir),
{ initialProps: { pattern: 'a' } },
);
+ await act(async () => {
+ deferredInit.resolve();
+ });
+
// Wait for the initial search to complete (using real timers)
await waitFor(() => {
expect(result.current.suggestions.map((s) => s.value)).toEqual([
@@ -355,8 +375,17 @@ describe('useAtCompletion', () => {
testRootDir = await createTmpDir(structure);
const abortSpy = vi.spyOn(AbortController.prototype, 'abort');
+ let deferredInit: {
+ resolve: (value?: unknown) => void;
+ reject: (e: Error) => void;
+ };
const mockFileSearch: FileSearch = {
- initialize: vi.fn().mockResolvedValue(undefined),
+ initialize: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve, reject) => {
+ deferredInit = { resolve, reject };
+ }),
+ ),
search: vi.fn().mockImplementation(async (pattern: string) => {
const delay = pattern === 'a' ? 500 : 50;
await new Promise((resolve) => setTimeout(resolve, delay));
@@ -365,12 +394,16 @@ describe('useAtCompletion', () => {
};
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ pattern }) =>
useTestHarnessForAtCompletion(true, pattern, mockConfig, testRootDir),
{ initialProps: { pattern: 'a' } },
);
+ await act(async () => {
+ deferredInit.resolve();
+ });
+
// Wait for the hook to be ready (initialization is complete)
await waitFor(() => {
expect(mockFileSearch.search).toHaveBeenCalledWith(
@@ -408,7 +441,7 @@ describe('useAtCompletion', () => {
const structure: FileSystemStructure = { 'a.txt': '' };
testRootDir = await createTmpDir(structure);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ enabled }) =>
useTestHarnessForAtCompletion(enabled, 'a', mockConfig, testRootDir),
{ initialProps: { enabled: true } },
@@ -431,21 +464,32 @@ describe('useAtCompletion', () => {
it('should reset the state when disabled after being in an ERROR state', async () => {
testRootDir = await createTmpDir({});
+ let deferredInit: {
+ resolve: (value?: unknown) => void;
+ reject: (e: Error) => void;
+ };
// Force an error during initialization
const mockFileSearch: FileSearch = {
- initialize: vi
- .fn()
- .mockRejectedValue(new Error('Initialization failed')),
+ initialize: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve, reject) => {
+ deferredInit = { resolve, reject };
+ }),
+ ),
search: vi.fn(),
};
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ enabled }) =>
useTestHarnessForAtCompletion(enabled, '', mockConfig, testRootDir),
{ initialProps: { enabled: true } },
);
+ await act(async () => {
+ deferredInit.reject(new Error('Initialization failed'));
+ });
+
// Wait for the hook to enter the error state
await waitFor(() => {
expect(result.current.isLoadingSuggestions).toBe(false);
@@ -474,7 +518,7 @@ describe('useAtCompletion', () => {
};
testRootDir = await createTmpDir(structure);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
);
@@ -495,7 +539,7 @@ describe('useAtCompletion', () => {
};
testRootDir = await createTmpDir(structure);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', undefined, testRootDir),
);
@@ -515,7 +559,7 @@ describe('useAtCompletion', () => {
const structure2: FileSystemStructure = { 'file2.txt': '' };
const rootDir2 = await createTmpDir(structure2);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ cwd, pattern }) =>
useTestHarnessForAtCompletion(true, pattern, mockConfig, cwd),
{
@@ -574,7 +618,21 @@ describe('useAtCompletion', () => {
getFileFilteringEnableFuzzySearch: () => true,
} as unknown as Config;
- const { result } = renderHook(() =>
+ let deferredInit: { resolve: (value?: unknown) => void };
+ const mockFileSearch: FileSearch = {
+ initialize: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredInit = { resolve };
+ }),
+ ),
+ search: vi.fn().mockResolvedValue(['src/', 'file.txt']),
+ };
+ vi.spyOn(FileSearchFactory, 'create').mockReturnValue(
+ mockFileSearch as unknown as FileSearch,
+ );
+
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(
true,
'',
@@ -583,6 +641,10 @@ describe('useAtCompletion', () => {
),
);
+ await act(async () => {
+ deferredInit.resolve();
+ });
+
await waitFor(() => {
expect(result.current.suggestions.length).toBeGreaterThan(0);
});
@@ -619,7 +681,7 @@ describe('useAtCompletion', () => {
}),
} as unknown as Config;
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', multiDirConfig, cwdDir),
);
@@ -656,7 +718,7 @@ describe('useAtCompletion', () => {
}),
} as unknown as Config;
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', dynamicConfig, cwdDir),
);
@@ -695,7 +757,7 @@ describe('useAtCompletion', () => {
}),
} as unknown as Config;
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, 'readme', multiDirConfig, dir1),
);
diff --git a/packages/cli/src/ui/hooks/useAtCompletion_agents.test.ts b/packages/cli/src/ui/hooks/useAtCompletion_agents.test.ts
index 054abb47ca..7a0b333384 100644
--- a/packages/cli/src/ui/hooks/useAtCompletion_agents.test.ts
+++ b/packages/cli/src/ui/hooks/useAtCompletion_agents.test.ts
@@ -83,7 +83,7 @@ describe('useAtCompletion with Agents', () => {
it('should include agent suggestions', async () => {
testRootDir = await createTmpDir({});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
);
@@ -101,7 +101,7 @@ describe('useAtCompletion with Agents', () => {
it('should filter agent suggestions', async () => {
testRootDir = await createTmpDir({});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForAtCompletion(true, 'Code', mockConfig, testRootDir),
);
diff --git a/packages/cli/src/ui/hooks/useBackgroundShellManager.test.tsx b/packages/cli/src/ui/hooks/useBackgroundShellManager.test.tsx
index 0cf5fd995f..c6a5e9ef4e 100644
--- a/packages/cli/src/ui/hooks/useBackgroundShellManager.test.tsx
+++ b/packages/cli/src/ui/hooks/useBackgroundShellManager.test.tsx
@@ -21,13 +21,13 @@ describe('useBackgroundShellManager', () => {
vi.clearAllMocks();
});
- const renderHook = (props: BackgroundShellManagerProps) => {
+ const renderHook = async (props: BackgroundShellManagerProps) => {
let hookResult: ReturnType;
function TestComponent({ p }: { p: BackgroundShellManagerProps }) {
hookResult = useBackgroundShellManager(p);
return null;
}
- const { rerender } = render();
+ const { rerender } = await render();
return {
result: {
get current() {
@@ -39,9 +39,9 @@ describe('useBackgroundShellManager', () => {
};
};
- it('should initialize with correct default values', () => {
+ it('should initialize with correct default values', async () => {
const backgroundShells = new Map();
- const { result } = renderHook({
+ const { result } = await renderHook({
backgroundShells,
backgroundShellCount: 0,
isBackgroundShellVisible: false,
@@ -56,9 +56,9 @@ describe('useBackgroundShellManager', () => {
expect(result.current.backgroundShellHeight).toBe(0);
});
- it('should auto-select the first background shell when added', () => {
+ it('should auto-select the first background shell when added', async () => {
const backgroundShells = new Map();
- const { result, rerender } = renderHook({
+ const { result, rerender } = await renderHook({
backgroundShells,
backgroundShellCount: 0,
isBackgroundShellVisible: false,
@@ -84,11 +84,11 @@ describe('useBackgroundShellManager', () => {
expect(result.current.activeBackgroundShellPid).toBe(123);
});
- it('should reset state when all shells are removed', () => {
+ it('should reset state when all shells are removed', async () => {
const backgroundShells = new Map([
[123, {} as BackgroundShell],
]);
- const { result, rerender } = renderHook({
+ const { result, rerender } = await renderHook({
backgroundShells,
backgroundShellCount: 1,
isBackgroundShellVisible: true,
@@ -117,11 +117,11 @@ describe('useBackgroundShellManager', () => {
expect(result.current.isBackgroundShellListOpen).toBe(false);
});
- it('should unfocus embedded shell when no shells are active', () => {
+ it('should unfocus embedded shell when no shells are active', async () => {
const backgroundShells = new Map([
[123, {} as BackgroundShell],
]);
- renderHook({
+ await renderHook({
backgroundShells,
backgroundShellCount: 1,
isBackgroundShellVisible: false, // Background shell not visible
@@ -134,11 +134,11 @@ describe('useBackgroundShellManager', () => {
expect(setEmbeddedShellFocused).toHaveBeenCalledWith(false);
});
- it('should calculate backgroundShellHeight correctly when visible', () => {
+ it('should calculate backgroundShellHeight correctly when visible', async () => {
const backgroundShells = new Map([
[123, {} as BackgroundShell],
]);
- const { result } = renderHook({
+ const { result } = await renderHook({
backgroundShells,
backgroundShellCount: 1,
isBackgroundShellVisible: true,
@@ -152,12 +152,12 @@ describe('useBackgroundShellManager', () => {
expect(result.current.backgroundShellHeight).toBe(30);
});
- it('should maintain current active shell if it still exists', () => {
+ it('should maintain current active shell if it still exists', async () => {
const backgroundShells = new Map([
[123, {} as BackgroundShell],
[456, {} as BackgroundShell],
]);
- const { result, rerender } = renderHook({
+ const { result, rerender } = await renderHook({
backgroundShells,
backgroundShellCount: 2,
isBackgroundShellVisible: true,
diff --git a/packages/cli/src/ui/hooks/useBanner.test.ts b/packages/cli/src/ui/hooks/useBanner.test.ts
index cb5712bec4..ad2c3ce0d5 100644
--- a/packages/cli/src/ui/hooks/useBanner.test.ts
+++ b/packages/cli/src/ui/hooks/useBanner.test.ts
@@ -61,15 +61,15 @@ describe('useBanner', () => {
mockedPersistentStateGet.mockReturnValue({});
});
- it('should return warning text and warning color if warningText is present', () => {
+ it('should return warning text and warning color if warningText is present', async () => {
const data = { defaultText: 'Standard', warningText: 'Critical Error' };
- const { result } = renderHook(() => useBanner(data));
+ const { result } = await renderHook(() => useBanner(data));
expect(result.current.bannerText).toBe('Critical Error');
});
- it('should hide banner if show count exceeds max limit (Legacy format)', () => {
+ it('should hide banner if show count exceeds max limit (Legacy format)', async () => {
mockedPersistentStateGet.mockReturnValue({
[crypto
.createHash('sha256')
@@ -77,12 +77,12 @@ describe('useBanner', () => {
.digest('hex')]: 5,
});
- const { result } = renderHook(() => useBanner(defaultBannerData));
+ const { result } = await renderHook(() => useBanner(defaultBannerData));
expect(result.current.bannerText).toBe('');
});
- it('should increment the persistent count when banner is shown', () => {
+ it('should increment the persistent count when banner is shown', async () => {
const data = { defaultText: 'Tracker', warningText: '' };
// Current count is 1
@@ -90,7 +90,7 @@ describe('useBanner', () => {
[crypto.createHash('sha256').update(data.defaultText).digest('hex')]: 1,
});
- renderHook(() => useBanner(data));
+ await renderHook(() => useBanner(data));
// Expect set to be called with incremented count
expect(mockedPersistentStateSet).toHaveBeenCalledWith(
@@ -101,19 +101,19 @@ describe('useBanner', () => {
);
});
- it('should NOT increment count if warning text is shown instead', () => {
+ it('should NOT increment count if warning text is shown instead', async () => {
const data = { defaultText: 'Standard', warningText: 'Warning' };
- renderHook(() => useBanner(data));
+ await renderHook(() => useBanner(data));
// Since warning text takes precedence, default banner logic (and increment) is skipped
expect(mockedPersistentStateSet).not.toHaveBeenCalled();
});
- it('should handle newline replacements', () => {
+ it('should handle newline replacements', async () => {
const data = { defaultText: 'Line1\\nLine2', warningText: '' };
- const { result } = renderHook(() => useBanner(data));
+ const { result } = await renderHook(() => useBanner(data));
expect(result.current.bannerText).toBe('Line1\nLine2');
});
diff --git a/packages/cli/src/ui/hooks/useBatchedScroll.test.ts b/packages/cli/src/ui/hooks/useBatchedScroll.test.ts
index 268c5b6bfa..1a3e935cb4 100644
--- a/packages/cli/src/ui/hooks/useBatchedScroll.test.ts
+++ b/packages/cli/src/ui/hooks/useBatchedScroll.test.ts
@@ -9,14 +9,14 @@ import { renderHook } from '../../test-utils/render.js';
import { useBatchedScroll } from './useBatchedScroll.js';
describe('useBatchedScroll', () => {
- it('returns initial scrollTop', () => {
- const { result } = renderHook(() => useBatchedScroll(10));
+ it('returns initial scrollTop', async () => {
+ const { result } = await renderHook(() => useBatchedScroll(10));
expect(result.current.getScrollTop()).toBe(10);
});
- it('returns updated scrollTop from props', () => {
+ it('returns updated scrollTop from props', async () => {
let currentScrollTop = 10;
- const { result, rerender } = renderHook(() =>
+ const { result, rerender } = await renderHook(() =>
useBatchedScroll(currentScrollTop),
);
@@ -28,24 +28,24 @@ describe('useBatchedScroll', () => {
expect(result.current.getScrollTop()).toBe(100);
});
- it('returns pending scrollTop when set', () => {
- const { result } = renderHook(() => useBatchedScroll(10));
+ it('returns pending scrollTop when set', async () => {
+ const { result } = await renderHook(() => useBatchedScroll(10));
result.current.setPendingScrollTop(50);
expect(result.current.getScrollTop()).toBe(50);
});
- it('overwrites pending scrollTop with subsequent sets before render', () => {
- const { result } = renderHook(() => useBatchedScroll(10));
+ it('overwrites pending scrollTop with subsequent sets before render', async () => {
+ const { result } = await renderHook(() => useBatchedScroll(10));
result.current.setPendingScrollTop(50);
result.current.setPendingScrollTop(75);
expect(result.current.getScrollTop()).toBe(75);
});
- it('resets pending scrollTop after rerender', () => {
+ it('resets pending scrollTop after rerender', async () => {
let currentScrollTop = 10;
- const { result, rerender } = renderHook(() =>
+ const { result, rerender } = await renderHook(() =>
useBatchedScroll(currentScrollTop),
);
@@ -60,8 +60,8 @@ describe('useBatchedScroll', () => {
expect(result.current.getScrollTop()).toBe(100);
});
- it('resets pending scrollTop after rerender even if prop is same', () => {
- const { result, rerender } = renderHook(() => useBatchedScroll(10));
+ it('resets pending scrollTop after rerender even if prop is same', async () => {
+ const { result, rerender } = await renderHook(() => useBatchedScroll(10));
result.current.setPendingScrollTop(50);
expect(result.current.getScrollTop()).toBe(50);
@@ -73,8 +73,8 @@ describe('useBatchedScroll', () => {
expect(result.current.getScrollTop()).toBe(10);
});
- it('maintains stable function references', () => {
- const { result, rerender } = renderHook(() => useBatchedScroll(10));
+ it('maintains stable function references', async () => {
+ const { result, rerender } = await renderHook(() => useBatchedScroll(10));
const initialGetScrollTop = result.current.getScrollTop;
const initialSetPendingScrollTop = result.current.setPendingScrollTop;
diff --git a/packages/cli/src/ui/hooks/useConsoleMessages.test.tsx b/packages/cli/src/ui/hooks/useConsoleMessages.test.tsx
index 8761ef7167..af78f73447 100644
--- a/packages/cli/src/ui/hooks/useConsoleMessages.test.tsx
+++ b/packages/cli/src/ui/hooks/useConsoleMessages.test.tsx
@@ -66,13 +66,13 @@ describe('useConsoleMessages', () => {
};
};
- const renderConsoleMessagesHook = () => {
+ const renderConsoleMessagesHook = async () => {
let hookResult: ReturnType;
function TestComponent() {
hookResult = useTestableConsoleMessages();
return null;
}
- const { unmount } = render();
+ const { unmount } = await render();
return {
result: {
get current() {
@@ -83,13 +83,13 @@ describe('useConsoleMessages', () => {
};
};
- it('should initialize with an empty array of console messages', () => {
- const { result } = renderConsoleMessagesHook();
+ it('should initialize with an empty array of console messages', async () => {
+ const { result } = await renderConsoleMessagesHook();
expect(result.current.consoleMessages).toEqual([]);
});
it('should add a new message when log is called', async () => {
- const { result } = renderConsoleMessagesHook();
+ const { result } = await renderConsoleMessagesHook();
act(() => {
result.current.log('Test message');
@@ -105,7 +105,7 @@ describe('useConsoleMessages', () => {
});
it('should batch and count identical consecutive messages', async () => {
- const { result } = renderConsoleMessagesHook();
+ const { result } = await renderConsoleMessagesHook();
act(() => {
result.current.log('Test message');
@@ -123,7 +123,7 @@ describe('useConsoleMessages', () => {
});
it('should not batch different messages', async () => {
- const { result } = renderConsoleMessagesHook();
+ const { result } = await renderConsoleMessagesHook();
act(() => {
result.current.log('First message');
@@ -141,7 +141,7 @@ describe('useConsoleMessages', () => {
});
it('should clear all messages when clearConsoleMessages is called', async () => {
- const { result } = renderConsoleMessagesHook();
+ const { result } = await renderConsoleMessagesHook();
act(() => {
result.current.log('A message');
@@ -160,8 +160,8 @@ describe('useConsoleMessages', () => {
expect(result.current.consoleMessages).toHaveLength(0);
});
- it('should clear the pending timeout when clearConsoleMessages is called', () => {
- const { result } = renderConsoleMessagesHook();
+ it('should clear the pending timeout when clearConsoleMessages is called', async () => {
+ const { result } = await renderConsoleMessagesHook();
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
act(() => {
@@ -176,8 +176,8 @@ describe('useConsoleMessages', () => {
// clearTimeoutSpy.mockRestore() is handled by afterEach restoreAllMocks
});
- it('should clean up the timeout on unmount', () => {
- const { result, unmount } = renderConsoleMessagesHook();
+ it('should clean up the timeout on unmount', async () => {
+ const { result, unmount } = await renderConsoleMessagesHook();
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
act(() => {
diff --git a/packages/cli/src/ui/hooks/useEditorSettings.test.tsx b/packages/cli/src/ui/hooks/useEditorSettings.test.tsx
index 68c2b93f22..0019027eb5 100644
--- a/packages/cli/src/ui/hooks/useEditorSettings.test.tsx
+++ b/packages/cli/src/ui/hooks/useEditorSettings.test.tsx
@@ -77,14 +77,14 @@ describe('useEditorSettings', () => {
vi.restoreAllMocks();
});
- it('should initialize with dialog closed', () => {
- render();
+ it('should initialize with dialog closed', async () => {
+ await render();
expect(result.isEditorDialogOpen).toBe(false);
});
- it('should open editor dialog when openEditorDialog is called', () => {
- render();
+ it('should open editor dialog when openEditorDialog is called', async () => {
+ await render();
act(() => {
result.openEditorDialog();
@@ -93,8 +93,8 @@ describe('useEditorSettings', () => {
expect(result.isEditorDialogOpen).toBe(true);
});
- it('should close editor dialog when exitEditorDialog is called', () => {
- render();
+ it('should close editor dialog when exitEditorDialog is called', async () => {
+ await render();
act(() => {
result.openEditorDialog();
result.exitEditorDialog();
@@ -102,8 +102,8 @@ describe('useEditorSettings', () => {
expect(result.isEditorDialogOpen).toBe(false);
});
- it('should handle editor selection successfully', () => {
- render();
+ it('should handle editor selection successfully', async () => {
+ await render();
const editorType: EditorType = 'vscode';
const scope = SettingScope.User;
@@ -131,8 +131,8 @@ describe('useEditorSettings', () => {
expect(result.isEditorDialogOpen).toBe(false);
});
- it('should handle clearing editor preference (undefined editor)', () => {
- render();
+ it('should handle clearing editor preference (undefined editor)', async () => {
+ await render();
const scope = SettingScope.Workspace;
@@ -159,8 +159,8 @@ describe('useEditorSettings', () => {
expect(result.isEditorDialogOpen).toBe(false);
});
- it('should handle different editor types', () => {
- render();
+ it('should handle different editor types', async () => {
+ await render();
const editorTypes: EditorType[] = ['cursor', 'windsurf', 'vim'];
const displayNames: Record = {
@@ -191,8 +191,8 @@ describe('useEditorSettings', () => {
});
});
- it('should handle different setting scopes', () => {
- render();
+ it('should handle different setting scopes', async () => {
+ await render();
const editorType: EditorType = 'vscode';
const scopes: LoadableSettingScope[] = [
@@ -221,8 +221,8 @@ describe('useEditorSettings', () => {
});
});
- it('should not set preference for unavailable editors', () => {
- render();
+ it('should not set preference for unavailable editors', async () => {
+ await render();
mockHasValidEditorCommand.mockReturnValue(false);
@@ -239,8 +239,8 @@ describe('useEditorSettings', () => {
expect(result.isEditorDialogOpen).toBe(true);
});
- it('should not set preference for editors not allowed in sandbox', () => {
- render();
+ it('should not set preference for editors not allowed in sandbox', async () => {
+ await render();
mockAllowEditorTypeInSandbox.mockReturnValue(false);
@@ -257,8 +257,8 @@ describe('useEditorSettings', () => {
expect(result.isEditorDialogOpen).toBe(true);
});
- it('should handle errors during editor selection', () => {
- render();
+ it('should handle errors during editor selection', async () => {
+ await render();
const errorMessage = 'Failed to save settings';
(
diff --git a/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx b/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx
index 95212b023c..5c37dbd680 100644
--- a/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx
+++ b/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx
@@ -127,7 +127,7 @@ describe('useExtensionUpdates', () => {
return null;
}
- render();
+ await render();
await waitFor(() => {
expect(addItem).toHaveBeenCalledWith(
@@ -177,7 +177,7 @@ describe('useExtensionUpdates', () => {
return null;
}
- render();
+ await render();
await waitFor(
() => {
@@ -255,7 +255,7 @@ describe('useExtensionUpdates', () => {
return null;
}
- render();
+ await render();
await waitFor(
() => {
@@ -338,7 +338,7 @@ describe('useExtensionUpdates', () => {
return null;
}
- render();
+ await render();
await waitFor(() => {
expect(addItem).toHaveBeenCalledTimes(1);
diff --git a/packages/cli/src/ui/hooks/useFlickerDetector.test.ts b/packages/cli/src/ui/hooks/useFlickerDetector.test.ts
index 8328a8c9d4..ab976fe15e 100644
--- a/packages/cli/src/ui/hooks/useFlickerDetector.test.ts
+++ b/packages/cli/src/ui/hooks/useFlickerDetector.test.ts
@@ -62,51 +62,53 @@ describe('useFlickerDetector', () => {
vi.clearAllMocks();
});
- it('should not record a flicker when height is less than terminal height', () => {
+ it('should not record a flicker when height is less than terminal height', async () => {
mockMeasureElement.mockReturnValue({ width: 80, height: 20 });
- renderHook(() => useFlickerDetector(mockRef, 25));
+ await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
- it('should not record a flicker when height is equal to terminal height', () => {
+ it('should not record a flicker when height is equal to terminal height', async () => {
mockMeasureElement.mockReturnValue({ width: 80, height: 25 });
- renderHook(() => useFlickerDetector(mockRef, 25));
+ await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
- it('should record a flicker when height is greater than terminal height and height is constrained', () => {
+ it('should record a flicker when height is greater than terminal height and height is constrained', async () => {
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
- renderHook(() => useFlickerDetector(mockRef, 25));
+ await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).toHaveBeenCalledTimes(1);
expect(mockRecordFlickerFrame).toHaveBeenCalledWith(mockConfig);
expect(mockAppEventsEmit).toHaveBeenCalledTimes(1);
expect(mockAppEventsEmit).toHaveBeenCalledWith(AppEvent.Flicker);
});
- it('should NOT record a flicker when height is greater than terminal height but height is NOT constrained', () => {
+ it('should NOT record a flicker when height is greater than terminal height but height is NOT constrained', async () => {
// Override default UI state for this test
mockUseUIState.mockReturnValue({ constrainHeight: false });
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
- renderHook(() => useFlickerDetector(mockRef, 25));
+ await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
- it('should not check for flicker if the ref is not set', () => {
+ it('should not check for flicker if the ref is not set', async () => {
mockRef.current = null;
mockMeasureElement.mockReturnValue({ width: 80, height: 30 });
- renderHook(() => useFlickerDetector(mockRef, 25));
+ await renderHook(() => useFlickerDetector(mockRef, 25));
expect(mockMeasureElement).not.toHaveBeenCalled();
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
expect(mockAppEventsEmit).not.toHaveBeenCalled();
});
- it('should re-evaluate on re-render', () => {
+ it('should re-evaluate on re-render', async () => {
// Start with a valid height
mockMeasureElement.mockReturnValue({ width: 80, height: 20 });
- const { rerender } = renderHook(() => useFlickerDetector(mockRef, 25));
+ const { rerender } = await renderHook(() =>
+ useFlickerDetector(mockRef, 25),
+ );
expect(mockRecordFlickerFrame).not.toHaveBeenCalled();
// Now, simulate a re-render where the height is too great
diff --git a/packages/cli/src/ui/hooks/useFolderTrust.test.ts b/packages/cli/src/ui/hooks/useFolderTrust.test.ts
index 4017397220..04c5b64dd2 100644
--- a/packages/cli/src/ui/hooks/useFolderTrust.test.ts
+++ b/packages/cli/src/ui/hooks/useFolderTrust.test.ts
@@ -119,18 +119,18 @@ describe('useFolderTrust', () => {
});
});
- it('should not open dialog when folder is already trusted', () => {
+ it('should not open dialog when folder is already trusted', async () => {
isWorkspaceTrustedSpy.mockReturnValue({ isTrusted: true, source: 'file' });
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
expect(onTrustChange).toHaveBeenCalledWith(true);
});
- it('should not open dialog when folder is already untrusted', () => {
+ it('should not open dialog when folder is already untrusted', async () => {
isWorkspaceTrustedSpy.mockReturnValue({ isTrusted: false, source: 'file' });
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
expect(result.current.isFolderTrustDialogOpen).toBe(false);
@@ -142,7 +142,7 @@ describe('useFolderTrust', () => {
isTrusted: undefined,
source: undefined,
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
await waitFor(() => {
@@ -151,9 +151,11 @@ describe('useFolderTrust', () => {
expect(onTrustChange).toHaveBeenCalledWith(undefined);
});
- it('should send a message if the folder is untrusted', () => {
+ it('should send a message if the folder is untrusted', async () => {
isWorkspaceTrustedSpy.mockReturnValue({ isTrusted: false, source: 'file' });
- renderHook(() => useFolderTrust(mockSettings, onTrustChange, addItem));
+ await renderHook(() =>
+ useFolderTrust(mockSettings, onTrustChange, addItem),
+ );
expect(addItem).toHaveBeenCalledWith(
{
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
@@ -163,9 +165,11 @@ describe('useFolderTrust', () => {
);
});
- it('should not send a message if the folder is trusted', () => {
+ it('should not send a message if the folder is trusted', async () => {
isWorkspaceTrustedSpy.mockReturnValue({ isTrusted: true, source: 'file' });
- renderHook(() => useFolderTrust(mockSettings, onTrustChange, addItem));
+ await renderHook(() =>
+ useFolderTrust(mockSettings, onTrustChange, addItem),
+ );
expect(addItem).not.toHaveBeenCalled();
});
@@ -182,7 +186,7 @@ describe('useFolderTrust', () => {
});
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -212,7 +216,7 @@ describe('useFolderTrust', () => {
isTrusted: undefined,
source: undefined,
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -238,7 +242,7 @@ describe('useFolderTrust', () => {
isTrusted: undefined,
source: undefined,
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -264,7 +268,7 @@ describe('useFolderTrust', () => {
isTrusted: undefined,
source: undefined,
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -292,7 +296,7 @@ describe('useFolderTrust', () => {
});
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -317,7 +321,7 @@ describe('useFolderTrust', () => {
isTrusted: true,
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -342,7 +346,7 @@ describe('useFolderTrust', () => {
throw new Error('test error');
});
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
@@ -362,14 +366,14 @@ describe('useFolderTrust', () => {
});
describe('headless mode', () => {
- it('should force trust and hide dialog in headless mode', () => {
+ it('should force trust and hide dialog in headless mode', async () => {
vi.mocked(isHeadlessMode).mockReturnValue(true);
isWorkspaceTrustedSpy.mockReturnValue({
isTrusted: false,
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useFolderTrust(mockSettings, onTrustChange, addItem),
);
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
index 6ca6825d67..3ff11292e3 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
+++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
@@ -3249,6 +3249,10 @@ describe('useGeminiStream', () => {
),
);
+ // Reset start time after hook render, because renderHook (async)
+ // advances fake timers by 50ms during its internal waitUntilReady() check.
+ vi.setSystemTime(startTime);
+
// Submit query
await act(async () => {
await result.current.submitQuery('Test query');
diff --git a/packages/cli/src/ui/hooks/useGitBranchName.test.tsx b/packages/cli/src/ui/hooks/useGitBranchName.test.tsx
index f0db013309..5a55b57607 100644
--- a/packages/cli/src/ui/hooks/useGitBranchName.test.tsx
+++ b/packages/cli/src/ui/hooks/useGitBranchName.test.tsx
@@ -4,15 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import {
- afterEach,
- beforeEach,
- describe,
- expect,
- it,
- vi,
- type MockedFunction,
-} from 'vitest';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { act } from 'react';
import { render } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
@@ -51,99 +43,96 @@ const CWD = '/test/project';
const GIT_LOGS_HEAD_PATH = path.join(CWD, '.git', 'logs', 'HEAD');
describe('useGitBranchName', () => {
+ let deferredSpawn: {
+ resolve: (val: { stdout: string; stderr: string }) => void;
+ reject: (err: Error) => void;
+ } | null = null;
+
beforeEach(() => {
vol.reset(); // Reset in-memory filesystem
vol.fromJSON({
[GIT_LOGS_HEAD_PATH]: 'ref: refs/heads/main',
});
+
+ deferredSpawn = null;
+ vi.mocked(mockSpawnAsync).mockImplementation(
+ () =>
+ new Promise((resolve, reject) => {
+ deferredSpawn = { resolve, reject };
+ }),
+ );
});
afterEach(() => {
vi.restoreAllMocks();
});
- const renderGitBranchNameHook = (cwd: string) => {
+ const renderGitBranchNameHook = async (cwd: string) => {
let hookResult: ReturnType;
function TestComponent() {
hookResult = useGitBranchName(cwd);
return null;
}
- const { rerender, unmount } = render();
+ const result = await render();
return {
result: {
get current() {
return hookResult;
},
},
- rerender: () => rerender(),
- unmount,
+ rerender: () => result.rerender(),
+ unmount: result.unmount,
};
};
it('should return branch name', async () => {
- (mockSpawnAsync as MockedFunction).mockResolvedValue(
- {
- stdout: 'main\n',
- } as { stdout: string; stderr: string },
- );
- const { result, rerender } = renderGitBranchNameHook(CWD);
+ const { result } = await renderGitBranchNameHook(CWD);
+
+ expect(result.current).toBeUndefined();
await act(async () => {
- rerender(); // Rerender to get the updated state
+ deferredSpawn?.resolve({ stdout: 'main\n', stderr: '' });
});
expect(result.current).toBe('main');
});
it('should return undefined if git command fails', async () => {
- (mockSpawnAsync as MockedFunction).mockRejectedValue(
- new Error('Git error'),
- );
-
- const { result, rerender } = renderGitBranchNameHook(CWD);
- expect(result.current).toBeUndefined();
+ const { result } = await renderGitBranchNameHook(CWD);
await act(async () => {
- rerender();
+ deferredSpawn?.reject(new Error('Git error'));
});
+
expect(result.current).toBeUndefined();
});
it('should return short commit hash if branch is HEAD (detached state)', async () => {
- (
- mockSpawnAsync as MockedFunction
- ).mockImplementation(async (command: string, args: string[]) => {
- if (args.includes('--abbrev-ref')) {
- return { stdout: 'HEAD\n' } as { stdout: string; stderr: string };
- } else if (args.includes('--short')) {
- return { stdout: 'a1b2c3d\n' } as { stdout: string; stderr: string };
- }
- return { stdout: '' } as { stdout: string; stderr: string };
+ const { result } = await renderGitBranchNameHook(CWD);
+
+ await act(async () => {
+ deferredSpawn?.resolve({ stdout: 'HEAD\n', stderr: '' });
});
- const { result, rerender } = renderGitBranchNameHook(CWD);
+ // It should now call spawnAsync again for the short hash
await act(async () => {
- rerender();
+ deferredSpawn?.resolve({ stdout: 'a1b2c3d\n', stderr: '' });
});
+
expect(result.current).toBe('a1b2c3d');
});
it('should return undefined if branch is HEAD and getting commit hash fails', async () => {
- (
- mockSpawnAsync as MockedFunction
- ).mockImplementation(async (command: string, args: string[]) => {
- if (args.includes('--abbrev-ref')) {
- return { stdout: 'HEAD\n' } as { stdout: string; stderr: string };
- } else if (args.includes('--short')) {
- throw new Error('Git error');
- }
- return { stdout: '' } as { stdout: string; stderr: string };
+ const { result } = await renderGitBranchNameHook(CWD);
+
+ await act(async () => {
+ deferredSpawn?.resolve({ stdout: 'HEAD\n', stderr: '' });
});
- const { result, rerender } = renderGitBranchNameHook(CWD);
await act(async () => {
- rerender();
+ deferredSpawn?.reject(new Error('Git error'));
});
+
expect(result.current).toBeUndefined();
});
@@ -151,21 +140,12 @@ describe('useGitBranchName', () => {
vi.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
const watchSpy = vi.spyOn(fs, 'watch');
- (mockSpawnAsync as MockedFunction)
- .mockResolvedValueOnce({ stdout: 'main\n' } as {
- stdout: string;
- stderr: string;
- })
- .mockResolvedValue({ stdout: 'develop\n' } as {
- stdout: string;
- stderr: string;
- });
-
- const { result, rerender } = renderGitBranchNameHook(CWD);
+ const { result } = await renderGitBranchNameHook(CWD);
await act(async () => {
- rerender();
+ deferredSpawn?.resolve({ stdout: 'main\n', stderr: '' });
});
+
expect(result.current).toBe('main');
// Wait for watcher to be set up
@@ -176,40 +156,29 @@ describe('useGitBranchName', () => {
// Simulate file change event
await act(async () => {
fs.writeFileSync(GIT_LOGS_HEAD_PATH, 'ref: refs/heads/develop'); // Trigger watcher
- rerender();
});
- await waitFor(() => {
- expect(result.current).toBe('develop');
+ // Resolving the new branch name fetch
+ await act(async () => {
+ deferredSpawn?.resolve({ stdout: 'develop\n', stderr: '' });
});
+
+ expect(result.current).toBe('develop');
});
it('should handle watcher setup error silently', async () => {
// Remove .git/logs/HEAD to cause an error in fs.watch setup
vol.unlinkSync(GIT_LOGS_HEAD_PATH);
- (mockSpawnAsync as MockedFunction).mockResolvedValue(
- {
- stdout: 'main\n',
- } as { stdout: string; stderr: string },
- );
-
- const { result, rerender } = renderGitBranchNameHook(CWD);
+ const { result } = await renderGitBranchNameHook(CWD);
await act(async () => {
- rerender();
+ deferredSpawn?.resolve({ stdout: 'main\n', stderr: '' });
});
- expect(result.current).toBe('main'); // Branch name should still be fetched initially
-
- (
- mockSpawnAsync as MockedFunction
- ).mockResolvedValueOnce({
- stdout: 'develop\n',
- } as { stdout: string; stderr: string });
+ expect(result.current).toBe('main');
// This write would trigger the watcher if it was set up
- // but since it failed, the branch name should not update
// We need to create the file again for writeFileSync to not throw
vol.fromJSON({
[GIT_LOGS_HEAD_PATH]: 'ref: refs/heads/develop',
@@ -217,10 +186,10 @@ describe('useGitBranchName', () => {
await act(async () => {
fs.writeFileSync(GIT_LOGS_HEAD_PATH, 'ref: refs/heads/develop');
- rerender();
});
- // Branch name should not change because watcher setup failed
+ // spawnAsync should NOT have been called again
+ expect(vi.mocked(mockSpawnAsync)).toHaveBeenCalledTimes(1);
expect(result.current).toBe('main');
});
@@ -231,16 +200,10 @@ describe('useGitBranchName', () => {
close: closeMock,
} as unknown as ReturnType);
- (mockSpawnAsync as MockedFunction).mockResolvedValue(
- {
- stdout: 'main\n',
- } as { stdout: string; stderr: string },
- );
-
- const { unmount, rerender } = renderGitBranchNameHook(CWD);
+ const { unmount } = await renderGitBranchNameHook(CWD);
await act(async () => {
- rerender();
+ deferredSpawn?.resolve({ stdout: 'main\n', stderr: '' });
});
// Wait for watcher to be set up BEFORE unmounting
diff --git a/packages/cli/src/ui/hooks/useHistoryManager.test.ts b/packages/cli/src/ui/hooks/useHistoryManager.test.ts
index 696f9d60c0..0c304e3823 100644
--- a/packages/cli/src/ui/hooks/useHistoryManager.test.ts
+++ b/packages/cli/src/ui/hooks/useHistoryManager.test.ts
@@ -11,13 +11,13 @@ import { useHistory } from './useHistoryManager.js';
import type { HistoryItem } from '../types.js';
describe('useHistoryManager', () => {
- it('should initialize with an empty history', () => {
- const { result } = renderHook(() => useHistory());
+ it('should initialize with an empty history', async () => {
+ const { result } = await renderHook(() => useHistory());
expect(result.current.history).toEqual([]);
});
- it('should add an item to history with a unique ID', () => {
- const { result } = renderHook(() => useHistory());
+ it('should add an item to history with a unique ID', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData: Omit = {
type: 'user', // Replaced HistoryItemType.User
@@ -39,8 +39,8 @@ describe('useHistoryManager', () => {
expect(result.current.history[0].id).toBeGreaterThanOrEqual(timestamp);
});
- it('should generate unique IDs for items added with the same base timestamp', () => {
- const { result } = renderHook(() => useHistory());
+ it('should generate unique IDs for items added with the same base timestamp', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit = {
type: 'user', // Replaced HistoryItemType.User
@@ -67,8 +67,8 @@ describe('useHistoryManager', () => {
expect(id2).toBeGreaterThan(id1);
});
- it('should update an existing history item', () => {
- const { result } = renderHook(() => useHistory());
+ it('should update an existing history item', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const initialItem: Omit = {
type: 'gemini', // Replaced HistoryItemType.Gemini
@@ -93,8 +93,8 @@ describe('useHistoryManager', () => {
});
});
- it('should not change history if updateHistoryItem is called with a nonexistent ID', () => {
- const { result } = renderHook(() => useHistory());
+ it('should not change history if updateHistoryItem is called with a nonexistent ID', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData: Omit = {
type: 'user', // Replaced HistoryItemType.User
@@ -114,8 +114,8 @@ describe('useHistoryManager', () => {
expect(result.current.history).toEqual(originalHistory);
});
- it('should clear the history', () => {
- const { result } = renderHook(() => useHistory());
+ it('should clear the history', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit = {
type: 'user', // Replaced HistoryItemType.User
@@ -140,8 +140,8 @@ describe('useHistoryManager', () => {
expect(result.current.history).toEqual([]);
});
- it('should not add consecutive duplicate user messages', () => {
- const { result } = renderHook(() => useHistory());
+ it('should not add consecutive duplicate user messages', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit = {
type: 'user', // Replaced HistoryItemType.User
@@ -173,8 +173,8 @@ describe('useHistoryManager', () => {
expect(result.current.history[2].text).toBe('Another user message');
});
- it('should add duplicate user messages if they are not consecutive', () => {
- const { result } = renderHook(() => useHistory());
+ it('should add duplicate user messages if they are not consecutive', async () => {
+ const { result } = await renderHook(() => useHistory());
const timestamp = Date.now();
const itemData1: Omit = {
type: 'user', // Replaced HistoryItemType.User
@@ -201,8 +201,8 @@ describe('useHistoryManager', () => {
expect(result.current.history[2].text).toBe('Message 1');
});
- it('should use Date.now() as default baseTimestamp if not provided', () => {
- const { result } = renderHook(() => useHistory());
+ it('should use Date.now() as default baseTimestamp if not provided', async () => {
+ const { result } = await renderHook(() => useHistory());
const before = Date.now();
const itemData: Omit = {
type: 'user',
@@ -221,7 +221,7 @@ describe('useHistoryManager', () => {
});
describe('initialItems with auth information', () => {
- it('should initialize with auth information', () => {
+ it('should initialize with auth information', async () => {
const email = 'user@example.com';
const tier = 'Pro';
const authMessage = `Authenticated as: ${email} (Plan: ${tier})`;
@@ -232,13 +232,13 @@ describe('useHistoryManager', () => {
text: authMessage,
},
];
- const { result } = renderHook(() => useHistory({ initialItems }));
+ const { result } = await renderHook(() => useHistory({ initialItems }));
expect(result.current.history).toHaveLength(1);
expect(result.current.history[0].text).toBe(authMessage);
});
- it('should add items with auth information via addItem', () => {
- const { result } = renderHook(() => useHistory());
+ it('should add items with auth information via addItem', async () => {
+ const { result } = await renderHook(() => useHistory());
const email = 'user@example.com';
const tier = 'Pro';
const authMessage = `Authenticated as: ${email} (Plan: ${tier})`;
diff --git a/packages/cli/src/ui/hooks/useHookDisplayState.test.ts b/packages/cli/src/ui/hooks/useHookDisplayState.test.ts
index 3f087771c8..8ab68cadae 100644
--- a/packages/cli/src/ui/hooks/useHookDisplayState.test.ts
+++ b/packages/cli/src/ui/hooks/useHookDisplayState.test.ts
@@ -28,13 +28,13 @@ describe('useHookDisplayState', () => {
coreEvents.removeAllListeners(CoreEvent.HookEnd);
});
- it('should initialize with empty hooks', () => {
- const { result } = renderHook(() => useHookDisplayState());
+ it('should initialize with empty hooks', async () => {
+ const { result } = await renderHook(() => useHookDisplayState());
expect(result.current).toEqual([]);
});
- it('should add a hook when HookStart event is emitted', () => {
- const { result } = renderHook(() => useHookDisplayState());
+ it('should add a hook when HookStart event is emitted', async () => {
+ const { result } = await renderHook(() => useHookDisplayState());
const payload: HookStartPayload = {
hookName: 'test-hook',
@@ -54,8 +54,8 @@ describe('useHookDisplayState', () => {
});
});
- it('should remove a hook immediately if duration > minimum duration', () => {
- const { result } = renderHook(() => useHookDisplayState());
+ it('should remove a hook immediately if duration > minimum duration', async () => {
+ const { result } = await renderHook(() => useHookDisplayState());
const startPayload: HookStartPayload = {
hookName: 'test-hook',
@@ -84,8 +84,8 @@ describe('useHookDisplayState', () => {
expect(result.current).toHaveLength(0);
});
- it('should delay removal if duration < minimum duration', () => {
- const { result } = renderHook(() => useHookDisplayState());
+ it('should delay removal if duration < minimum duration', async () => {
+ const { result } = await renderHook(() => useHookDisplayState());
const startPayload: HookStartPayload = {
hookName: 'test-hook',
@@ -122,8 +122,8 @@ describe('useHookDisplayState', () => {
expect(result.current).toHaveLength(0);
});
- it('should handle multiple hooks correctly', () => {
- const { result } = renderHook(() => useHookDisplayState());
+ it('should handle multiple hooks correctly', async () => {
+ const { result } = await renderHook(() => useHookDisplayState());
act(() => {
coreEvents.emitHookStart({ hookName: 'h1', eventName: 'e1' });
@@ -188,8 +188,8 @@ describe('useHookDisplayState', () => {
expect(result.current).toHaveLength(0);
});
- it('should handle interleaved hooks with same name and event', () => {
- const { result } = renderHook(() => useHookDisplayState());
+ it('should handle interleaved hooks with same name and event', async () => {
+ const { result } = await renderHook(() => useHookDisplayState());
const hook = { hookName: 'same-hook', eventName: 'same-event' };
// Start Hook 1 at t=0
diff --git a/packages/cli/src/ui/hooks/useIdeTrustListener.test.tsx b/packages/cli/src/ui/hooks/useIdeTrustListener.test.tsx
index 2da958b71a..7661cb11c5 100644
--- a/packages/cli/src/ui/hooks/useIdeTrustListener.test.tsx
+++ b/packages/cli/src/ui/hooks/useIdeTrustListener.test.tsx
@@ -52,9 +52,27 @@ describe('useIdeTrustListener', () => {
let trustChangeCallback: (isTrusted: boolean) => void;
let statusChangeCallback: (state: IDEConnectionState) => void;
+ let deferredIdeClient: { resolve: (c: IdeClient) => void };
+
beforeEach(async () => {
vi.clearAllMocks();
- mockIdeClient = await IdeClient.getInstance();
+
+ vi.mocked(IdeClient.getInstance).mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredIdeClient = { resolve };
+ }),
+ );
+
+ mockIdeClient = {
+ addTrustChangeListener: vi.fn(),
+ removeTrustChangeListener: vi.fn(),
+ addStatusChangeListener: vi.fn(),
+ removeStatusChangeListener: vi.fn(),
+ getConnectionStatus: vi.fn(() => ({
+ status: IDEConnectionStatus.Disconnected,
+ })),
+ } as unknown as IdeClient;
mockSettings = {
merged: {
@@ -84,11 +102,10 @@ describe('useIdeTrustListener', () => {
hookResult = useIdeTrustListener();
return null;
}
- const { rerender, unmount } = render();
+ const result = await render();
- // Flush any pending async state updates from the hook's initialization
await act(async () => {
- await new Promise((resolve) => setTimeout(resolve, 0));
+ deferredIdeClient.resolve(mockIdeClient);
});
return {
@@ -98,10 +115,10 @@ describe('useIdeTrustListener', () => {
},
},
rerender: async () => {
- rerender();
+ result.rerender();
},
unmount: async () => {
- unmount();
+ result.unmount();
},
};
};
diff --git a/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx b/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx
index 3f9c656048..65a6012105 100644
--- a/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx
+++ b/packages/cli/src/ui/hooks/useIncludeDirsTrust.test.tsx
@@ -95,8 +95,8 @@ describe('useIncludeDirsTrust', () => {
mockSetCustomDialog = vi.fn();
});
- const renderTestHook = (isTrustedFolder: boolean | undefined) => {
- renderHook(() =>
+ const renderTestHook = async (isTrustedFolder: boolean | undefined) => {
+ await renderHook(() =>
useIncludeDirsTrust(
mockConfig,
isTrustedFolder,
@@ -106,16 +106,16 @@ describe('useIncludeDirsTrust', () => {
);
};
- it('should do nothing if isTrustedFolder is undefined', () => {
+ it('should do nothing if isTrustedFolder is undefined', async () => {
vi.mocked(mockConfig.getPendingIncludeDirectories).mockReturnValue([
'/foo',
]);
- renderTestHook(undefined);
+ await renderTestHook(undefined);
expect(mockConfig.clearPendingIncludeDirectories).not.toHaveBeenCalled();
});
- it('should do nothing if there are no pending directories', () => {
- renderTestHook(true);
+ it('should do nothing if there are no pending directories', async () => {
+ await renderTestHook(true);
expect(mockConfig.clearPendingIncludeDirectories).not.toHaveBeenCalled();
});
@@ -140,7 +140,7 @@ describe('useIncludeDirsTrust', () => {
failed: [{ path: '/dir2', error: new Error('Test error') }],
});
- renderTestHook(isTrusted);
+ await renderTestHook(isTrusted);
await waitFor(() => {
expect(mockWorkspaceContext.addDirectories).toHaveBeenCalledWith([
@@ -195,7 +195,7 @@ describe('useIncludeDirsTrust', () => {
failed: [],
});
- renderTestHook(true);
+ await renderTestHook(true);
// Opens dialog for undefined trust dir
expect(mockSetCustomDialog).toHaveBeenCalledTimes(1);
@@ -222,7 +222,7 @@ describe('useIncludeDirsTrust', () => {
failed: [],
});
- renderTestHook(true);
+ await renderTestHook(true);
await waitFor(() => {
expect(mockWorkspaceContext.addDirectories).toHaveBeenCalledWith(
diff --git a/packages/cli/src/ui/hooks/useInlineEditBuffer.test.ts b/packages/cli/src/ui/hooks/useInlineEditBuffer.test.ts
index b22ee62c81..b3a87f7c9a 100644
--- a/packages/cli/src/ui/hooks/useInlineEditBuffer.test.ts
+++ b/packages/cli/src/ui/hooks/useInlineEditBuffer.test.ts
@@ -17,8 +17,8 @@ describe('useEditBuffer', () => {
mockOnCommit = vi.fn();
});
- it('should initialize with empty state', () => {
- const { result } = renderHook(() =>
+ it('should initialize with empty state', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
expect(result.current.editState.editingKey).toBeNull();
@@ -26,8 +26,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.cursorPos).toBe(0);
});
- it('should start editing correctly', () => {
- const { result } = renderHook(() =>
+ it('should start editing correctly', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('my-key', 'initial'));
@@ -37,8 +37,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.cursorPos).toBe(7); // End of string
});
- it('should commit edit and reset state', () => {
- const { result } = renderHook(() =>
+ it('should commit edit and reset state', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
@@ -50,8 +50,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.buffer).toBe('');
});
- it('should move cursor left and right', () => {
- const { result } = renderHook(() =>
+ it('should move cursor left and right', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('key', 'ab')); // cursor at 2
@@ -70,8 +70,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.cursorPos).toBe(1);
});
- it('should handle home and end', () => {
- const { result } = renderHook(() =>
+ it('should handle home and end', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('key', 'testing')); // cursor at 7
@@ -83,8 +83,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.cursorPos).toBe(7);
});
- it('should delete characters to the left (backspace)', () => {
- const { result } = renderHook(() =>
+ it('should delete characters to the left (backspace)', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('key', 'abc')); // cursor at 3
@@ -99,8 +99,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.buffer).toBe('ab');
});
- it('should delete characters to the right (delete tab)', () => {
- const { result } = renderHook(() =>
+ it('should delete characters to the right (delete tab)', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('key', 'abc'));
@@ -111,8 +111,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.cursorPos).toBe(0);
});
- it('should insert valid characters into string', () => {
- const { result } = renderHook(() =>
+ it('should insert valid characters into string', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('key', 'ab'));
@@ -129,8 +129,8 @@ describe('useEditBuffer', () => {
expect(result.current.editState.cursorPos).toBe(2);
});
- it('should validate number character insertions', () => {
- const { result } = renderHook(() =>
+ it('should validate number character insertions', async () => {
+ const { result } = await renderHook(() =>
useInlineEditBuffer({ onCommit: mockOnCommit }),
);
act(() => result.current.startEditing('key', '12'));
diff --git a/packages/cli/src/ui/hooks/useInputHistory.test.ts b/packages/cli/src/ui/hooks/useInputHistory.test.ts
index e9a985484a..ef52a073dd 100644
--- a/packages/cli/src/ui/hooks/useInputHistory.test.ts
+++ b/packages/cli/src/ui/hooks/useInputHistory.test.ts
@@ -18,8 +18,8 @@ describe('useInputHistory', () => {
const userMessages = ['message 1', 'message 2', 'message 3'];
- it('should initialize with historyIndex -1 and empty originalQueryBeforeNav', () => {
- const { result } = renderHook(() =>
+ it('should initialize with historyIndex -1 and empty originalQueryBeforeNav', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages: [],
onSubmit: mockOnSubmit,
@@ -39,8 +39,8 @@ describe('useInputHistory', () => {
});
describe('handleSubmit', () => {
- it('should call onSubmit with trimmed value and reset history', () => {
- const { result } = renderHook(() =>
+ it('should call onSubmit with trimmed value and reset history', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -63,8 +63,8 @@ describe('useInputHistory', () => {
expect(mockOnChange).not.toHaveBeenCalled();
});
- it('should not call onSubmit if value is empty after trimming', () => {
- const { result } = renderHook(() =>
+ it('should not call onSubmit if value is empty after trimming', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -84,8 +84,8 @@ describe('useInputHistory', () => {
});
describe('navigateUp', () => {
- it('should not navigate if isActive is false', () => {
- const { result } = renderHook(() =>
+ it('should not navigate if isActive is false', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -102,8 +102,8 @@ describe('useInputHistory', () => {
expect(mockOnChange).not.toHaveBeenCalled();
});
- it('should not navigate if userMessages is empty', () => {
- const { result } = renderHook(() =>
+ it('should not navigate if userMessages is empty', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages: [],
onSubmit: mockOnSubmit,
@@ -120,9 +120,9 @@ describe('useInputHistory', () => {
expect(mockOnChange).not.toHaveBeenCalled();
});
- it('should call onChange with the last message when navigating up from initial state', () => {
+ it('should call onChange with the last message when navigating up from initial state', async () => {
const currentQuery = 'current query';
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -140,10 +140,10 @@ describe('useInputHistory', () => {
expect(mockOnChange).toHaveBeenCalledWith(userMessages[2], 'start'); // Last message
});
- it('should store currentQuery and currentCursorOffset as original state on first navigateUp', () => {
+ it('should store currentQuery and currentCursorOffset as original state on first navigateUp', async () => {
const currentQuery = 'original user input';
const currentCursorOffset = 5;
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -169,8 +169,8 @@ describe('useInputHistory', () => {
);
});
- it('should navigate through history messages on subsequent navigateUp calls', () => {
- const { result } = renderHook(() =>
+ it('should navigate through history messages on subsequent navigateUp calls', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -199,7 +199,7 @@ describe('useInputHistory', () => {
});
describe('navigateDown', () => {
- it('should not navigate if isActive is false', () => {
+ it('should not navigate if isActive is false', async () => {
const initialProps = {
userMessages,
onSubmit: mockOnSubmit,
@@ -208,7 +208,7 @@ describe('useInputHistory', () => {
currentCursorOffset: 0,
onChange: mockOnChange,
};
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
(props) => useInputHistory(props),
{
initialProps,
@@ -231,8 +231,8 @@ describe('useInputHistory', () => {
expect(mockOnChange).not.toHaveBeenCalled();
});
- it('should not navigate if historyIndex is -1 (not in history navigation)', () => {
- const { result } = renderHook(() =>
+ it('should not navigate if historyIndex is -1 (not in history navigation)', async () => {
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -249,10 +249,10 @@ describe('useInputHistory', () => {
expect(mockOnChange).not.toHaveBeenCalled();
});
- it('should restore cursor offset only when in middle of compose prompt', () => {
+ it('should restore cursor offset only when in middle of compose prompt', async () => {
const originalQuery = 'my original input';
const originalCursorOffset = 5; // Middle
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useInputHistory({
userMessages,
onSubmit: mockOnSubmit,
@@ -278,9 +278,9 @@ describe('useInputHistory', () => {
);
});
- it('should NOT restore cursor offset if it was at start or end of compose prompt', () => {
+ it('should NOT restore cursor offset if it was at start or end of compose prompt', async () => {
const originalQuery = 'my original input';
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
(props) => useInputHistory(props),
{
initialProps: {
@@ -325,10 +325,10 @@ describe('useInputHistory', () => {
expect(mockOnChange).toHaveBeenCalledWith(originalQuery, 'end');
});
- it('should remember text edits but use default cursor when navigating between history items', () => {
+ it('should remember text edits but use default cursor when navigating between history items', async () => {
const originalQuery = 'my original input';
const originalCursorOffset = 5;
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
(props) => useInputHistory(props),
{
initialProps: {
@@ -400,7 +400,7 @@ describe('useInputHistory', () => {
);
});
- it('should restore offset for history items ONLY if returning from them immediately', () => {
+ it('should restore offset for history items ONLY if returning from them immediately', async () => {
const originalQuery = 'my original input';
const initialProps = {
userMessages,
@@ -411,7 +411,7 @@ describe('useInputHistory', () => {
onChange: mockOnChange,
};
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
(props) => useInputHistory(props),
{
initialProps,
diff --git a/packages/cli/src/ui/hooks/useInputHistoryStore.test.ts b/packages/cli/src/ui/hooks/useInputHistoryStore.test.ts
index 1efacedb21..842009594d 100644
--- a/packages/cli/src/ui/hooks/useInputHistoryStore.test.ts
+++ b/packages/cli/src/ui/hooks/useInputHistoryStore.test.ts
@@ -15,14 +15,14 @@ describe('useInputHistoryStore', () => {
vi.clearAllMocks();
});
- it('should initialize with empty input history', () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ it('should initialize with empty input history', async () => {
+ const { result } = await renderHook(() => useInputHistoryStore());
expect(result.current.inputHistory).toEqual([]);
});
- it('should add input to history', () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ it('should add input to history', async () => {
+ const { result } = await renderHook(() => useInputHistoryStore());
act(() => {
result.current.addInput('test message 1');
@@ -40,8 +40,8 @@ describe('useInputHistoryStore', () => {
]);
});
- it('should not add empty or whitespace-only inputs', () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ it('should not add empty or whitespace-only inputs', async () => {
+ const { result } = await renderHook(() => useInputHistoryStore());
act(() => {
result.current.addInput('');
@@ -56,8 +56,8 @@ describe('useInputHistoryStore', () => {
expect(result.current.inputHistory).toEqual([]);
});
- it('should deduplicate consecutive identical messages', () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ it('should deduplicate consecutive identical messages', async () => {
+ const { result } = await renderHook(() => useInputHistoryStore());
act(() => {
result.current.addInput('test message');
@@ -91,7 +91,7 @@ describe('useInputHistoryStore', () => {
.mockResolvedValue(['newest', 'middle', 'oldest']),
};
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
await act(async () => {
await result.current.initializeFromLogger(mockLogger);
@@ -113,7 +113,7 @@ describe('useInputHistoryStore', () => {
.spyOn(debugLogger, 'warn')
.mockImplementation(() => {});
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
await act(async () => {
await result.current.initializeFromLogger(mockLogger);
@@ -135,7 +135,7 @@ describe('useInputHistoryStore', () => {
.mockResolvedValue(['message1', 'message2']),
};
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
// Call initializeFromLogger twice
await act(async () => {
@@ -152,7 +152,7 @@ describe('useInputHistoryStore', () => {
});
it('should handle null logger gracefully', async () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
await act(async () => {
await result.current.initializeFromLogger(null);
@@ -161,8 +161,8 @@ describe('useInputHistoryStore', () => {
expect(result.current.inputHistory).toEqual([]);
});
- it('should trim input before adding to history', () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ it('should trim input before adding to history', async () => {
+ const { result } = await renderHook(() => useInputHistoryStore());
act(() => {
result.current.addInput(' test message ');
@@ -185,7 +185,7 @@ describe('useInputHistoryStore', () => {
]), // newest first with duplicates
};
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
await act(async () => {
await result.current.initializeFromLogger(mockLogger);
@@ -204,7 +204,7 @@ describe('useInputHistoryStore', () => {
getPreviousUserMessages: vi.fn().mockResolvedValue(['old2', 'old1']), // newest first
};
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
// Initialize with past session
await act(async () => {
@@ -233,7 +233,7 @@ describe('useInputHistoryStore', () => {
.mockResolvedValue(['message2', 'message1', 'message2']), // newest first with non-consecutive duplicate
};
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
await act(async () => {
await result.current.initializeFromLogger(mockLogger);
@@ -247,8 +247,8 @@ describe('useInputHistoryStore', () => {
]);
});
- it('should handle complex deduplication with current session', () => {
- const { result } = renderHook(() => useInputHistoryStore());
+ it('should handle complex deduplication with current session', async () => {
+ const { result } = await renderHook(() => useInputHistoryStore());
// Add multiple messages with duplicates
act(() => {
@@ -278,7 +278,7 @@ describe('useInputHistoryStore', () => {
.mockResolvedValue(['newest', 'middle', 'oldest']), // newest first
};
- const { result } = renderHook(() => useInputHistoryStore());
+ const { result } = await renderHook(() => useInputHistoryStore());
await act(async () => {
await result.current.initializeFromLogger(mockLogger);
diff --git a/packages/cli/src/ui/hooks/useLoadingIndicator.test.tsx b/packages/cli/src/ui/hooks/useLoadingIndicator.test.tsx
index ae5e20e0e8..a16c6ea192 100644
--- a/packages/cli/src/ui/hooks/useLoadingIndicator.test.tsx
+++ b/packages/cli/src/ui/hooks/useLoadingIndicator.test.tsx
@@ -30,7 +30,7 @@ describe('useLoadingIndicator', () => {
vi.restoreAllMocks();
});
- const renderLoadingIndicatorHook = (
+ const renderLoadingIndicatorHook = async (
initialStreamingState: StreamingState,
initialShouldShowFocusHint: boolean = false,
initialRetryStatus: RetryAttemptPayload | null = null,
@@ -60,7 +60,7 @@ describe('useLoadingIndicator', () => {
});
return null;
}
- const { rerender } = render(
+ const { rerender } = await render(
{
};
};
- it('should initialize with default values when Idle', () => {
+ it('should initialize with default values when Idle', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
- const { result } = renderLoadingIndicatorHook(StreamingState.Idle);
+ const { result } = await renderLoadingIndicatorHook(StreamingState.Idle);
expect(result.current.elapsedTime).toBe(0);
expect(result.current.currentLoadingPhrase).toBeUndefined();
});
it('should show interactive shell waiting phrase when shouldShowFocusHint is true', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
- const { result, rerender } = renderLoadingIndicatorHook(
+ const { result, rerender } = await renderLoadingIndicatorHook(
StreamingState.Responding,
false,
);
@@ -125,7 +125,9 @@ describe('useLoadingIndicator', () => {
it('should reflect values when Responding', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
- const { result } = renderLoadingIndicatorHook(StreamingState.Responding);
+ const { result } = await renderLoadingIndicatorHook(
+ StreamingState.Responding,
+ );
// Initial phrase on first activation will be a tip, not necessarily from witty phrases
expect(result.current.elapsedTime).toBe(0);
@@ -142,7 +144,7 @@ describe('useLoadingIndicator', () => {
});
it('should show waiting phrase and retain elapsedTime when WaitingForConfirmation', async () => {
- const { result, rerender } = renderLoadingIndicatorHook(
+ const { result, rerender } = await renderLoadingIndicatorHook(
StreamingState.Responding,
);
@@ -169,7 +171,7 @@ describe('useLoadingIndicator', () => {
it('should reset elapsedTime and use a witty phrase when transitioning from WaitingForConfirmation to Responding', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
- const { result, rerender } = renderLoadingIndicatorHook(
+ const { result, rerender } = await renderLoadingIndicatorHook(
StreamingState.Responding,
);
@@ -202,7 +204,7 @@ describe('useLoadingIndicator', () => {
it('should reset timer and phrase when streamingState changes from Responding to Idle', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
- const { result, rerender } = renderLoadingIndicatorHook(
+ const { result, rerender } = await renderLoadingIndicatorHook(
StreamingState.Responding,
);
@@ -225,14 +227,14 @@ describe('useLoadingIndicator', () => {
expect(result.current.elapsedTime).toBe(0);
});
- it('should reflect retry status in currentLoadingPhrase when provided', () => {
+ it('should reflect retry status in currentLoadingPhrase when provided', async () => {
const retryStatus = {
model: 'gemini-pro',
attempt: 2,
maxAttempts: 3,
delayMs: 1000,
};
- const { result } = renderLoadingIndicatorHook(
+ const { result } = await renderLoadingIndicatorHook(
StreamingState.Responding,
false,
retryStatus,
@@ -242,14 +244,14 @@ describe('useLoadingIndicator', () => {
expect(result.current.currentLoadingPhrase).toContain('Attempt 3/3');
});
- it('should hide low-verbosity retry status for early retry attempts', () => {
+ it('should hide low-verbosity retry status for early retry attempts', async () => {
const retryStatus = {
model: 'gemini-pro',
attempt: 1,
maxAttempts: 5,
delayMs: 1000,
};
- const { result } = renderLoadingIndicatorHook(
+ const { result } = await renderLoadingIndicatorHook(
StreamingState.Responding,
false,
retryStatus,
@@ -262,14 +264,14 @@ describe('useLoadingIndicator', () => {
);
});
- it('should show a generic retry phrase in low error verbosity mode for later retries', () => {
+ it('should show a generic retry phrase in low error verbosity mode for later retries', async () => {
const retryStatus = {
model: 'gemini-pro',
attempt: 2,
maxAttempts: 5,
delayMs: 1000,
};
- const { result } = renderLoadingIndicatorHook(
+ const { result } = await renderLoadingIndicatorHook(
StreamingState.Responding,
false,
retryStatus,
@@ -282,8 +284,8 @@ describe('useLoadingIndicator', () => {
);
});
- it('should show no phrases when loadingPhrasesMode is "off"', () => {
- const { result } = renderLoadingIndicatorHook(
+ it('should show no phrases when loadingPhrasesMode is "off"', async () => {
+ const { result } = await renderLoadingIndicatorHook(
StreamingState.Responding,
false,
null,
diff --git a/packages/cli/src/ui/hooks/useLogger.test.tsx b/packages/cli/src/ui/hooks/useLogger.test.tsx
index 262dfb5380..c0791f5afe 100644
--- a/packages/cli/src/ui/hooks/useLogger.test.tsx
+++ b/packages/cli/src/ui/hooks/useLogger.test.tsx
@@ -4,9 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { act } from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook } from '../../test-utils/render.js';
-import { waitFor } from '../../test-utils/async.js';
import { useLogger } from './useLogger.js';
import {
sessionId as globalSessionId,
@@ -17,6 +17,8 @@ import {
import { ConfigContext } from '../contexts/ConfigContext.js';
import type React from 'react';
+let deferredInit: { resolve: (val?: unknown) => void };
+
// Mock Logger
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const actual =
@@ -24,7 +26,12 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
return {
...actual,
Logger: vi.fn().mockImplementation((id: string) => ({
- initialize: vi.fn().mockResolvedValue(undefined),
+ initialize: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredInit = { resolve };
+ }),
+ ),
sessionId: id,
})),
};
@@ -41,9 +48,15 @@ describe('useLogger', () => {
});
it('should initialize with the global sessionId by default', async () => {
- const { result } = renderHook(() => useLogger(mockStorage));
+ const { result } = await renderHook(() => useLogger(mockStorage));
- await waitFor(() => expect(result.current).not.toBeNull());
+ expect(result.current).toBeNull();
+
+ await act(async () => {
+ deferredInit.resolve();
+ });
+
+ expect(result.current).not.toBeNull();
expect(Logger).toHaveBeenCalledWith(globalSessionId, mockStorage);
});
@@ -54,9 +67,17 @@ describe('useLogger', () => {
);
- const { result } = renderHook(() => useLogger(mockStorage), { wrapper });
+ const { result } = await renderHook(() => useLogger(mockStorage), {
+ wrapper,
+ });
- await waitFor(() => expect(result.current).not.toBeNull());
+ expect(result.current).toBeNull();
+
+ await act(async () => {
+ deferredInit.resolve();
+ });
+
+ expect(result.current).not.toBeNull();
expect(Logger).toHaveBeenCalledWith('active-session-id', mockStorage);
});
});
diff --git a/packages/cli/src/ui/hooks/useMcpStatus.test.tsx b/packages/cli/src/ui/hooks/useMcpStatus.test.tsx
index 0311f03c63..6bb50eafd3 100644
--- a/packages/cli/src/ui/hooks/useMcpStatus.test.tsx
+++ b/packages/cli/src/ui/hooks/useMcpStatus.test.tsx
@@ -33,13 +33,13 @@ describe('useMcpStatus', () => {
} as unknown as Config;
});
- const renderMcpStatusHook = (config: Config) => {
+ const renderMcpStatusHook = async (config: Config) => {
let hookResult: ReturnType;
function TestComponent({ config }: { config: Config }) {
hookResult = useMcpStatus(config);
return null;
}
- render();
+ await render();
return {
result: {
get current() {
@@ -49,37 +49,37 @@ describe('useMcpStatus', () => {
};
};
- it('should initialize with correct values (no servers)', () => {
- const { result } = renderMcpStatusHook(mockConfig);
+ it('should initialize with correct values (no servers)', async () => {
+ const { result } = await renderMcpStatusHook(mockConfig);
expect(result.current.discoveryState).toBe(MCPDiscoveryState.NOT_STARTED);
expect(result.current.mcpServerCount).toBe(0);
expect(result.current.isMcpReady).toBe(true);
});
- it('should initialize with correct values (with servers, not started)', () => {
+ it('should initialize with correct values (with servers, not started)', async () => {
mockMcpClientManager.getMcpServerCount.mockReturnValue(1);
- const { result } = renderMcpStatusHook(mockConfig);
+ const { result } = await renderMcpStatusHook(mockConfig);
expect(result.current.isMcpReady).toBe(false);
});
- it('should not be ready while in progress', () => {
+ it('should not be ready while in progress', async () => {
mockMcpClientManager.getDiscoveryState.mockReturnValue(
MCPDiscoveryState.IN_PROGRESS,
);
mockMcpClientManager.getMcpServerCount.mockReturnValue(1);
- const { result } = renderMcpStatusHook(mockConfig);
+ const { result } = await renderMcpStatusHook(mockConfig);
expect(result.current.isMcpReady).toBe(false);
});
- it('should update state when McpClientUpdate is emitted', () => {
+ it('should update state when McpClientUpdate is emitted', async () => {
mockMcpClientManager.getMcpServerCount.mockReturnValue(1);
mockMcpClientManager.getDiscoveryState.mockReturnValue(
MCPDiscoveryState.IN_PROGRESS,
);
- const { result } = renderMcpStatusHook(mockConfig);
+ const { result } = await renderMcpStatusHook(mockConfig);
expect(result.current.isMcpReady).toBe(false);
diff --git a/packages/cli/src/ui/hooks/useMemoryMonitor.test.tsx b/packages/cli/src/ui/hooks/useMemoryMonitor.test.tsx
index c421270d81..cfaf2fb470 100644
--- a/packages/cli/src/ui/hooks/useMemoryMonitor.test.tsx
+++ b/packages/cli/src/ui/hooks/useMemoryMonitor.test.tsx
@@ -32,20 +32,20 @@ describe('useMemoryMonitor', () => {
return null;
}
- it('should not warn when memory usage is below threshold', () => {
+ it('should not warn when memory usage is below threshold', async () => {
memoryUsageSpy.mockReturnValue({
rss: MEMORY_WARNING_THRESHOLD / 2,
} as NodeJS.MemoryUsage);
- render();
+ await render();
vi.advanceTimersByTime(10000);
expect(addItem).not.toHaveBeenCalled();
});
- it('should warn when memory usage is above threshold', () => {
+ it('should warn when memory usage is above threshold', async () => {
memoryUsageSpy.mockReturnValue({
rss: MEMORY_WARNING_THRESHOLD * 1.5,
} as NodeJS.MemoryUsage);
- render();
+ await render();
vi.advanceTimersByTime(MEMORY_CHECK_INTERVAL);
expect(addItem).toHaveBeenCalledTimes(1);
expect(addItem).toHaveBeenCalledWith(
@@ -57,11 +57,11 @@ describe('useMemoryMonitor', () => {
);
});
- it('should only warn once', () => {
+ it('should only warn once', async () => {
memoryUsageSpy.mockReturnValue({
rss: MEMORY_WARNING_THRESHOLD * 1.5,
} as NodeJS.MemoryUsage);
- const { rerender } = render();
+ const { rerender } = await render();
vi.advanceTimersByTime(MEMORY_CHECK_INTERVAL);
expect(addItem).toHaveBeenCalledTimes(1);
diff --git a/packages/cli/src/ui/hooks/useMessageQueue.test.tsx b/packages/cli/src/ui/hooks/useMessageQueue.test.tsx
index 5b05d2a9f1..da6eea233c 100644
--- a/packages/cli/src/ui/hooks/useMessageQueue.test.tsx
+++ b/packages/cli/src/ui/hooks/useMessageQueue.test.tsx
@@ -24,7 +24,7 @@ describe('useMessageQueue', () => {
vi.clearAllMocks();
});
- const renderMessageQueueHook = (initialProps: {
+ const renderMessageQueueHook = async (initialProps: {
isConfigInitialized: boolean;
streamingState: StreamingState;
submitQuery: (query: string) => void;
@@ -35,7 +35,7 @@ describe('useMessageQueue', () => {
hookResult = useMessageQueue(props);
return null;
}
- const { rerender } = render();
+ const { rerender } = await render();
return {
result: {
get current() {
@@ -47,8 +47,8 @@ describe('useMessageQueue', () => {
};
};
- it('should initialize with empty queue', () => {
- const { result } = renderMessageQueueHook({
+ it('should initialize with empty queue', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
@@ -59,8 +59,8 @@ describe('useMessageQueue', () => {
expect(result.current.getQueuedMessagesText()).toBe('');
});
- it('should add messages to queue', () => {
- const { result } = renderMessageQueueHook({
+ it('should add messages to queue', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -78,8 +78,8 @@ describe('useMessageQueue', () => {
]);
});
- it('should filter out empty messages', () => {
- const { result } = renderMessageQueueHook({
+ it('should filter out empty messages', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -99,8 +99,8 @@ describe('useMessageQueue', () => {
]);
});
- it('should clear queue', () => {
- const { result } = renderMessageQueueHook({
+ it('should clear queue', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -120,8 +120,8 @@ describe('useMessageQueue', () => {
expect(result.current.messageQueue).toEqual([]);
});
- it('should return queued messages as text with double newlines', () => {
- const { result } = renderMessageQueueHook({
+ it('should return queued messages as text with double newlines', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -140,7 +140,7 @@ describe('useMessageQueue', () => {
});
it('should auto-submit queued messages when transitioning to Idle and MCP is ready', async () => {
- const { result, rerender } = renderMessageQueueHook({
+ const { result, rerender } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -165,7 +165,7 @@ describe('useMessageQueue', () => {
});
it('should wait for MCP readiness before auto-submitting', async () => {
- const { result, rerender } = renderMessageQueueHook({
+ const { result, rerender } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
@@ -189,8 +189,8 @@ describe('useMessageQueue', () => {
});
});
- it('should not auto-submit when queue is empty', () => {
- const { rerender } = renderMessageQueueHook({
+ it('should not auto-submit when queue is empty', async () => {
+ const { rerender } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -203,8 +203,8 @@ describe('useMessageQueue', () => {
expect(mockSubmitQuery).not.toHaveBeenCalled();
});
- it('should not auto-submit when not transitioning to Idle', () => {
- const { result, rerender } = renderMessageQueueHook({
+ it('should not auto-submit when not transitioning to Idle', async () => {
+ const { result, rerender } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -224,7 +224,7 @@ describe('useMessageQueue', () => {
});
it('should handle multiple state transitions correctly', async () => {
- const { result, rerender } = renderMessageQueueHook({
+ const { result, rerender } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Idle,
submitQuery: mockSubmitQuery,
@@ -265,8 +265,8 @@ describe('useMessageQueue', () => {
});
describe('popAllMessages', () => {
- it('should pop all messages and return them joined with double newlines', () => {
- const { result } = renderMessageQueueHook({
+ it('should pop all messages and return them joined with double newlines', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -296,8 +296,8 @@ describe('useMessageQueue', () => {
expect(result.current.messageQueue).toEqual([]);
});
- it('should return undefined when queue is empty', () => {
- const { result } = renderMessageQueueHook({
+ it('should return undefined when queue is empty', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -313,8 +313,8 @@ describe('useMessageQueue', () => {
expect(result.current.messageQueue).toEqual([]);
});
- it('should handle single message correctly', () => {
- const { result } = renderMessageQueueHook({
+ it('should handle single message correctly', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -334,8 +334,8 @@ describe('useMessageQueue', () => {
expect(result.current.messageQueue).toEqual([]);
});
- it('should clear the entire queue after popping', () => {
- const { result } = renderMessageQueueHook({
+ it('should clear the entire queue after popping', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
@@ -364,8 +364,8 @@ describe('useMessageQueue', () => {
expect(secondPop).toBeUndefined();
});
- it('should work correctly with state updates', () => {
- const { result } = renderMessageQueueHook({
+ it('should work correctly with state updates', async () => {
+ const { result } = await renderMessageQueueHook({
isConfigInitialized: true,
streamingState: StreamingState.Responding,
submitQuery: mockSubmitQuery,
diff --git a/packages/cli/src/ui/hooks/useModelCommand.test.tsx b/packages/cli/src/ui/hooks/useModelCommand.test.tsx
index 7232308cc7..b93474e149 100644
--- a/packages/cli/src/ui/hooks/useModelCommand.test.tsx
+++ b/packages/cli/src/ui/hooks/useModelCommand.test.tsx
@@ -17,14 +17,14 @@ describe('useModelCommand', () => {
return null;
}
- it('should initialize with the model dialog closed', () => {
- const { unmount } = render();
+ it('should initialize with the model dialog closed', async () => {
+ const { unmount } = await render();
expect(result.isModelDialogOpen).toBe(false);
unmount();
});
- it('should open the model dialog when openModelDialog is called', () => {
- const { unmount } = render();
+ it('should open the model dialog when openModelDialog is called', async () => {
+ const { unmount } = await render();
act(() => {
result.openModelDialog();
@@ -34,8 +34,8 @@ describe('useModelCommand', () => {
unmount();
});
- it('should close the model dialog when closeModelDialog is called', () => {
- const { unmount } = render();
+ it('should close the model dialog when closeModelDialog is called', async () => {
+ const { unmount } = await render();
// Open it first
act(() => {
diff --git a/packages/cli/src/ui/hooks/useMouse.test.ts b/packages/cli/src/ui/hooks/useMouse.test.ts
index 28439f6850..c08ec3eab2 100644
--- a/packages/cli/src/ui/hooks/useMouse.test.ts
+++ b/packages/cli/src/ui/hooks/useMouse.test.ts
@@ -30,22 +30,22 @@ describe('useMouse', () => {
vi.clearAllMocks();
});
- it('should not subscribe when isActive is false', () => {
- renderHook(() => useMouse(mockOnMouseEvent, { isActive: false }));
+ it('should not subscribe when isActive is false', async () => {
+ await renderHook(() => useMouse(mockOnMouseEvent, { isActive: false }));
const { subscribe } = useMouseContext();
expect(subscribe).not.toHaveBeenCalled();
});
- it('should subscribe when isActive is true', () => {
- renderHook(() => useMouse(mockOnMouseEvent, { isActive: true }));
+ it('should subscribe when isActive is true', async () => {
+ await renderHook(() => useMouse(mockOnMouseEvent, { isActive: true }));
const { subscribe } = useMouseContext();
expect(subscribe).toHaveBeenCalledWith(mockOnMouseEvent);
});
- it('should unsubscribe on unmount', () => {
- const { unmount } = renderHook(() =>
+ it('should unsubscribe on unmount', async () => {
+ const { unmount } = await renderHook(() =>
useMouse(mockOnMouseEvent, { isActive: true }),
);
@@ -54,8 +54,8 @@ describe('useMouse', () => {
expect(unsubscribe).toHaveBeenCalledWith(mockOnMouseEvent);
});
- it('should unsubscribe when isActive becomes false', () => {
- const { rerender } = renderHook(
+ it('should unsubscribe when isActive becomes false', async () => {
+ const { rerender } = await renderHook(
({ isActive }: { isActive: boolean }) =>
useMouse(mockOnMouseEvent, { isActive }),
{
diff --git a/packages/cli/src/ui/hooks/useMouseClick.test.ts b/packages/cli/src/ui/hooks/useMouseClick.test.ts
index abb73d279d..ffe5a9ec6c 100644
--- a/packages/cli/src/ui/hooks/useMouseClick.test.ts
+++ b/packages/cli/src/ui/hooks/useMouseClick.test.ts
@@ -43,7 +43,7 @@ describe('useMouseClick', () => {
height: 10,
} as unknown as ReturnType);
- const { unmount, waitUntilReady } = renderHook(() =>
+ const { unmount, waitUntilReady } = await renderHook(() =>
useMouseClick(containerRef, handler),
);
await waitUntilReady();
@@ -74,7 +74,7 @@ describe('useMouseClick', () => {
height: 10,
} as unknown as ReturnType);
- const { unmount, waitUntilReady } = renderHook(() =>
+ const { unmount, waitUntilReady } = await renderHook(() =>
useMouseClick(containerRef, handler),
);
await waitUntilReady();
diff --git a/packages/cli/src/ui/hooks/usePermissionsModifyTrust.test.ts b/packages/cli/src/ui/hooks/usePermissionsModifyTrust.test.ts
index 0fcf3d62d7..991a52a1c8 100644
--- a/packages/cli/src/ui/hooks/usePermissionsModifyTrust.test.ts
+++ b/packages/cli/src/ui/hooks/usePermissionsModifyTrust.test.ts
@@ -93,7 +93,7 @@ describe('usePermissionsModifyTrust', () => {
});
describe('when targetDirectory is the current workspace', () => {
- it('should initialize with the correct trust level', () => {
+ it('should initialize with the correct trust level', async () => {
mockedLoadTrustedFolders.mockReturnValue({
user: { config: { '/test/dir': TrustLevel.TRUST_FOLDER } },
} as unknown as LoadedTrustedFolders);
@@ -102,14 +102,14 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
expect(result.current.currentTrustLevel).toBe(TrustLevel.TRUST_FOLDER);
});
- it('should detect inherited trust from parent', () => {
+ it('should detect inherited trust from parent', async () => {
mockedLoadTrustedFolders.mockReturnValue({
user: { config: {} },
setValue: vi.fn(),
@@ -119,7 +119,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -127,7 +127,7 @@ describe('usePermissionsModifyTrust', () => {
expect(result.current.isInheritedTrustFromIde).toBe(false);
});
- it('should detect inherited trust from IDE', () => {
+ it('should detect inherited trust from IDE', async () => {
mockedLoadTrustedFolders.mockReturnValue({
user: { config: {} }, // No explicit trust
} as unknown as LoadedTrustedFolders);
@@ -136,7 +136,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'ide',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -155,7 +155,7 @@ describe('usePermissionsModifyTrust', () => {
.mockReturnValueOnce({ isTrusted: false, source: 'file' })
.mockReturnValueOnce({ isTrusted: true, source: 'file' });
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -179,7 +179,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -206,7 +206,7 @@ describe('usePermissionsModifyTrust', () => {
.mockReturnValueOnce({ isTrusted: false, source: 'file' })
.mockReturnValueOnce({ isTrusted: true, source: 'file' });
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -236,7 +236,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -263,7 +263,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'ide',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -284,7 +284,7 @@ describe('usePermissionsModifyTrust', () => {
describe('when targetDirectory is not the current workspace', () => {
const otherDirectory = '/other/dir';
- it('should not detect inherited trust', () => {
+ it('should not detect inherited trust', async () => {
mockedLoadTrustedFolders.mockReturnValue({
user: { config: {} },
} as unknown as LoadedTrustedFolders);
@@ -293,7 +293,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
);
@@ -312,7 +312,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
);
@@ -338,7 +338,7 @@ describe('usePermissionsModifyTrust', () => {
source: 'file',
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, otherDirectory),
);
@@ -366,7 +366,7 @@ describe('usePermissionsModifyTrust', () => {
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
@@ -396,7 +396,7 @@ describe('usePermissionsModifyTrust', () => {
const emitFeedbackSpy = vi.spyOn(coreEvents, 'emitFeedback');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
usePermissionsModifyTrust(mockOnExit, mockAddItem, mockedCwd()),
);
diff --git a/packages/cli/src/ui/hooks/usePhraseCycler.test.tsx b/packages/cli/src/ui/hooks/usePhraseCycler.test.tsx
index ca89c623ac..81299870c7 100644
--- a/packages/cli/src/ui/hooks/usePhraseCycler.test.tsx
+++ b/packages/cli/src/ui/hooks/usePhraseCycler.test.tsx
@@ -52,19 +52,17 @@ describe('usePhraseCycler', () => {
it('should initialize with an empty string when not active and not waiting', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true }).trim()).toBe('');
unmount();
});
it('should show "Waiting for user confirmation..." when isWaiting is true', async () => {
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
await act(async () => {
rerender();
@@ -76,10 +74,9 @@ describe('usePhraseCycler', () => {
});
it('should show interactive shell waiting message immediately when isInteractiveShellWaiting is true', async () => {
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
await act(async () => {
rerender(
@@ -97,10 +94,9 @@ describe('usePhraseCycler', () => {
});
it('should prioritize interactive shell waiting over normal waiting immediately', async () => {
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame().trim()).toMatchSnapshot();
await act(async () => {
@@ -118,10 +114,9 @@ describe('usePhraseCycler', () => {
});
it('should not cycle phrases if isActive is false and not waiting', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const initialPhrase = lastFrame({ allowEmpty: true }).trim();
await act(async () => {
@@ -135,10 +130,9 @@ describe('usePhraseCycler', () => {
it('should show a tip on first activation, then a witty phrase', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.99); // Subsequent phrases are witty
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
// Initial phrase on first activation should be a tip
expect(INFORMATIVE_TIPS).toContain(lastFrame().trim());
@@ -154,10 +148,9 @@ describe('usePhraseCycler', () => {
it('should cycle through phrases when isActive is true and not waiting', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
// Initial phrase on first activation will be a tip
// After the first interval, it should follow the random pattern (witty phrases due to mock)
@@ -187,14 +180,13 @@ describe('usePhraseCycler', () => {
return val;
});
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
// Activate -> On first activation will show tip on initial call, then first interval will use first mock value for 'Phrase A'
await act(async () => {
@@ -257,10 +249,9 @@ describe('usePhraseCycler', () => {
});
it('should clear phrase interval on unmount when active', async () => {
- const { unmount, waitUntilReady } = render(
+ const { unmount } = await render(
,
);
- await waitUntilReady();
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
unmount();
@@ -299,8 +290,9 @@ describe('usePhraseCycler', () => {
);
};
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount, waitUntilReady } = await render(
+ ,
+ );
// After first interval, it should use custom phrases
await act(async () => {
@@ -350,14 +342,11 @@ describe('usePhraseCycler', () => {
expect(WITTY_LOADING_PHRASES).toContain(lastFrame().trim());
unmount();
});
-
it('should fall back to witty phrases if custom phrases are an empty array', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount, waitUntilReady } = await render(
,
);
- await waitUntilReady();
-
await act(async () => {
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS); // Next phrase after tip
});
@@ -365,13 +354,11 @@ describe('usePhraseCycler', () => {
expect(WITTY_LOADING_PHRASES).toContain(lastFrame().trim());
unmount();
});
-
it('should reset phrase when transitioning from waiting to active', async () => {
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty for subsequent phrases
- const { lastFrame, rerender, waitUntilReady, unmount } = render(
+ const { lastFrame, rerender, unmount, waitUntilReady } = await render(
,
);
- await waitUntilReady();
// Cycle to a different phrase (should be witty due to mock)
await act(async () => {
diff --git a/packages/cli/src/ui/hooks/usePrivacySettings.test.tsx b/packages/cli/src/ui/hooks/usePrivacySettings.test.tsx
index fbb990ffbc..adf1eb53d5 100644
--- a/packages/cli/src/ui/hooks/usePrivacySettings.test.tsx
+++ b/packages/cli/src/ui/hooks/usePrivacySettings.test.tsx
@@ -33,13 +33,13 @@ describe('usePrivacySettings', () => {
vi.clearAllMocks();
});
- const renderPrivacySettingsHook = () => {
+ const renderPrivacySettingsHook = async () => {
let hookResult: ReturnType;
function TestComponent() {
hookResult = usePrivacySettings(mockConfig);
return null;
}
- render();
+ await render();
return {
result: {
get current() {
@@ -52,7 +52,7 @@ describe('usePrivacySettings', () => {
it('should throw error when content generator is not a CodeAssistServer', async () => {
vi.mocked(getCodeAssistServer).mockReturnValue(undefined);
- const { result } = renderPrivacySettingsHook();
+ const { result } = await act(async () => renderPrivacySettingsHook());
await waitFor(() => {
expect(result.current.privacyState.isLoading).toBe(false);
@@ -68,7 +68,7 @@ describe('usePrivacySettings', () => {
userTier: UserTierId.STANDARD,
} as unknown as CodeAssistServer);
- const { result } = renderPrivacySettingsHook();
+ const { result } = await act(async () => renderPrivacySettingsHook());
await waitFor(() => {
expect(result.current.privacyState.isLoading).toBe(false);
@@ -84,7 +84,7 @@ describe('usePrivacySettings', () => {
userTier: UserTierId.FREE,
} as unknown as CodeAssistServer);
- const { result } = renderPrivacySettingsHook();
+ const { result } = await act(async () => renderPrivacySettingsHook());
await waitFor(() => {
expect(result.current.privacyState.isLoading).toBe(false);
@@ -96,11 +96,15 @@ describe('usePrivacySettings', () => {
});
it('should update data collection opt-in setting', async () => {
+ let deferredGet: { resolve: (val: unknown) => void };
const mockCodeAssistServer = {
projectId: 'test-project-id',
- getCodeAssistGlobalUserSetting: vi.fn().mockResolvedValue({
- freeTierDataCollectionOptin: true,
- }),
+ getCodeAssistGlobalUserSetting: vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredGet = { resolve };
+ }),
+ ),
setCodeAssistGlobalUserSetting: vi.fn().mockResolvedValue({
freeTierDataCollectionOptin: false,
}),
@@ -108,9 +112,19 @@ describe('usePrivacySettings', () => {
} as unknown as CodeAssistServer;
vi.mocked(getCodeAssistServer).mockReturnValue(mockCodeAssistServer);
- const { result } = renderPrivacySettingsHook();
+ const { result } = await act(async () => renderPrivacySettingsHook());
- // Wait for initial load
+ // Initially loading
+ expect(result.current.privacyState.isLoading).toBe(true);
+
+ // Finish initial load
+ await act(async () => {
+ deferredGet.resolve({
+ freeTierDataCollectionOptin: true,
+ });
+ });
+
+ // Wait for initial load to process
await waitFor(() => {
expect(result.current.privacyState.isLoading).toBe(false);
});
diff --git a/packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts b/packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts
index ea4234bd10..4883789659 100644
--- a/packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts
+++ b/packages/cli/src/ui/hooks/useQuotaAndFallback.test.ts
@@ -106,8 +106,8 @@ describe('useQuotaAndFallback', () => {
vi.restoreAllMocks();
});
- it('should register a fallback handler on initialization', () => {
- renderHook(() =>
+ it('should register a fallback handler on initialization', async () => {
+ await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -130,7 +130,7 @@ describe('useQuotaAndFallback', () => {
authType: AuthType.USE_GEMINI,
});
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -162,7 +162,7 @@ describe('useQuotaAndFallback', () => {
});
it('should auto-retry transient capacity failures in low verbosity mode', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -190,7 +190,7 @@ describe('useQuotaAndFallback', () => {
});
it('should still prompt for terminal quota in low verbosity mode', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -224,7 +224,7 @@ describe('useQuotaAndFallback', () => {
describe('Interactive Fallback', () => {
it('should set an interactive request for a terminal quota error', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -279,7 +279,7 @@ describe('useQuotaAndFallback', () => {
});
it('should show the model name for a terminal quota error on a non-pro model', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -320,7 +320,7 @@ describe('useQuotaAndFallback', () => {
});
it('should handle terminal quota error without retry delay', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -354,7 +354,7 @@ describe('useQuotaAndFallback', () => {
});
it('should handle race conditions by stopping subsequent requests', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -421,7 +421,7 @@ describe('useQuotaAndFallback', () => {
for (const { description, error } of testCases) {
it(`should handle ${description} correctly`, async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -479,7 +479,7 @@ describe('useQuotaAndFallback', () => {
}
it('should handle ModelNotFoundError correctly', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -526,7 +526,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should handle ModelNotFoundError with invalid model correctly', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -592,7 +592,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
// so the user can downgrade to Flash instead of retrying infinitely.
vi.mocked(shouldAutoUseCredits).mockReturnValue(true);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -637,7 +637,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
vi.mocked(shouldAutoUseCredits).mockReturnValue(false);
vi.mocked(shouldShowOverageMenu).mockReturnValue(true);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -679,7 +679,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
vi.mocked(shouldAutoUseCredits).mockReturnValue(false);
vi.mocked(shouldShowOverageMenu).mockReturnValue(true);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -719,7 +719,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
vi.mocked(shouldShowOverageMenu).mockReturnValue(false);
vi.mocked(shouldShowEmptyWalletMenu).mockReturnValue(true);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -762,7 +762,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
vi.mocked(shouldShowOverageMenu).mockReturnValue(false);
vi.mocked(shouldShowEmptyWalletMenu).mockReturnValue(true);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -807,8 +807,8 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
describe('handleProQuotaChoice', () => {
- it('should do nothing if there is no pending pro quota request', () => {
- const { result } = renderHook(() =>
+ it('should do nothing if there is no pending pro quota request', async () => {
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -828,7 +828,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should resolve intent to "retry_later"', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -861,7 +861,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should resolve intent to "retry_always" and add info message on continue', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -907,7 +907,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should show a special message when falling back from the preview model', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -945,7 +945,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should show a special message when falling back from the preview model, but do not show periodical check message for flash model fallback', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -990,8 +990,8 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
setValidationHandlerSpy = vi.spyOn(mockConfig, 'setValidationHandler');
});
- it('should register a validation handler on initialization', () => {
- renderHook(() =>
+ it('should register a validation handler on initialization', async () => {
+ await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -1008,7 +1008,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should set a validation request when handler is called', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -1052,7 +1052,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should handle race conditions by returning cancel for subsequent requests', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -1096,7 +1096,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should call onShowAuthSelection when change_auth is chosen', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -1128,7 +1128,7 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
});
it('should call onShowAuthSelection when cancel is chosen', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
@@ -1159,8 +1159,8 @@ Your admin might have disabled the access. Contact them to enable the Preview Re
expect(mockOnShowAuthSelection).toHaveBeenCalledTimes(1);
});
- it('should do nothing if handleValidationChoice is called without pending request', () => {
- const { result } = renderHook(() =>
+ it('should do nothing if handleValidationChoice is called without pending request', async () => {
+ const { result } = await renderHook(() =>
useQuotaAndFallback({
config: mockConfig,
historyManager: mockHistoryManager,
diff --git a/packages/cli/src/ui/hooks/useRewind.test.ts b/packages/cli/src/ui/hooks/useRewind.test.ts
index 5640a6965b..45c63c935d 100644
--- a/packages/cli/src/ui/hooks/useRewind.test.ts
+++ b/packages/cli/src/ui/hooks/useRewind.test.ts
@@ -48,14 +48,14 @@ describe('useRewindLogic', () => {
vi.clearAllMocks();
});
- it('should initialize with no selection', () => {
- const { result } = renderHook(() => useRewind(mockConversation));
+ it('should initialize with no selection', async () => {
+ const { result } = await renderHook(() => useRewind(mockConversation));
expect(result.current.selectedMessageId).toBeNull();
expect(result.current.confirmationStats).toBeNull();
});
- it('should update state when a message is selected', () => {
+ it('should update state when a message is selected', async () => {
const mockStats: FileChangeStats = {
fileCount: 1,
addedLines: 5,
@@ -63,7 +63,7 @@ describe('useRewindLogic', () => {
};
vi.mocked(rewindFileOps.calculateRewindImpact).mockReturnValue(mockStats);
- const { result } = renderHook(() => useRewind(mockConversation));
+ const { result } = await renderHook(() => useRewind(mockConversation));
act(() => {
result.current.selectMessage('msg-1');
@@ -77,8 +77,8 @@ describe('useRewindLogic', () => {
);
});
- it('should not update state if selected message is not found', () => {
- const { result } = renderHook(() => useRewind(mockConversation));
+ it('should not update state if selected message is not found', async () => {
+ const { result } = await renderHook(() => useRewind(mockConversation));
act(() => {
result.current.selectMessage('non-existent-id');
@@ -88,7 +88,7 @@ describe('useRewindLogic', () => {
expect(result.current.confirmationStats).toBeNull();
});
- it('should clear selection correctly', () => {
+ it('should clear selection correctly', async () => {
const mockStats: FileChangeStats = {
fileCount: 1,
addedLines: 5,
@@ -96,7 +96,7 @@ describe('useRewindLogic', () => {
};
vi.mocked(rewindFileOps.calculateRewindImpact).mockReturnValue(mockStats);
- const { result } = renderHook(() => useRewind(mockConversation));
+ const { result } = await renderHook(() => useRewind(mockConversation));
// Select first
act(() => {
@@ -113,7 +113,7 @@ describe('useRewindLogic', () => {
expect(result.current.confirmationStats).toBeNull();
});
- it('should proxy getStats call to utility function', () => {
+ it('should proxy getStats call to utility function', async () => {
const mockStats: FileChangeStats = {
fileCount: 2,
addedLines: 10,
@@ -121,7 +121,7 @@ describe('useRewindLogic', () => {
};
vi.mocked(rewindFileOps.calculateTurnStats).mockReturnValue(mockStats);
- const { result } = renderHook(() => useRewind(mockConversation));
+ const { result } = await renderHook(() => useRewind(mockConversation));
const stats = result.current.getStats(mockUserMessage);
diff --git a/packages/cli/src/ui/hooks/useSelectionList.test.tsx b/packages/cli/src/ui/hooks/useSelectionList.test.tsx
index 6a1b82f77a..744fb18cf8 100644
--- a/packages/cli/src/ui/hooks/useSelectionList.test.tsx
+++ b/packages/cli/src/ui/hooks/useSelectionList.test.tsx
@@ -89,10 +89,9 @@ describe('useSelectionList', () => {
hookResult = useSelectionList(props);
return null;
}
- const { rerender, unmount, waitUntilReady } = render(
+ const { rerender, unmount, waitUntilReady } = await render(
,
);
- await waitUntilReady();
return {
result: {
@@ -1102,10 +1101,9 @@ describe('useSelectionList', () => {
});
return null;
}
- const { rerender, waitUntilReady } = render(
+ const { rerender, waitUntilReady } = await render(
,
);
- await waitUntilReady();
return {
rerender: async (newProps: Partial) => {
diff --git a/packages/cli/src/ui/hooks/useSessionBrowser.test.ts b/packages/cli/src/ui/hooks/useSessionBrowser.test.ts
index 73022f1542..6ef39b7a5d 100644
--- a/packages/cli/src/ui/hooks/useSessionBrowser.test.ts
+++ b/packages/cli/src/ui/hooks/useSessionBrowser.test.ts
@@ -100,7 +100,7 @@ describe('useSessionBrowser', () => {
mockedGetSessionFiles.mockResolvedValue([mockSession]);
mockedFs.readFile.mockResolvedValue(JSON.stringify(mockConversation));
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useSessionBrowser(mockConfig, mockOnLoadHistory),
);
@@ -127,7 +127,7 @@ describe('useSessionBrowser', () => {
} as SessionInfo;
mockedFs.readFile.mockRejectedValue(new Error('File not found'));
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useSessionBrowser(mockConfig, mockOnLoadHistory),
);
@@ -151,7 +151,7 @@ describe('useSessionBrowser', () => {
} as SessionInfo;
mockedFs.readFile.mockResolvedValue('invalid json');
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useSessionBrowser(mockConfig, mockOnLoadHistory),
);
diff --git a/packages/cli/src/ui/hooks/useSessionResume.test.ts b/packages/cli/src/ui/hooks/useSessionResume.test.ts
index 9350cc167a..3997eb06c5 100644
--- a/packages/cli/src/ui/hooks/useSessionResume.test.ts
+++ b/packages/cli/src/ui/hooks/useSessionResume.test.ts
@@ -56,14 +56,18 @@ describe('useSessionResume', () => {
});
describe('loadHistoryForResume', () => {
- it('should return a loadHistoryForResume callback', () => {
- const { result } = renderHook(() => useSessionResume(getDefaultProps()));
+ it('should return a loadHistoryForResume callback', async () => {
+ const { result } = await renderHook(() =>
+ useSessionResume(getDefaultProps()),
+ );
expect(result.current.loadHistoryForResume).toBeInstanceOf(Function);
});
it('should clear history and add items when loading history', async () => {
- const { result } = renderHook(() => useSessionResume(getDefaultProps()));
+ const { result } = await renderHook(() =>
+ useSessionResume(getDefaultProps()),
+ );
const uiHistory: HistoryItemWithoutId[] = [
{ type: 'user', text: 'Hello' },
@@ -117,7 +121,7 @@ describe('useSessionResume', () => {
});
it('should not load history if Gemini client is not initialized', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useSessionResume({
...getDefaultProps(),
isGeminiClientInitialized: false,
@@ -155,7 +159,9 @@ describe('useSessionResume', () => {
});
it('should handle empty history arrays', async () => {
- const { result } = renderHook(() => useSessionResume(getDefaultProps()));
+ const { result } = await renderHook(() =>
+ useSessionResume(getDefaultProps()),
+ );
const resumedData: ResumedSessionData = {
conversation: {
@@ -190,7 +196,7 @@ describe('useSessionResume', () => {
getWorkspaceContext: vi.fn().mockReturnValue(mockWorkspaceContext),
};
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useSessionResume({
...getDefaultProps(),
config: configWithWorkspace as unknown as Config,
@@ -230,7 +236,7 @@ describe('useSessionResume', () => {
getWorkspaceContext: vi.fn().mockReturnValue(mockWorkspaceContext),
};
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useSessionResume({
...getDefaultProps(),
config: configWithWorkspace as unknown as Config,
@@ -258,8 +264,8 @@ describe('useSessionResume', () => {
});
describe('callback stability', () => {
- it('should maintain stable loadHistoryForResume reference across renders', () => {
- const { result, rerender } = renderHook(() =>
+ it('should maintain stable loadHistoryForResume reference across renders', async () => {
+ const { result, rerender } = await renderHook(() =>
useSessionResume(getDefaultProps()),
);
@@ -270,8 +276,8 @@ describe('useSessionResume', () => {
expect(result.current.loadHistoryForResume).toBe(initialCallback);
});
- it('should update callback when config changes', () => {
- const { result, rerender } = renderHook(
+ it('should update callback when config changes', async () => {
+ const { result, rerender } = await renderHook(
({ config }: { config: Config }) =>
useSessionResume({
...getDefaultProps(),
@@ -295,15 +301,15 @@ describe('useSessionResume', () => {
});
describe('automatic resume on mount', () => {
- it('should not resume when resumedSessionData is not provided', () => {
- renderHook(() => useSessionResume(getDefaultProps()));
+ it('should not resume when resumedSessionData is not provided', async () => {
+ await renderHook(() => useSessionResume(getDefaultProps()));
expect(mockHistoryManager.clearItems).not.toHaveBeenCalled();
expect(mockHistoryManager.addItem).not.toHaveBeenCalled();
expect(mockGeminiClient.resumeChat).not.toHaveBeenCalled();
});
- it('should not resume when user is authenticating', () => {
+ it('should not resume when user is authenticating', async () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-123',
projectHash: 'project-123',
@@ -319,7 +325,7 @@ describe('useSessionResume', () => {
] as MessageRecord[],
};
- renderHook(() =>
+ await renderHook(() =>
useSessionResume({
...getDefaultProps(),
resumedSessionData: {
@@ -335,7 +341,7 @@ describe('useSessionResume', () => {
expect(mockGeminiClient.resumeChat).not.toHaveBeenCalled();
});
- it('should not resume when Gemini client is not initialized', () => {
+ it('should not resume when Gemini client is not initialized', async () => {
const conversation: ConversationRecord = {
sessionId: 'auto-resume-123',
projectHash: 'project-123',
@@ -351,7 +357,7 @@ describe('useSessionResume', () => {
] as MessageRecord[],
};
- renderHook(() =>
+ await renderHook(() =>
useSessionResume({
...getDefaultProps(),
resumedSessionData: {
@@ -390,7 +396,7 @@ describe('useSessionResume', () => {
};
await act(async () => {
- renderHook(() =>
+ await renderHook(() =>
useSessionResume({
...getDefaultProps(),
resumedSessionData: {
@@ -440,7 +446,7 @@ describe('useSessionResume', () => {
let rerenderFunc: (props: { refreshStatic: () => void }) => void;
await act(async () => {
- const { rerender } = renderHook(
+ const { rerender } = await renderHook(
({ refreshStatic }: { refreshStatic: () => void }) =>
useSessionResume({
...getDefaultProps(),
@@ -500,7 +506,7 @@ describe('useSessionResume', () => {
};
await act(async () => {
- renderHook(() =>
+ await renderHook(() =>
useSessionResume({
...getDefaultProps(),
resumedSessionData: {
diff --git a/packages/cli/src/ui/hooks/useSettingsNavigation.test.ts b/packages/cli/src/ui/hooks/useSettingsNavigation.test.ts
index 5a64119f40..41365d8d72 100644
--- a/packages/cli/src/ui/hooks/useSettingsNavigation.test.ts
+++ b/packages/cli/src/ui/hooks/useSettingsNavigation.test.ts
@@ -18,8 +18,8 @@ describe('useSettingsNavigation', () => {
{ key: 'e' },
];
- it('should initialize with the first item active', () => {
- const { result } = renderHook(() =>
+ it('should initialize with the first item active', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
expect(result.current.activeIndex).toBe(0);
@@ -27,8 +27,8 @@ describe('useSettingsNavigation', () => {
expect(result.current.windowStart).toBe(0);
});
- it('should move down correctly', () => {
- const { result } = renderHook(() =>
+ it('should move down correctly', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
act(() => result.current.moveDown());
@@ -36,8 +36,8 @@ describe('useSettingsNavigation', () => {
expect(result.current.activeItemKey).toBe('b');
});
- it('should move up correctly', () => {
- const { result } = renderHook(() =>
+ it('should move up correctly', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
act(() => result.current.moveDown()); // to index 1
@@ -45,8 +45,8 @@ describe('useSettingsNavigation', () => {
expect(result.current.activeIndex).toBe(0);
});
- it('should wrap around from top to bottom', () => {
- const { result } = renderHook(() =>
+ it('should wrap around from top to bottom', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
act(() => result.current.moveUp());
@@ -54,8 +54,8 @@ describe('useSettingsNavigation', () => {
expect(result.current.activeItemKey).toBe('e');
});
- it('should wrap around from bottom to top', () => {
- const { result } = renderHook(() =>
+ it('should wrap around from bottom to top', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
// Move to last item
@@ -71,8 +71,8 @@ describe('useSettingsNavigation', () => {
expect(result.current.activeIndex).toBe(0);
});
- it('should adjust scrollOffset when moving down past visible area', () => {
- const { result } = renderHook(() =>
+ it('should adjust scrollOffset when moving down past visible area', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
@@ -84,8 +84,8 @@ describe('useSettingsNavigation', () => {
expect(result.current.windowStart).toBe(1);
});
- it('should adjust scrollOffset when moving up past visible area', () => {
- const { result } = renderHook(() =>
+ it('should adjust scrollOffset when moving up past visible area', async () => {
+ const { result } = await renderHook(() =>
useSettingsNavigation({ items: mockItems, maxItemsToShow: 3 }),
);
@@ -100,9 +100,9 @@ describe('useSettingsNavigation', () => {
expect(result.current.windowStart).toBe(0);
});
- it('should handle item preservation when list filters (Part 1 logic)', () => {
+ it('should handle item preservation when list filters (Part 1 logic)', async () => {
let items = mockItems;
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ list }) => useSettingsNavigation({ items: list, maxItemsToShow: 3 }),
{ initialProps: { list: items } },
);
diff --git a/packages/cli/src/ui/hooks/useShellHistory.test.ts b/packages/cli/src/ui/hooks/useShellHistory.test.ts
index 325e8d6adb..2ed8608141 100644
--- a/packages/cli/src/ui/hooks/useShellHistory.test.ts
+++ b/packages/cli/src/ui/hooks/useShellHistory.test.ts
@@ -100,7 +100,7 @@ describe('useShellHistory', () => {
it('should initialize and read the history file from the correct path', async () => {
mockedFs.readFile.mockResolvedValue('cmd1\ncmd2');
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
@@ -127,7 +127,7 @@ describe('useShellHistory', () => {
error.code = 'ENOENT';
mockedFs.readFile.mockRejectedValue(error);
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
@@ -146,7 +146,7 @@ describe('useShellHistory', () => {
});
it('should add a command and write to the history file', async () => {
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
@@ -179,7 +179,7 @@ describe('useShellHistory', () => {
it('should navigate history correctly with previous/next commands', async () => {
mockedFs.readFile.mockResolvedValue('cmd1\ncmd2\ncmd3');
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
@@ -231,7 +231,7 @@ describe('useShellHistory', () => {
});
it('should not add empty or whitespace-only commands to history', async () => {
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
@@ -252,7 +252,7 @@ describe('useShellHistory', () => {
const oldCommands = Array.from({ length: 120 }, (_, i) => `old_cmd_${i}`);
mockedFs.readFile.mockResolvedValue(oldCommands.join('\n'));
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
await waitFor(() => {
@@ -284,7 +284,7 @@ describe('useShellHistory', () => {
it('should move an existing command to the top when re-added', async () => {
mockedFs.readFile.mockResolvedValue('cmd1\ncmd2\ncmd3');
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useShellHistory(MOCKED_PROJECT_ROOT),
);
diff --git a/packages/cli/src/ui/hooks/useShellInactivityStatus.test.ts b/packages/cli/src/ui/hooks/useShellInactivityStatus.test.ts
index 618091494a..74dc8e5ed1 100644
--- a/packages/cli/src/ui/hooks/useShellInactivityStatus.test.ts
+++ b/packages/cli/src/ui/hooks/useShellInactivityStatus.test.ts
@@ -39,7 +39,9 @@ describe('useShellInactivityStatus', () => {
};
it('should show action_required status after 30s when output has been produced', async () => {
- const { result } = renderHook(() => useShellInactivityStatus(defaultProps));
+ const { result } = await renderHook(() =>
+ useShellInactivityStatus(defaultProps),
+ );
expect(result.current.inactivityStatus).toBe('none');
@@ -50,7 +52,7 @@ describe('useShellInactivityStatus', () => {
});
it('should show silent_working status after 60s when no output has been produced (silent)', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useShellInactivityStatus({ ...defaultProps, lastOutputTime: 500 }),
);
@@ -71,7 +73,9 @@ describe('useShellInactivityStatus', () => {
isRedirectionActive: true,
});
- const { result } = renderHook(() => useShellInactivityStatus(defaultProps));
+ const { result } = await renderHook(() =>
+ useShellInactivityStatus(defaultProps),
+ );
// Should NOT show action_required even after 60s
await act(async () => {
@@ -92,7 +96,9 @@ describe('useShellInactivityStatus', () => {
isRedirectionActive: true,
});
- const { result } = renderHook(() => useShellInactivityStatus(defaultProps));
+ const { result } = await renderHook(() =>
+ useShellInactivityStatus(defaultProps),
+ );
// Even after delay, focus hint should be suppressed
await act(async () => {
diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
index 638172d2eb..47935c8c6a 100644
--- a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
+++ b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
@@ -6,6 +6,7 @@
import { describe, it, expect, vi } from 'vitest';
import { act, useState } from 'react';
+import type { FzfResultItem } from 'fzf';
import { renderHook } from '../../test-utils/render.js';
import { waitFor } from '../../test-utils/async.js';
import { useSlashCompletion } from './useSlashCompletion.js';
@@ -38,8 +39,26 @@ const getConstructorCallCount = () => asyncFzfConstructorCalls;
// Note: This is a simplified reimplementation that may diverge from real fzf behavior.
// Integration tests in useSlashCompletion.integration.test.ts use the real fzf library
// to catch any behavioral differences and serve as our "canary in a coal mine."
+
+let deferredMatch: { resolve: (val?: unknown) => void } | null = null;
+
+export const resolveMatch = async () => {
+ // Wait up to 1s for deferredMatch to be set by the hook
+ const start = Date.now();
+ while (!deferredMatch && Date.now() - start < 1000) {
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ }
+
+ if (deferredMatch) {
+ await act(async () => {
+ deferredMatch?.resolve(null);
+ });
+ deferredMatch = null;
+ }
+};
+
function simulateFuzzyMatching(items: readonly string[], query: string) {
- const results = [];
+ const results: Array> = [];
if (query) {
const lowerQuery = query.toLowerCase();
for (const item of items) {
@@ -98,7 +117,13 @@ function simulateFuzzyMatching(items: readonly string[], query: string) {
// Sort by score descending (better matches first)
results.sort((a, b) => b.score - a.score);
- return Promise.resolve(results);
+ return new Promise((resolve) => {
+ deferredMatch = {
+ resolve: () => {
+ resolve(results);
+ },
+ };
+ });
}
// Mock the fzf module to provide a working fuzzy search implementation for tests
@@ -199,38 +224,25 @@ describe('useSlashCompletion', () => {
}),
createTestCommand({ name: 'chat', description: 'Manage chat history' }),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/',
- slashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
- await act(async () => {
- await waitFor(() => {
- expect(result.current.suggestions.length).toBe(slashCommands.length);
- expect(result.current.suggestions.map((s) => s.label)).toEqual(
- expect.arrayContaining([
- 'help',
- 'clear',
- 'memory',
- 'chat',
- 'stats',
- ]),
- );
- });
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
+
+ await waitFor(() => {
+ expect(result.current.suggestions.length).toBe(slashCommands.length);
+ expect(result.current.suggestions.map((s) => s.label)).toEqual(
+ expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']),
+ );
});
- unmount!();
+ unmount();
});
it('should filter commands based on partial input', async () => {
@@ -241,44 +253,33 @@ describe('useSlashCompletion', () => {
const setIsLoadingSuggestions = vi.fn();
const setIsPerfectMatch = vi.fn();
- let result: {
- current: { completionStart: number; completionEnd: number };
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useSlashCompletion({
- enabled: true,
- query: '/mem',
- slashCommands,
- commandContext: mockCommandContext,
- setSuggestions,
- setIsLoadingSuggestions,
- setIsPerfectMatch,
- }),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+ const { result, unmount } = await renderHook(() =>
+ useSlashCompletion({
+ enabled: true,
+ query: '/mem',
+ slashCommands,
+ commandContext: mockCommandContext,
+ setSuggestions,
+ setIsLoadingSuggestions,
+ setIsPerfectMatch,
+ }),
+ );
- await act(async () => {
- await waitFor(() => {
- expect(setSuggestions).toHaveBeenCalledWith([
- {
- label: 'memory',
- value: 'memory',
- description: 'Manage memory',
- commandKind: CommandKind.BUILT_IN,
- },
- ]);
- expect(result.current.completionStart).toBe(1);
- expect(result.current.completionEnd).toBe(4);
- });
+ await resolveMatch();
+
+ await waitFor(() => {
+ expect(setSuggestions).toHaveBeenCalledWith([
+ {
+ label: 'memory',
+ value: 'memory',
+ description: 'Manage memory',
+ commandKind: CommandKind.BUILT_IN,
+ },
+ ]);
+ expect(result.current.completionStart).toBe(1);
+ expect(result.current.completionEnd).toBe(4);
});
- await act(async () => {
- await new Promise((resolve) => setTimeout(resolve, 50));
- });
- unmount!();
+ unmount();
});
it('should suggest commands based on partial altNames', async () => {
@@ -290,22 +291,17 @@ describe('useSlashCompletion', () => {
'check session stats. Usage: /stats [session|model|tools]',
}),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/usage',
- slashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/usage',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
await waitFor(() => {
expect(result.current.suggestions).toEqual([
@@ -319,7 +315,7 @@ describe('useSlashCompletion', () => {
]);
expect(result.current.completionStart).toBe(1);
});
- unmount!();
+ unmount();
});
it('should provide suggestions even for a perfectly typed command that is a leaf node', async () => {
@@ -330,28 +326,24 @@ describe('useSlashCompletion', () => {
action: vi.fn(),
}),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/clear',
- slashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/clear',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(1);
expect(result.current.suggestions[0].label).toBe('clear');
expect(result.current.completionStart).toBe(1);
});
- unmount!();
+ unmount();
});
it.each([['/?'], ['/usage']])(
@@ -373,28 +365,22 @@ describe('useSlashCompletion', () => {
}),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- query,
- mockSlashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ query,
+ mockSlashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(1);
expect(result.current.completionStart).toBe(1);
});
- unmount!();
+ unmount();
},
);
@@ -417,7 +403,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/review',
@@ -426,6 +412,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
// All three should match 'review' in our fuzzy mock or as prefix/exact
expect(result.current.suggestions.length).toBe(3);
@@ -472,15 +460,18 @@ describe('useSlashCompletion', () => {
}),
];
- const { result: chatResult, unmount: unmountChat } = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/chat',
- slashCommands,
- mockCommandContext,
- ),
+ const { result: chatResult, unmount: unmountChat } = await renderHook(
+ () =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/chat',
+ slashCommands,
+ mockCommandContext,
+ ),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(chatResult.current.suggestions[0]).toMatchObject({
label: 'list',
@@ -489,15 +480,18 @@ describe('useSlashCompletion', () => {
});
});
- const { result: resumeResult, unmount: unmountResume } = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/resume',
- slashCommands,
- mockCommandContext,
- ),
+ const { result: resumeResult, unmount: unmountResume } = await renderHook(
+ () =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/resume',
+ slashCommands,
+ mockCommandContext,
+ ),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(resumeResult.current.suggestions[0]).toMatchObject({
label: 'list',
@@ -540,7 +534,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/resum',
@@ -549,6 +543,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions[0]).toMatchObject({
label: 'list',
@@ -579,7 +575,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/?',
@@ -588,6 +584,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
// 'help' should be first because '?' is an exact altName match
expect(result.current.suggestions[0].label).toBe('help');
@@ -608,7 +606,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/chat',
@@ -617,6 +615,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
// Should show the auto-session entry plus subcommands of 'chat'
expect(result.current.suggestions).toHaveLength(3);
@@ -638,55 +638,45 @@ describe('useSlashCompletion', () => {
const slashCommands = [
createTestCommand({ name: 'clear', description: 'Clear the screen' }),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/clear ',
- slashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/clear ',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(0);
});
- unmount!();
+ unmount();
});
it('should not provide suggestions for an unknown command', async () => {
const slashCommands = [
createTestCommand({ name: 'help', description: 'Show help' }),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/unknown-command',
- slashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/unknown-command',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(0);
expect(result.current.completionStart).toBe(1);
});
- unmount!();
+ unmount();
});
it('should not suggest hidden commands', async () => {
@@ -701,28 +691,23 @@ describe('useSlashCompletion', () => {
hidden: true,
}),
];
- let result: {
- current: ReturnType;
- };
- let unmount: () => void;
- await act(async () => {
- const hook = renderHook(() =>
- useTestHarnessForSlashCompletion(
- true,
- '/',
- slashCommands,
- mockCommandContext,
- ),
- );
- result = hook.result;
- unmount = hook.unmount;
- });
+
+ const { result, unmount } = await renderHook(() =>
+ useTestHarnessForSlashCompletion(
+ true,
+ '/',
+ slashCommands,
+ mockCommandContext,
+ ),
+ );
+
+ await resolveMatch();
await waitFor(() => {
expect(result.current.suggestions.length).toBe(1);
expect(result.current.suggestions[0].label).toBe('visible');
});
- unmount!();
+ unmount();
});
});
@@ -739,7 +724,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/memory ',
@@ -748,6 +733,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(2);
expect(result.current.suggestions).toEqual(
@@ -785,7 +772,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/memory',
@@ -794,6 +781,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
// Should verify that we see BOTH 'memory' and 'memory-leak'
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(2);
@@ -827,7 +816,7 @@ describe('useSlashCompletion', () => {
],
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/memory ',
@@ -836,6 +825,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toHaveLength(2);
expect(result.current.suggestions).toEqual(
@@ -869,7 +860,7 @@ describe('useSlashCompletion', () => {
],
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/memory a',
@@ -878,6 +869,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toEqual([
{
@@ -903,7 +896,7 @@ describe('useSlashCompletion', () => {
],
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/memory dothisnow',
@@ -911,11 +904,12 @@ describe('useSlashCompletion', () => {
mockCommandContext,
),
);
- await act(async () => {
- await waitFor(() => {
- expect(result.current.suggestions).toHaveLength(0);
- expect(result.current.completionStart).toBe(8);
- });
+
+ await resolveMatch();
+
+ await waitFor(() => {
+ expect(result.current.suggestions).toHaveLength(0);
+ expect(result.current.completionStart).toBe(8);
});
unmount();
});
@@ -928,12 +922,18 @@ describe('useSlashCompletion', () => {
'my-chat-tag-2',
'another-channel',
];
- const mockCompletionFn = vi
- .fn()
- .mockImplementation(
- async (_context: CommandContext, partialArg: string) =>
- availableTags.filter((tag) => tag.startsWith(partialArg)),
- );
+ let deferredCompletion: { resolve: (v: string[]) => void } | null = null;
+ const mockCompletionFn = vi.fn().mockImplementation(
+ (_context: CommandContext, partialArg: string) =>
+ new Promise((resolve) => {
+ deferredCompletion = {
+ resolve: () =>
+ resolve(
+ availableTags.filter((tag) => tag.startsWith(partialArg)),
+ ),
+ };
+ }),
+ );
const slashCommands = [
createTestCommand({
@@ -949,7 +949,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/chat resume my-ch',
@@ -958,38 +958,45 @@ describe('useSlashCompletion', () => {
),
);
- await act(async () => {
- await waitFor(() => {
- expect(mockCompletionFn).toHaveBeenCalledWith(
- expect.objectContaining({
- invocation: {
- raw: '/chat resume my-ch',
- name: 'resume',
- args: 'my-ch',
- },
- }),
- 'my-ch',
- );
- });
+ await waitFor(() => {
+ expect(mockCompletionFn).toHaveBeenCalledWith(
+ expect.objectContaining({
+ invocation: {
+ raw: '/chat resume my-ch',
+ name: 'resume',
+ args: 'my-ch',
+ },
+ }),
+ 'my-ch',
+ );
});
await act(async () => {
- await waitFor(() => {
- expect(result.current.suggestions).toEqual([
- { label: 'my-chat-tag-1', value: 'my-chat-tag-1' },
- { label: 'my-chat-tag-2', value: 'my-chat-tag-2' },
- ]);
- expect(result.current.completionStart).toBe(13);
- expect(result.current.isLoadingSuggestions).toBe(false);
- });
+ deferredCompletion?.resolve([]);
+ });
+
+ await waitFor(() => {
+ expect(result.current.suggestions).toEqual([
+ { label: 'my-chat-tag-1', value: 'my-chat-tag-1' },
+ { label: 'my-chat-tag-2', value: 'my-chat-tag-2' },
+ ]);
+ expect(result.current.completionStart).toBe(13);
+ expect(result.current.isLoadingSuggestions).toBe(false);
});
unmount();
});
it('should call command.completion with an empty string when args start with a space', async () => {
- const mockCompletionFn = vi
- .fn()
- .mockResolvedValue(['my-chat-tag-1', 'my-chat-tag-2', 'my-channel']);
+ let deferredCompletion: { resolve: (v: string[]) => void } | null = null;
+ const mockCompletionFn = vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredCompletion = {
+ resolve: () =>
+ resolve(['my-chat-tag-1', 'my-chat-tag-2', 'my-channel']),
+ };
+ }),
+ );
const slashCommands = [
createTestCommand({
@@ -1005,7 +1012,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/chat resume ',
@@ -1014,32 +1021,38 @@ describe('useSlashCompletion', () => {
),
);
- await act(async () => {
- await waitFor(() => {
- expect(mockCompletionFn).toHaveBeenCalledWith(
- expect.objectContaining({
- invocation: {
- raw: '/chat resume ',
- name: 'resume',
- args: '',
- },
- }),
- '',
- );
- });
+ await waitFor(() => {
+ expect(mockCompletionFn).toHaveBeenCalledWith(
+ expect.objectContaining({
+ invocation: {
+ raw: '/chat resume ',
+ name: 'resume',
+ args: '',
+ },
+ }),
+ '',
+ );
});
await act(async () => {
- await waitFor(() => {
- expect(result.current.suggestions).toHaveLength(3);
- expect(result.current.completionStart).toBe(13);
- });
+ deferredCompletion?.resolve([]);
+ });
+
+ await waitFor(() => {
+ expect(result.current.suggestions).toHaveLength(3);
+ expect(result.current.completionStart).toBe(13);
});
unmount();
});
it('should handle completion function that returns null', async () => {
- const mockCompletionFn = vi.fn().mockResolvedValue(null);
+ let deferredCompletion: { resolve: (v: null) => void } | null = null;
+ const mockCompletionFn = vi.fn().mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ deferredCompletion = { resolve: () => resolve(null) };
+ }),
+ );
const slashCommands = [
createTestCommand({
@@ -1049,7 +1062,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/test arg',
@@ -1058,6 +1071,10 @@ describe('useSlashCompletion', () => {
),
);
+ await act(async () => {
+ deferredCompletion?.resolve(null);
+ });
+
await waitFor(() => {
expect(result.current.suggestions).toEqual([]);
expect(result.current.isLoadingSuggestions).toBe(false);
@@ -1083,7 +1100,7 @@ describe('useSlashCompletion', () => {
},
] as SlashCommand[];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/',
@@ -1092,6 +1109,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toEqual(
expect.arrayContaining([
@@ -1129,7 +1148,7 @@ describe('useSlashCompletion', () => {
},
] as SlashCommand[];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/summ',
@@ -1138,6 +1157,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toEqual([
{
@@ -1175,7 +1196,7 @@ describe('useSlashCompletion', () => {
},
] as SlashCommand[];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/memory ',
@@ -1184,6 +1205,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toEqual(
expect.arrayContaining([
@@ -1215,7 +1238,7 @@ describe('useSlashCompletion', () => {
},
] as SlashCommand[];
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useTestHarnessForSlashCompletion(
true,
'/custom',
@@ -1224,6 +1247,8 @@ describe('useSlashCompletion', () => {
),
);
+ await resolveMatch();
+
await waitFor(() => {
expect(result.current.suggestions).toEqual([
{
@@ -1251,7 +1276,7 @@ describe('useSlashCompletion', () => {
}),
];
- const { rerender, unmount } = renderHook(
+ const { rerender, unmount } = await renderHook(
({ enabled, query }) =>
useSlashCompletion({
enabled,
diff --git a/packages/cli/src/ui/hooks/useSuspend.test.ts b/packages/cli/src/ui/hooks/useSuspend.test.ts
index 941bfd44b9..7e4d8808d3 100644
--- a/packages/cli/src/ui/hooks/useSuspend.test.ts
+++ b/packages/cli/src/ui/hooks/useSuspend.test.ts
@@ -80,7 +80,7 @@ describe('useSuspend', () => {
setPlatform(originalPlatform);
});
- it('cleans terminal state on suspend and restores/repaints on resume in alternate screen mode', () => {
+ it('cleans terminal state on suspend and restores/repaints on resume in alternate screen mode', async () => {
const handleWarning = vi.fn();
const setRawMode = vi.fn();
const refreshStatic = vi.fn();
@@ -88,7 +88,7 @@ describe('useSuspend', () => {
const enableSupportedModes =
terminalCapabilityManager.enableSupportedModes as unknown as Mock;
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useSuspend({
handleWarning,
setRawMode,
@@ -137,13 +137,13 @@ describe('useSuspend', () => {
unmount();
});
- it('does not toggle alternate screen or mouse restore when alternate screen mode is disabled', () => {
+ it('does not toggle alternate screen or mouse restore when alternate screen mode is disabled', async () => {
const handleWarning = vi.fn();
const setRawMode = vi.fn();
const refreshStatic = vi.fn();
const setForceRerenderKey = vi.fn();
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useSuspend({
handleWarning,
setRawMode,
@@ -169,7 +169,7 @@ describe('useSuspend', () => {
unmount();
});
- it('warns and skips suspension on windows', () => {
+ it('warns and skips suspension on windows', async () => {
setPlatform('win32');
const handleWarning = vi.fn();
@@ -177,7 +177,7 @@ describe('useSuspend', () => {
const refreshStatic = vi.fn();
const setForceRerenderKey = vi.fn();
- const { result, unmount } = renderHook(() =>
+ const { result, unmount } = await renderHook(() =>
useSuspend({
handleWarning,
setRawMode,
diff --git a/packages/cli/src/ui/hooks/useTabbedNavigation.test.ts b/packages/cli/src/ui/hooks/useTabbedNavigation.test.ts
index 20e1c13fb8..e8c346ad31 100644
--- a/packages/cli/src/ui/hooks/useTabbedNavigation.test.ts
+++ b/packages/cli/src/ui/hooks/useTabbedNavigation.test.ts
@@ -40,8 +40,8 @@ describe('useTabbedNavigation', () => {
});
describe('keyboard navigation', () => {
- it('moves to next tab on Right arrow', () => {
- const { result } = renderHook(() =>
+ it('moves to next tab on Right arrow', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, enableArrowNavigation: true }),
);
@@ -52,8 +52,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(1);
});
- it('moves to previous tab on Left arrow', () => {
- const { result } = renderHook(() =>
+ it('moves to previous tab on Left arrow', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({
tabCount: 3,
initialIndex: 1,
@@ -68,8 +68,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(0);
});
- it('moves to next tab on Tab key', () => {
- const { result } = renderHook(() =>
+ it('moves to next tab on Tab key', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, enableTabKey: true }),
);
@@ -80,8 +80,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(1);
});
- it('moves to previous tab on Shift+Tab key', () => {
- const { result } = renderHook(() =>
+ it('moves to previous tab on Shift+Tab key', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({
tabCount: 3,
initialIndex: 1,
@@ -96,8 +96,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(0);
});
- it('does not navigate when isNavigationBlocked returns true', () => {
- const { result } = renderHook(() =>
+ it('does not navigate when isNavigationBlocked returns true', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({
tabCount: 3,
enableArrowNavigation: true,
@@ -114,25 +114,27 @@ describe('useTabbedNavigation', () => {
});
describe('initialization', () => {
- it('returns initial index of 0 by default', () => {
- const { result } = renderHook(() => useTabbedNavigation({ tabCount: 3 }));
+ it('returns initial index of 0 by default', async () => {
+ const { result } = await renderHook(() =>
+ useTabbedNavigation({ tabCount: 3 }),
+ );
expect(result.current.currentIndex).toBe(0);
});
- it('returns specified initial index', () => {
- const { result } = renderHook(() =>
+ it('returns specified initial index', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 2 }),
);
expect(result.current.currentIndex).toBe(2);
});
- it('clamps initial index to valid range', () => {
- const { result: high } = renderHook(() =>
+ it('clamps initial index to valid range', async () => {
+ const { result: high } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 10 }),
);
expect(high.current.currentIndex).toBe(2);
- const { result: negative } = renderHook(() =>
+ const { result: negative } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: -1 }),
);
expect(negative.current.currentIndex).toBe(0);
@@ -140,8 +142,10 @@ describe('useTabbedNavigation', () => {
});
describe('goToNextTab', () => {
- it('advances to next tab', () => {
- const { result } = renderHook(() => useTabbedNavigation({ tabCount: 3 }));
+ it('advances to next tab', async () => {
+ const { result } = await renderHook(() =>
+ useTabbedNavigation({ tabCount: 3 }),
+ );
act(() => {
result.current.goToNextTab();
@@ -150,8 +154,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(1);
});
- it('stops at last tab when wrapAround is false', () => {
- const { result } = renderHook(() =>
+ it('stops at last tab when wrapAround is false', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({
tabCount: 3,
initialIndex: 2,
@@ -166,8 +170,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(2);
});
- it('wraps to first tab when wrapAround is true', () => {
- const { result } = renderHook(() =>
+ it('wraps to first tab when wrapAround is true', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 2, wrapAround: true }),
);
@@ -180,8 +184,8 @@ describe('useTabbedNavigation', () => {
});
describe('goToPrevTab', () => {
- it('moves to previous tab', () => {
- const { result } = renderHook(() =>
+ it('moves to previous tab', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 2 }),
);
@@ -192,8 +196,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(1);
});
- it('stops at first tab when wrapAround is false', () => {
- const { result } = renderHook(() =>
+ it('stops at first tab when wrapAround is false', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({
tabCount: 3,
initialIndex: 0,
@@ -208,8 +212,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(0);
});
- it('wraps to last tab when wrapAround is true', () => {
- const { result } = renderHook(() =>
+ it('wraps to last tab when wrapAround is true', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 0, wrapAround: true }),
);
@@ -222,8 +226,10 @@ describe('useTabbedNavigation', () => {
});
describe('setCurrentIndex', () => {
- it('sets index directly', () => {
- const { result } = renderHook(() => useTabbedNavigation({ tabCount: 3 }));
+ it('sets index directly', async () => {
+ const { result } = await renderHook(() =>
+ useTabbedNavigation({ tabCount: 3 }),
+ );
act(() => {
result.current.setCurrentIndex(2);
@@ -232,8 +238,8 @@ describe('useTabbedNavigation', () => {
expect(result.current.currentIndex).toBe(2);
});
- it('ignores out-of-bounds index', () => {
- const { result } = renderHook(() =>
+ it('ignores out-of-bounds index', async () => {
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 1 }),
);
@@ -250,9 +256,9 @@ describe('useTabbedNavigation', () => {
});
describe('isNavigationBlocked', () => {
- it('blocks navigation when callback returns true', () => {
+ it('blocks navigation when callback returns true', async () => {
const isNavigationBlocked = vi.fn(() => true);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, isNavigationBlocked }),
);
@@ -264,9 +270,9 @@ describe('useTabbedNavigation', () => {
expect(isNavigationBlocked).toHaveBeenCalled();
});
- it('allows navigation when callback returns false', () => {
+ it('allows navigation when callback returns false', async () => {
const isNavigationBlocked = vi.fn(() => false);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, isNavigationBlocked }),
);
@@ -279,9 +285,9 @@ describe('useTabbedNavigation', () => {
});
describe('onTabChange callback', () => {
- it('calls onTabChange when tab changes via goToNextTab', () => {
+ it('calls onTabChange when tab changes via goToNextTab', async () => {
const onTabChange = vi.fn();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, onTabChange }),
);
@@ -292,9 +298,9 @@ describe('useTabbedNavigation', () => {
expect(onTabChange).toHaveBeenCalledWith(1);
});
- it('calls onTabChange when tab changes via setCurrentIndex', () => {
+ it('calls onTabChange when tab changes via setCurrentIndex', async () => {
const onTabChange = vi.fn();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, onTabChange }),
);
@@ -305,9 +311,9 @@ describe('useTabbedNavigation', () => {
expect(onTabChange).toHaveBeenCalledWith(2);
});
- it('does not call onTabChange when tab does not change', () => {
+ it('does not call onTabChange when tab does not change', async () => {
const onTabChange = vi.fn();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, onTabChange }),
);
@@ -320,20 +326,20 @@ describe('useTabbedNavigation', () => {
});
describe('isFirstTab and isLastTab', () => {
- it('returns correct boundary flags based on position', () => {
- const { result: first } = renderHook(() =>
+ it('returns correct boundary flags based on position', async () => {
+ const { result: first } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 0 }),
);
expect(first.current.isFirstTab).toBe(true);
expect(first.current.isLastTab).toBe(false);
- const { result: last } = renderHook(() =>
+ const { result: last } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 2 }),
);
expect(last.current.isFirstTab).toBe(false);
expect(last.current.isLastTab).toBe(true);
- const { result: middle } = renderHook(() =>
+ const { result: middle } = await renderHook(() =>
useTabbedNavigation({ tabCount: 3, initialIndex: 1 }),
);
expect(middle.current.isFirstTab).toBe(false);
@@ -342,9 +348,9 @@ describe('useTabbedNavigation', () => {
});
describe('tabCount changes', () => {
- it('reinitializes when tabCount changes', () => {
+ it('reinitializes when tabCount changes', async () => {
let tabCount = 5;
- const { result, rerender } = renderHook(() =>
+ const { result, rerender } = await renderHook(() =>
useTabbedNavigation({ tabCount, initialIndex: 4 }),
);
diff --git a/packages/cli/src/ui/hooks/useTerminalTheme.test.tsx b/packages/cli/src/ui/hooks/useTerminalTheme.test.tsx
index 31df95495c..7dcd35f1a6 100644
--- a/packages/cli/src/ui/hooks/useTerminalTheme.test.tsx
+++ b/packages/cli/src/ui/hooks/useTerminalTheme.test.tsx
@@ -95,8 +95,8 @@ describe('useTerminalTheme', () => {
vi.restoreAllMocks();
});
- it('should subscribe to terminal background events on mount', () => {
- const { unmount } = renderHook(() =>
+ it('should subscribe to terminal background events on mount', async () => {
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, vi.fn()),
);
expect(mockSubscribe).toHaveBeenCalled();
@@ -104,16 +104,15 @@ describe('useTerminalTheme', () => {
});
it('should unsubscribe on unmount', async () => {
- const { unmount, waitUntilReady } = renderHook(() =>
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, vi.fn()),
);
- await waitUntilReady();
unmount();
expect(mockUnsubscribe).toHaveBeenCalled();
});
- it('should poll for terminal background', () => {
- const { unmount } = renderHook(() =>
+ it('should poll for terminal background', async () => {
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, vi.fn()),
);
@@ -124,7 +123,7 @@ describe('useTerminalTheme', () => {
it('should not poll if terminal background is undefined at startup', async () => {
config.getTerminalBackground = vi.fn().mockReturnValue(undefined);
- const { unmount } = renderHook(() =>
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, vi.fn()),
);
@@ -133,9 +132,9 @@ describe('useTerminalTheme', () => {
unmount();
});
- it('should switch to light theme when background is light and not call refreshStatic directly', () => {
+ it('should switch to light theme when background is light and not call refreshStatic directly', async () => {
const refreshStatic = vi.fn();
- const { unmount } = renderHook(() =>
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, refreshStatic),
);
@@ -153,13 +152,13 @@ describe('useTerminalTheme', () => {
unmount();
});
- it('should switch to dark theme when background is dark', () => {
+ it('should switch to dark theme when background is dark', async () => {
mockSettings.merged.ui.theme = 'default-light';
config.setTerminalBackground('#ffffff');
const refreshStatic = vi.fn();
- const { unmount } = renderHook(() =>
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, refreshStatic),
);
@@ -179,9 +178,9 @@ describe('useTerminalTheme', () => {
unmount();
});
- it('should not update config or call refreshStatic on repeated identical background reports', () => {
+ it('should not update config or call refreshStatic on repeated identical background reports', async () => {
const refreshStatic = vi.fn();
- renderHook(() =>
+ await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, refreshStatic),
);
@@ -196,7 +195,7 @@ describe('useTerminalTheme', () => {
expect(mockHandleThemeSelect).not.toHaveBeenCalled();
});
- it('should switch theme even if terminal background report is identical to previousColor if current theme is mismatched', () => {
+ it('should switch theme even if terminal background report is identical to previousColor if current theme is mismatched', async () => {
// Background is dark at startup
config.setTerminalBackground('#000000');
vi.mocked(config.setTerminalBackground).mockClear();
@@ -204,7 +203,7 @@ describe('useTerminalTheme', () => {
mockSettings.merged.ui.theme = 'default-light';
const refreshStatic = vi.fn();
- const { unmount } = renderHook(() =>
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, refreshStatic),
);
@@ -226,9 +225,9 @@ describe('useTerminalTheme', () => {
unmount();
});
- it('should not switch theme if autoThemeSwitching is disabled', () => {
+ it('should not switch theme if autoThemeSwitching is disabled', async () => {
mockSettings.merged.ui.autoThemeSwitching = false;
- const { unmount } = renderHook(() =>
+ const { unmount } = await renderHook(() =>
useTerminalTheme(mockHandleThemeSelect, config, vi.fn()),
);
diff --git a/packages/cli/src/ui/hooks/useTimer.test.tsx b/packages/cli/src/ui/hooks/useTimer.test.tsx
index e8ebad7aec..15cc12477f 100644
--- a/packages/cli/src/ui/hooks/useTimer.test.tsx
+++ b/packages/cli/src/ui/hooks/useTimer.test.tsx
@@ -18,7 +18,7 @@ describe('useTimer', () => {
vi.restoreAllMocks();
});
- const renderTimerHook = (
+ const renderTimerHook = async (
initialIsActive: boolean,
initialResetKey: number,
) => {
@@ -33,7 +33,7 @@ describe('useTimer', () => {
hookResult = useTimer(isActive, resetKey);
return null;
}
- const { rerender, unmount } = render(
+ const { rerender, unmount } = await render(
,
);
return {
@@ -48,21 +48,21 @@ describe('useTimer', () => {
};
};
- it('should initialize with 0', () => {
- const { result } = renderTimerHook(false, 0);
+ it('should initialize with 0', async () => {
+ const { result } = await renderTimerHook(false, 0);
expect(result.current).toBe(0);
});
- it('should not increment time if isActive is false', () => {
- const { result } = renderTimerHook(false, 0);
+ it('should not increment time if isActive is false', async () => {
+ const { result } = await renderTimerHook(false, 0);
act(() => {
vi.advanceTimersByTime(5000);
});
expect(result.current).toBe(0);
});
- it('should increment time every second if isActive is true', () => {
- const { result } = renderTimerHook(true, 0);
+ it('should increment time every second if isActive is true', async () => {
+ const { result } = await renderTimerHook(true, 0);
act(() => {
vi.advanceTimersByTime(1000);
});
@@ -73,8 +73,8 @@ describe('useTimer', () => {
expect(result.current).toBe(3);
});
- it('should reset to 0 and start incrementing when isActive becomes true from false', () => {
- const { result, rerender } = renderTimerHook(false, 0);
+ it('should reset to 0 and start incrementing when isActive becomes true from false', async () => {
+ const { result, rerender } = await renderTimerHook(false, 0);
expect(result.current).toBe(0);
act(() => {
@@ -88,8 +88,8 @@ describe('useTimer', () => {
expect(result.current).toBe(1);
});
- it('should reset to 0 when resetKey changes while active', () => {
- const { result, rerender } = renderTimerHook(true, 0);
+ it('should reset to 0 when resetKey changes while active', async () => {
+ const { result, rerender } = await renderTimerHook(true, 0);
act(() => {
vi.advanceTimersByTime(3000); // 3s
});
@@ -106,8 +106,8 @@ describe('useTimer', () => {
expect(result.current).toBe(1); // Starts incrementing from 0
});
- it('should be 0 if isActive is false, regardless of resetKey changes', () => {
- const { result, rerender } = renderTimerHook(false, 0);
+ it('should be 0 if isActive is false, regardless of resetKey changes', async () => {
+ const { result, rerender } = await renderTimerHook(false, 0);
expect(result.current).toBe(0);
act(() => {
@@ -116,15 +116,15 @@ describe('useTimer', () => {
expect(result.current).toBe(0);
});
- it('should clear timer on unmount', () => {
- const { unmount } = renderTimerHook(true, 0);
+ it('should clear timer on unmount', async () => {
+ const { unmount } = await renderTimerHook(true, 0);
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
unmount();
expect(clearIntervalSpy).toHaveBeenCalledOnce();
});
- it('should preserve elapsedTime when isActive becomes false, and reset to 0 when it becomes active again', () => {
- const { result, rerender } = renderTimerHook(true, 0);
+ it('should preserve elapsedTime when isActive becomes false, and reset to 0 when it becomes active again', async () => {
+ const { result, rerender } = await renderTimerHook(true, 0);
act(() => {
vi.advanceTimersByTime(3000); // Advance to 3 seconds
diff --git a/packages/cli/src/ui/hooks/useToolScheduler.test.ts b/packages/cli/src/ui/hooks/useToolScheduler.test.ts
index 0d010f25fa..cc7216281b 100644
--- a/packages/cli/src/ui/hooks/useToolScheduler.test.ts
+++ b/packages/cli/src/ui/hooks/useToolScheduler.test.ts
@@ -80,8 +80,8 @@ describe('useToolScheduler', () => {
vi.clearAllMocks();
});
- it('initializes with empty tool calls', () => {
- const { result } = renderHook(() =>
+ it('initializes with empty tool calls', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -92,8 +92,8 @@ describe('useToolScheduler', () => {
expect(toolCalls).toEqual([]);
});
- it('updates tool calls when MessageBus emits TOOL_CALLS_UPDATE', () => {
- const { result } = renderHook(() =>
+ it('updates tool calls when MessageBus emits TOOL_CALLS_UPDATE', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -134,8 +134,8 @@ describe('useToolScheduler', () => {
});
});
- it('preserves responseSubmittedToGemini flag across updates', () => {
- const { result } = renderHook(() =>
+ it('preserves responseSubmittedToGemini flag across updates', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -192,9 +192,9 @@ describe('useToolScheduler', () => {
expect(result.current[0][0].responseSubmittedToGemini).toBe(true);
});
- it('updates lastToolOutputTime when tools are executing', () => {
+ it('updates lastToolOutputTime when tools are executing', async () => {
vi.useFakeTimers();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -231,8 +231,8 @@ describe('useToolScheduler', () => {
vi.useRealTimers();
});
- it('delegates cancelAll to the Core Scheduler', () => {
- const { result } = renderHook(() =>
+ it('delegates cancelAll to the Core Scheduler', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -285,7 +285,7 @@ describe('useToolScheduler', () => {
}) as unknown as Scheduler,
);
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useToolScheduler(onComplete, mockConfig, () => undefined),
);
@@ -310,8 +310,8 @@ describe('useToolScheduler', () => {
expect(onComplete).toHaveBeenCalledWith([completedToolCall]);
});
- it('setToolCallsForDisplay re-groups tools by schedulerId (Multi-Scheduler support)', () => {
- const { result } = renderHook(() =>
+ it('setToolCallsForDisplay re-groups tools by schedulerId (Multi-Scheduler support)', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -386,8 +386,8 @@ describe('useToolScheduler', () => {
expect(toolCalls2.every((t) => t.responseSubmittedToGemini)).toBe(true);
});
- it('ignores TOOL_CALLS_UPDATE from non-root schedulers when no tools await approval', () => {
- const { result } = renderHook(() =>
+ it('ignores TOOL_CALLS_UPDATE from non-root schedulers when no tools await approval', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -420,8 +420,8 @@ describe('useToolScheduler', () => {
expect(result.current[0]).toHaveLength(0);
});
- it('allows TOOL_CALLS_UPDATE from non-root schedulers when tools are awaiting approval', () => {
- const { result } = renderHook(() =>
+ it('allows TOOL_CALLS_UPDATE from non-root schedulers when tools are awaiting approval', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -458,8 +458,8 @@ describe('useToolScheduler', () => {
expect(toolCalls[0].status).toBe(CoreToolCallStatus.AwaitingApproval);
});
- it('preserves subagent tools in the UI after they have been approved', () => {
- const { result } = renderHook(() =>
+ it('preserves subagent tools in the UI after they have been approved', async () => {
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
@@ -538,9 +538,9 @@ describe('useToolScheduler', () => {
expect(result.current[0]).toHaveLength(0);
});
- it('adapts success/error status to executing when a tail call is present', () => {
+ it('adapts success/error status to executing when a tail call is present', async () => {
vi.useFakeTimers();
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useToolScheduler(
vi.fn().mockResolvedValue(undefined),
mockConfig,
diff --git a/packages/cli/src/ui/hooks/useTurnActivityMonitor.test.ts b/packages/cli/src/ui/hooks/useTurnActivityMonitor.test.ts
index f3791d1b32..e01b74730e 100644
--- a/packages/cli/src/ui/hooks/useTurnActivityMonitor.test.ts
+++ b/packages/cli/src/ui/hooks/useTurnActivityMonitor.test.ts
@@ -33,22 +33,25 @@ describe('useTurnActivityMonitor', () => {
vi.useRealTimers();
});
- it('should set operationStartTime when entering Responding state', () => {
- const { result, rerender } = renderHook(
+ it('should set operationStartTime when entering Responding state', async () => {
+ const { result, rerender } = await renderHook(
({ state }) => useTurnActivityMonitor(state, null, []),
{
initialProps: { state: StreamingState.Idle },
},
);
+ // Reset time to 1000 to counter the 50ms advanced by renderHook's wait
+ vi.setSystemTime(1000);
+
expect(result.current.operationStartTime).toBe(0);
rerender({ state: StreamingState.Responding });
expect(result.current.operationStartTime).toBe(1000);
});
- it('should reset operationStartTime when PTY ID changes while responding', () => {
- const { result, rerender } = renderHook(
+ it('should reset operationStartTime when PTY ID changes while responding', async () => {
+ const { result, rerender } = await renderHook(
({ state, ptyId }) => useTurnActivityMonitor(state, ptyId, []),
{
initialProps: {
@@ -65,13 +68,13 @@ describe('useTurnActivityMonitor', () => {
expect(result.current.operationStartTime).toBe(2000);
});
- it('should detect redirection from tool calls', () => {
+ it('should detect redirection from tool calls', async () => {
// Force mock implementation to ensure it's active
vi.mocked(hasRedirection).mockImplementation((q: string) =>
q.includes('>'),
);
- const { result, rerender } = renderHook(
+ const { result, rerender } = await renderHook(
({ state, pendingToolCalls }) =>
useTurnActivityMonitor(state, null, pendingToolCalls),
{
@@ -115,8 +118,8 @@ describe('useTurnActivityMonitor', () => {
expect(result.current.isRedirectionActive).toBe(true);
});
- it('should reset everything when idle', () => {
- const { result, rerender } = renderHook(
+ it('should reset everything when idle', async () => {
+ const { result, rerender } = await renderHook(
({ state }) => useTurnActivityMonitor(state, 'pty-1', []),
{
initialProps: { state: StreamingState.Responding },
diff --git a/packages/cli/src/ui/hooks/vim-passthrough.test.tsx b/packages/cli/src/ui/hooks/vim-passthrough.test.tsx
index 17a4bd5b74..c02b4b2823 100644
--- a/packages/cli/src/ui/hooks/vim-passthrough.test.tsx
+++ b/packages/cli/src/ui/hooks/vim-passthrough.test.tsx
@@ -70,9 +70,9 @@ describe('useVim passthrough', () => {
name: 'Ctrl-X',
key: createKey({ name: 'x', ctrl: true, sequence: '\x18' }),
},
- ])('should pass through $name in $mode mode', ({ mode, key }) => {
+ ])('should pass through $name in $mode mode', async ({ mode, key }) => {
mockVimContext.vimMode = mode;
- const { result } = renderHook(() => useVim(mockBuffer as TextBuffer));
+ const { result } = await renderHook(() => useVim(mockBuffer as TextBuffer));
let handled = true;
act(() => {
diff --git a/packages/cli/src/ui/hooks/vim.test.tsx b/packages/cli/src/ui/hooks/vim.test.tsx
index 8dad827dad..93e140db18 100644
--- a/packages/cli/src/ui/hooks/vim.test.tsx
+++ b/packages/cli/src/ui/hooks/vim.test.tsx
@@ -103,7 +103,7 @@ const TEST_SEQUENCES = {
F12: createKey({ sequence: '\u001b[24~', name: 'f12' }),
} as const;
-describe('useVim hook', () => {
+describe('useVim hook', async () => {
let mockBuffer: Partial;
let mockHandleFinalSubmit: Mock;
@@ -221,7 +221,7 @@ describe('useVim hook', () => {
};
};
- const renderVimHook = (buffer?: Partial) =>
+ const renderVimHook = async (buffer?: Partial) =>
renderHook(() =>
useVim((buffer || mockBuffer) as TextBuffer, mockHandleFinalSubmit),
);
@@ -247,14 +247,14 @@ describe('useVim hook', () => {
mockVimContext.setVimMode.mockClear();
});
- describe('Mode switching', () => {
- it('should start in INSERT mode', () => {
- const { result } = renderVimHook();
+ describe('Mode switching', async () => {
+ it('should start in INSERT mode', async () => {
+ const { result } = await renderVimHook();
expect(result.current.mode).toBe('INSERT');
});
- it('should switch to INSERT mode with i command', () => {
- const { result } = renderVimHook();
+ it('should switch to INSERT mode with i command', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
expect(result.current.mode).toBe('NORMAL');
@@ -267,8 +267,8 @@ describe('useVim hook', () => {
expect(mockVimContext.setVimMode).toHaveBeenCalledWith('INSERT');
});
- it('should switch back to NORMAL mode with Escape', () => {
- const { result } = renderVimHook();
+ it('should switch back to NORMAL mode with Escape', async () => {
+ const { result } = await renderVimHook();
act(() => {
result.current.handleInput(TEST_SEQUENCES.INSERT);
@@ -279,9 +279,9 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('NORMAL');
});
- it('should properly handle escape followed immediately by a command', () => {
+ it('should properly handle escape followed immediately by a command', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 6]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
act(() => {
result.current.handleInput(createKey({ sequence: 'i' }));
@@ -301,9 +301,9 @@ describe('useVim hook', () => {
});
});
- describe('Navigation commands', () => {
- it('should handle h (left movement)', () => {
- const { result } = renderVimHook();
+ describe('Navigation commands', async () => {
+ it('should handle h (left movement)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -313,8 +313,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimMoveLeft).toHaveBeenCalledWith(1);
});
- it('should handle l (right movement)', () => {
- const { result } = renderVimHook();
+ it('should handle l (right movement)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -324,9 +324,9 @@ describe('useVim hook', () => {
expect(mockBuffer.vimMoveRight).toHaveBeenCalledWith(1);
});
- it('should handle j (down movement)', () => {
+ it('should handle j (down movement)', async () => {
const testBuffer = createMockBuffer('first line\nsecond line');
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -336,9 +336,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveDown).toHaveBeenCalledWith(1);
});
- it('should handle k (up movement)', () => {
+ it('should handle k (up movement)', async () => {
const testBuffer = createMockBuffer('first line\nsecond line');
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -348,8 +348,8 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveUp).toHaveBeenCalledWith(1);
});
- it('should handle 0 (move to start of line)', () => {
- const { result } = renderVimHook();
+ it('should handle 0 (move to start of line)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -359,8 +359,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimMoveToLineStart).toHaveBeenCalled();
});
- it('should handle $ (move to end of line)', () => {
- const { result } = renderVimHook();
+ it('should handle $ (move to end of line)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -371,9 +371,9 @@ describe('useVim hook', () => {
});
});
- describe('Mode switching commands', () => {
- it('should handle a (append after cursor)', () => {
- const { result } = renderVimHook();
+ describe('Mode switching commands', async () => {
+ it('should handle a (append after cursor)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -384,8 +384,8 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should handle A (append at end of line)', () => {
- const { result } = renderVimHook();
+ it('should handle A (append at end of line)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -396,8 +396,8 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should handle o (open line below)', () => {
- const { result } = renderVimHook();
+ it('should handle o (open line below)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -408,8 +408,8 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should handle O (open line above)', () => {
- const { result } = renderVimHook();
+ it('should handle O (open line above)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -421,9 +421,9 @@ describe('useVim hook', () => {
});
});
- describe('Edit commands', () => {
- it('should handle x (delete character)', () => {
- const { result } = renderVimHook();
+ describe('Edit commands', async () => {
+ it('should handle x (delete character)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
vi.clearAllMocks();
@@ -434,9 +434,9 @@ describe('useVim hook', () => {
expect(mockBuffer.vimDeleteChar).toHaveBeenCalledWith(1);
});
- it('should move cursor left when deleting last character on line (vim behavior)', () => {
+ it('should move cursor left when deleting last character on line (vim behavior)', async () => {
const testBuffer = createMockBuffer('hello', [0, 4]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -446,8 +446,8 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1);
});
- it('should handle first d key (sets pending state)', () => {
- const { result } = renderVimHook();
+ it('should handle first d key (sets pending state)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -458,9 +458,9 @@ describe('useVim hook', () => {
});
});
- describe('Count handling', () => {
- it('should handle count input and return to count 0 after command', () => {
- const { result } = renderVimHook();
+ describe('Count handling', async () => {
+ it('should handle count input and return to count 0 after command', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -480,9 +480,9 @@ describe('useVim hook', () => {
expect(mockBuffer.vimMoveLeft).toHaveBeenCalledWith(3);
});
- it('should only delete 1 character with x command when no count is specified', () => {
+ it('should only delete 1 character with x command when no count is specified', async () => {
const testBuffer = createMockBuffer();
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -493,22 +493,22 @@ describe('useVim hook', () => {
});
});
- describe('Word movement', () => {
- it('should properly initialize vim hook with word movement support', () => {
+ describe('Word movement', async () => {
+ it('should properly initialize vim hook with word movement support', async () => {
const testBuffer = createMockBuffer('cat elephant mouse', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
expect(result.current.vimModeEnabled).toBe(true);
expect(result.current.mode).toBe('INSERT');
expect(result.current.handleInput).toBeDefined();
});
- it('should support vim mode and basic operations across multiple lines', () => {
+ it('should support vim mode and basic operations across multiple lines', async () => {
const testBuffer = createMockBuffer(
'first line word\nsecond line word',
[0, 11],
);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
expect(result.current.vimModeEnabled).toBe(true);
expect(result.current.mode).toBe('INSERT');
@@ -517,9 +517,9 @@ describe('useVim hook', () => {
expect(testBuffer.moveToOffset).toBeDefined();
});
- it('should handle w (next word)', () => {
+ it('should handle w (next word)', async () => {
const testBuffer = createMockBuffer('hello world test');
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -529,9 +529,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveWordForward).toHaveBeenCalledWith(1);
});
- it('should handle b (previous word)', () => {
+ it('should handle b (previous word)', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 6]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -541,9 +541,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveWordBackward).toHaveBeenCalledWith(1);
});
- it('should handle e (end of word)', () => {
+ it('should handle e (end of word)', async () => {
const testBuffer = createMockBuffer('hello world test');
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -553,9 +553,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveWordEnd).toHaveBeenCalledWith(1);
});
- it('should handle w when cursor is on the last word', () => {
+ it('should handle w when cursor is on the last word', async () => {
const testBuffer = createMockBuffer('hello world', [0, 8]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -565,8 +565,8 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveWordForward).toHaveBeenCalledWith(1);
});
- it('should handle first c key (sets pending change state)', () => {
- const { result } = renderVimHook();
+ it('should handle first c key (sets pending change state)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -577,8 +577,8 @@ describe('useVim hook', () => {
expect(mockBuffer.del).not.toHaveBeenCalled();
});
- it('should clear pending state on invalid command sequence (df)', () => {
- const { result } = renderVimHook();
+ it('should clear pending state on invalid command sequence (df)', async () => {
+ const { result } = await renderVimHook();
act(() => {
result.current.handleInput(createKey({ sequence: 'd' }));
@@ -589,8 +589,8 @@ describe('useVim hook', () => {
expect(mockBuffer.del).not.toHaveBeenCalled();
});
- it('should clear pending state with Escape in NORMAL mode', () => {
- const { result } = renderVimHook();
+ it('should clear pending state with Escape in NORMAL mode', async () => {
+ const { result } = await renderVimHook();
act(() => {
result.current.handleInput(createKey({ sequence: 'd' }));
@@ -602,10 +602,10 @@ describe('useVim hook', () => {
});
});
- describe('Big Word movement', () => {
- it('should handle W (next big word)', () => {
+ describe('Big Word movement', async () => {
+ it('should handle W (next big word)', async () => {
const testBuffer = createMockBuffer('hello world test');
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -615,9 +615,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveBigWordForward).toHaveBeenCalledWith(1);
});
- it('should handle B (previous big word)', () => {
+ it('should handle B (previous big word)', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 6]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -627,9 +627,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveBigWordBackward).toHaveBeenCalledWith(1);
});
- it('should handle E (end of big word)', () => {
+ it('should handle E (end of big word)', async () => {
const testBuffer = createMockBuffer('hello world test');
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -639,9 +639,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveBigWordEnd).toHaveBeenCalledWith(1);
});
- it('should handle dW (delete big word forward)', () => {
+ it('should handle dW (delete big word forward)', async () => {
const testBuffer = createMockBuffer('hello.world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -654,9 +654,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteBigWordForward).toHaveBeenCalledWith(1);
});
- it('should handle cW (change big word forward)', () => {
+ it('should handle cW (change big word forward)', async () => {
const testBuffer = createMockBuffer('hello.world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -670,9 +670,9 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should handle dB (delete big word backward)', () => {
+ it('should handle dB (delete big word backward)', async () => {
const testBuffer = createMockBuffer('hello.world test', [0, 11]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -685,9 +685,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteBigWordBackward).toHaveBeenCalledWith(1);
});
- it('should handle dE (delete big word end)', () => {
+ it('should handle dE (delete big word end)', async () => {
const testBuffer = createMockBuffer('hello.world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -701,10 +701,10 @@ describe('useVim hook', () => {
});
});
- describe('Disabled vim mode', () => {
- it('should not respond to vim commands when disabled', () => {
+ describe('Disabled vim mode', async () => {
+ it('should not respond to vim commands when disabled', async () => {
mockVimContext.vimEnabled = false;
- const { result } = renderVimHook(mockBuffer);
+ const { result } = await renderVimHook(mockBuffer);
act(() => {
result.current.handleInput(createKey({ sequence: 'h' }));
@@ -716,10 +716,10 @@ describe('useVim hook', () => {
// These tests are no longer applicable at the hook level
- describe('Command repeat system', () => {
- it('should repeat x command from current cursor position', () => {
+ describe('Command repeat system', async () => {
+ it('should repeat x command from current cursor position', async () => {
const testBuffer = createMockBuffer('abcd\nefgh\nijkl', [0, 1]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -735,9 +735,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1);
});
- it('should repeat dd command from current position', () => {
+ it('should repeat dd command from current position', async () => {
const testBuffer = createMockBuffer('line1\nline2\nline3', [1, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -757,9 +757,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteLine).toHaveBeenCalledTimes(2);
});
- it('should repeat ce command from current position', () => {
+ it('should repeat ce command from current position', async () => {
const testBuffer = createMockBuffer('word', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -782,9 +782,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimChangeWordEnd).toHaveBeenCalledTimes(2);
});
- it('should repeat cc command from current position', () => {
+ it('should repeat cc command from current position', async () => {
const testBuffer = createMockBuffer('line1\nline2\nline3', [1, 2]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -807,9 +807,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimChangeLine).toHaveBeenCalledTimes(2);
});
- it('should repeat cw command from current position', () => {
+ it('should repeat cw command from current position', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 6]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -832,9 +832,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimChangeWordForward).toHaveBeenCalledTimes(2);
});
- it('should repeat D command from current position', () => {
+ it('should repeat D command from current position', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 6]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -852,9 +852,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteToEndOfLine).toHaveBeenCalledTimes(1);
});
- it('should repeat C command from current position', () => {
+ it('should repeat C command from current position', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 6]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -874,9 +874,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimChangeToEndOfLine).toHaveBeenCalledTimes(2);
});
- it('should repeat command after cursor movement', () => {
+ it('should repeat command after cursor movement', async () => {
const testBuffer = createMockBuffer('test text', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -892,9 +892,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteChar).toHaveBeenCalledWith(1);
});
- it('should move cursor to the correct position after exiting INSERT mode with "a"', () => {
+ it('should move cursor to the correct position after exiting INSERT mode with "a"', async () => {
const testBuffer = createMockBuffer('hello world', [0, 11]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
expect(testBuffer.cursor).toEqual([0, 10]);
@@ -910,10 +910,10 @@ describe('useVim hook', () => {
});
});
- describe('Special characters and edge cases', () => {
- it('should handle ^ (move to first non-whitespace character)', () => {
+ describe('Special characters and edge cases', async () => {
+ it('should handle ^ (move to first non-whitespace character)', async () => {
const testBuffer = createMockBuffer(' hello world', [0, 5]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -923,9 +923,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveToFirstNonWhitespace).toHaveBeenCalled();
});
- it('should handle G without count (go to last line)', () => {
+ it('should handle G without count (go to last line)', async () => {
const testBuffer = createMockBuffer('line1\nline2\nline3', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -935,9 +935,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveToLastLine).toHaveBeenCalled();
});
- it('should handle gg (go to first line)', () => {
+ it('should handle gg (go to first line)', async () => {
const testBuffer = createMockBuffer('line1\nline2\nline3', [2, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// First 'g' sets pending state
@@ -953,9 +953,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimMoveToFirstLine).toHaveBeenCalled();
});
- it('should handle count with movement commands', () => {
+ it('should handle count with movement commands', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -970,11 +970,11 @@ describe('useVim hook', () => {
});
});
- describe('Vim word operations', () => {
- describe('dw (delete word forward)', () => {
- it('should delete from cursor to start of next word', () => {
+ describe('Vim word operations', async () => {
+ describe('dw (delete word forward)', async () => {
+ it('should delete from cursor to start of next word', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -987,7 +987,7 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteWordForward).toHaveBeenCalledWith(1);
});
- it('should actually delete the complete word including trailing space', () => {
+ it('should actually delete the complete word including trailing space', async () => {
// This test uses the real text-buffer reducer instead of mocks
const initialState = createMockTextBufferState({
lines: ['hello world test'],
@@ -1011,7 +1011,7 @@ describe('useVim hook', () => {
expect(result.cursorCol).toBe(0);
});
- it('should delete word from middle of word correctly', () => {
+ it('should delete word from middle of word correctly', async () => {
const initialState = createMockTextBufferState({
lines: ['hello world test'],
cursorRow: 0,
@@ -1034,7 +1034,7 @@ describe('useVim hook', () => {
expect(result.cursorCol).toBe(2);
});
- it('should handle dw at end of line', () => {
+ it('should handle dw at end of line', async () => {
const initialState = createMockTextBufferState({
lines: ['hello world'],
cursorRow: 0,
@@ -1058,9 +1058,9 @@ describe('useVim hook', () => {
expect(result.cursorCol).toBe(5);
});
- it('should delete multiple words with count', () => {
+ it('should delete multiple words with count', async () => {
const testBuffer = createMockBuffer('one two three four', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1076,9 +1076,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteWordForward).toHaveBeenCalledWith(2);
});
- it('should record command for repeat with dot', () => {
+ it('should record command for repeat with dot', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// Execute dw
@@ -1100,10 +1100,10 @@ describe('useVim hook', () => {
});
});
- describe('de (delete word end)', () => {
- it('should delete from cursor to end of current word', () => {
+ describe('de (delete word end)', async () => {
+ it('should delete from cursor to end of current word', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 1]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1116,9 +1116,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteWordEnd).toHaveBeenCalledWith(1);
});
- it('should handle count with de', () => {
+ it('should handle count with de', async () => {
const testBuffer = createMockBuffer('one two three four', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1135,10 +1135,10 @@ describe('useVim hook', () => {
});
});
- describe('cw (change word forward)', () => {
- it('should change from cursor to start of next word and enter INSERT mode', () => {
+ describe('cw (change word forward)', async () => {
+ it('should change from cursor to start of next word and enter INSERT mode', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1153,9 +1153,9 @@ describe('useVim hook', () => {
expect(mockVimContext.setVimMode).toHaveBeenCalledWith('INSERT');
});
- it('should handle count with cw', () => {
+ it('should handle count with cw', async () => {
const testBuffer = createMockBuffer('one two three four', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1172,9 +1172,9 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should be repeatable with dot', () => {
+ it('should be repeatable with dot', async () => {
const testBuffer = createMockBuffer('hello world test more', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// Execute cw
@@ -1201,10 +1201,10 @@ describe('useVim hook', () => {
});
});
- describe('ce (change word end)', () => {
- it('should change from cursor to end of word and enter INSERT mode', () => {
+ describe('ce (change word end)', async () => {
+ it('should change from cursor to end of word and enter INSERT mode', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 1]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1218,9 +1218,9 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should handle count with ce', () => {
+ it('should handle count with ce', async () => {
const testBuffer = createMockBuffer('one two three four', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1238,10 +1238,10 @@ describe('useVim hook', () => {
});
});
- describe('cc (change line)', () => {
- it('should change entire line and enter INSERT mode', () => {
+ describe('cc (change line)', async () => {
+ it('should change entire line and enter INSERT mode', async () => {
const testBuffer = createMockBuffer('hello world\nsecond line', [0, 5]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1255,12 +1255,12 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should change multiple lines with count', () => {
+ it('should change multiple lines with count', async () => {
const testBuffer = createMockBuffer(
'line1\nline2\nline3\nline4',
[1, 0],
);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1277,9 +1277,9 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should be repeatable with dot', () => {
+ it('should be repeatable with dot', async () => {
const testBuffer = createMockBuffer('line1\nline2\nline3', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// Execute cc
@@ -1306,10 +1306,10 @@ describe('useVim hook', () => {
});
});
- describe('db (delete word backward)', () => {
- it('should delete from cursor to start of previous word', () => {
+ describe('db (delete word backward)', async () => {
+ it('should delete from cursor to start of previous word', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 11]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1322,9 +1322,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteWordBackward).toHaveBeenCalledWith(1);
});
- it('should handle count with db', () => {
+ it('should handle count with db', async () => {
const testBuffer = createMockBuffer('one two three four', [0, 18]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1341,10 +1341,10 @@ describe('useVim hook', () => {
});
});
- describe('cb (change word backward)', () => {
- it('should change from cursor to start of previous word and enter INSERT mode', () => {
+ describe('cb (change word backward)', async () => {
+ it('should change from cursor to start of previous word and enter INSERT mode', async () => {
const testBuffer = createMockBuffer('hello world test', [0, 11]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1358,9 +1358,9 @@ describe('useVim hook', () => {
expect(result.current.mode).toBe('INSERT');
});
- it('should handle count with cb', () => {
+ it('should handle count with cb', async () => {
const testBuffer = createMockBuffer('one two three four', [0, 18]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
act(() => {
@@ -1378,10 +1378,10 @@ describe('useVim hook', () => {
});
});
- describe('Pending state handling', () => {
- it('should clear pending delete state after dw', () => {
+ describe('Pending state handling', async () => {
+ it('should clear pending delete state after dw', async () => {
const testBuffer = createMockBuffer('hello world', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// Press 'd' to enter pending delete state
@@ -1407,9 +1407,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimDeleteLine).toHaveBeenCalledWith(1);
});
- it('should clear pending change state after cw', () => {
+ it('should clear pending change state after cw', async () => {
const testBuffer = createMockBuffer('hello world', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// Execute cw
@@ -1434,9 +1434,9 @@ describe('useVim hook', () => {
expect(testBuffer.vimChangeLine).toHaveBeenCalledWith(1);
});
- it('should clear pending state with escape', () => {
+ it('should clear pending state with escape', async () => {
const testBuffer = createMockBuffer('hello world', [0, 0]);
- const { result } = renderVimHook(testBuffer);
+ const { result } = await renderVimHook(testBuffer);
exitInsertMode(result);
// Enter pending delete state
@@ -1460,10 +1460,10 @@ describe('useVim hook', () => {
});
});
- describe('NORMAL mode escape behavior', () => {
- it('should pass escape through when no pending operator is active', () => {
+ describe('NORMAL mode escape behavior', async () => {
+ it('should pass escape through when no pending operator is active', async () => {
mockVimContext.vimMode = 'NORMAL';
- const { result } = renderVimHook();
+ const { result } = await renderVimHook();
const handled = result.current.handleInput(
createKey({ name: 'escape' }),
@@ -1472,9 +1472,9 @@ describe('useVim hook', () => {
expect(handled).toBe(false);
});
- it('should handle escape and clear pending operator', () => {
+ it('should handle escape and clear pending operator', async () => {
mockVimContext.vimMode = 'NORMAL';
- const { result } = renderVimHook();
+ const { result } = await renderVimHook();
act(() => {
result.current.handleInput(createKey({ sequence: 'd' }));
@@ -1490,10 +1490,10 @@ describe('useVim hook', () => {
});
});
- describe('Shell command pass-through', () => {
+ describe('Shell command pass-through', async () => {
it('should pass through ctrl+r in INSERT mode', async () => {
mockVimContext.vimMode = 'INSERT';
- const { result } = renderVimHook();
+ const { result } = await renderVimHook();
await waitFor(() => {
expect(result.current.mode).toBe('INSERT');
@@ -1509,7 +1509,7 @@ describe('useVim hook', () => {
it('should pass through ! in INSERT mode when buffer is empty', async () => {
mockVimContext.vimMode = 'INSERT';
const emptyBuffer = createMockBuffer('');
- const { result } = renderVimHook(emptyBuffer);
+ const { result } = await renderVimHook(emptyBuffer);
await waitFor(() => {
expect(result.current.mode).toBe('INSERT');
@@ -1523,7 +1523,7 @@ describe('useVim hook', () => {
it('should handle ! as input in INSERT mode when buffer is not empty', async () => {
mockVimContext.vimMode = 'INSERT';
const nonEmptyBuffer = createMockBuffer('not empty');
- const { result } = renderVimHook(nonEmptyBuffer);
+ const { result } = await renderVimHook(nonEmptyBuffer);
await waitFor(() => {
expect(result.current.mode).toBe('INSERT');
@@ -1543,7 +1543,7 @@ describe('useVim hook', () => {
// Line operations (dd, cc) are tested in text-buffer.test.ts
- describe('Reducer-based integration tests', () => {
+ describe('Reducer-based integration tests', async () => {
type VimActionType =
| 'vim_delete_word_end'
| 'vim_delete_word_backward'
@@ -1814,7 +1814,7 @@ describe('useVim hook', () => {
);
});
- describe('double-escape to clear buffer', () => {
+ describe('double-escape to clear buffer', async () => {
beforeEach(() => {
mockBuffer = createMockBuffer('hello world');
mockVimContext.vimEnabled = true;
@@ -1828,7 +1828,7 @@ describe('useVim hook', () => {
});
it('should clear buffer on double-escape in NORMAL mode', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
);
exitInsertMode(result);
@@ -1853,7 +1853,7 @@ describe('useVim hook', () => {
});
it('should clear buffer on double-escape in INSERT mode', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
);
@@ -1874,7 +1874,7 @@ describe('useVim hook', () => {
});
it('should NOT clear buffer if escapes are too slow', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
);
exitInsertMode(result);
@@ -1904,7 +1904,7 @@ describe('useVim hook', () => {
});
it('should clear escape history when clearing pending operator', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
);
exitInsertMode(result);
@@ -1938,7 +1938,7 @@ describe('useVim hook', () => {
});
it('should pass Ctrl+C through to InputPrompt in NORMAL mode', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
);
exitInsertMode(result);
@@ -1952,7 +1952,7 @@ describe('useVim hook', () => {
});
it('should pass Ctrl+C through to InputPrompt in INSERT mode', async () => {
- const { result } = renderHook(() =>
+ const { result } = await renderHook(() =>
useVim(mockBuffer as TextBuffer, mockHandleFinalSubmit),
);
@@ -1965,9 +1965,9 @@ describe('useVim hook', () => {
});
});
- describe('Character deletion and case toggle (X, ~)', () => {
- it('X: should call vimDeleteCharBefore', () => {
- const { result } = renderVimHook();
+ describe('Character deletion and case toggle (X, ~)', async () => {
+ it('X: should call vimDeleteCharBefore', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
let handled: boolean;
@@ -1979,8 +1979,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimDeleteCharBefore).toHaveBeenCalledWith(1);
});
- it('~: should call vimToggleCase', () => {
- const { result } = renderVimHook();
+ it('~: should call vimToggleCase', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
let handled: boolean;
@@ -1992,8 +1992,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimToggleCase).toHaveBeenCalledWith(1);
});
- it('X can be repeated with dot (.)', () => {
- const { result } = renderVimHook();
+ it('X can be repeated with dot (.)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2007,8 +2007,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimDeleteCharBefore).toHaveBeenCalledTimes(2);
});
- it('~ can be repeated with dot (.)', () => {
- const { result } = renderVimHook();
+ it('~ can be repeated with dot (.)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2022,8 +2022,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimToggleCase).toHaveBeenCalledTimes(2);
});
- it('3X calls vimDeleteCharBefore with count=3', () => {
- const { result } = renderVimHook();
+ it('3X calls vimDeleteCharBefore with count=3', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '3' }));
@@ -2034,8 +2034,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimDeleteCharBefore).toHaveBeenCalledWith(3);
});
- it('2~ calls vimToggleCase with count=2', () => {
- const { result } = renderVimHook();
+ it('2~ calls vimToggleCase with count=2', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '2' }));
@@ -2047,9 +2047,9 @@ describe('useVim hook', () => {
});
});
- describe('Replace character (r)', () => {
- it('r{char}: should call vimReplaceChar with the next key', () => {
- const { result } = renderVimHook();
+ describe('Replace character (r)', async () => {
+ it('r{char}: should call vimReplaceChar with the next key', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2062,8 +2062,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimReplaceChar).toHaveBeenCalledWith('x', 1);
});
- it('r: should consume the pending char without passing through', () => {
- const { result } = renderVimHook();
+ it('r: should consume the pending char without passing through', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
let rHandled: boolean;
@@ -2080,8 +2080,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimReplaceChar).toHaveBeenCalledWith('a', 1);
});
- it('Escape cancels pending r (pendingFindOp cleared on Esc)', () => {
- const { result } = renderVimHook();
+ it('Escape cancels pending r (pendingFindOp cleared on Esc)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2099,8 +2099,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimReplaceChar).not.toHaveBeenCalled();
});
- it('2rx calls vimReplaceChar with count=2', () => {
- const { result } = renderVimHook();
+ it('2rx calls vimReplaceChar with count=2', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '2' }));
@@ -2114,8 +2114,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimReplaceChar).toHaveBeenCalledWith('x', 2);
});
- it('r{char} is dot-repeatable', () => {
- const { result } = renderVimHook();
+ it('r{char} is dot-repeatable', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'r' }));
@@ -2133,7 +2133,7 @@ describe('useVim hook', () => {
});
});
- describe('Character find motions (f, F, t, T, ;, ,)', () => {
+ describe('Character find motions (f, F, t, T, ;, ,)', async () => {
type FindCase = {
key: string;
char: string;
@@ -2147,8 +2147,8 @@ describe('useVim hook', () => {
{ key: 'T', char: 'w', mockFn: 'vimFindCharBackward', till: true },
])(
'$key{char}: calls $mockFn (till=$till)',
- ({ key, char, mockFn, till }) => {
- const { result } = renderVimHook();
+ async ({ key, char, mockFn, till }) => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: key }));
@@ -2160,8 +2160,8 @@ describe('useVim hook', () => {
},
);
- it(';: should repeat last f forward find', () => {
- const { result } = renderVimHook();
+ it(';: should repeat last f forward find', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
// f o
@@ -2184,8 +2184,8 @@ describe('useVim hook', () => {
);
});
- it(',: should repeat last f find in reverse direction', () => {
- const { result } = renderVimHook();
+ it(',: should repeat last f find in reverse direction', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
// f o
@@ -2207,8 +2207,8 @@ describe('useVim hook', () => {
);
});
- it('; and , should do nothing if no prior find', () => {
- const { result } = renderVimHook();
+ it('; and , should do nothing if no prior find', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2222,8 +2222,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimFindCharBackward).not.toHaveBeenCalled();
});
- it('Escape cancels pending f (pendingFindOp cleared on Esc)', () => {
- const { result } = renderVimHook();
+ it('Escape cancels pending f (pendingFindOp cleared on Esc)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2242,8 +2242,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimFindCharForward).not.toHaveBeenCalled();
});
- it('2fo calls vimFindCharForward with count=2', () => {
- const { result } = renderVimHook();
+ it('2fo calls vimFindCharForward with count=2', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '2' }));
@@ -2258,9 +2258,9 @@ describe('useVim hook', () => {
});
});
- describe('Operator + find motions (df, dt, dF, dT, cf, ct, cF, cT)', () => {
- it('df{char}: executes delete-to-char, not a dangling operator', () => {
- const { result } = renderVimHook();
+ describe('Operator + find motions (df, dt, dF, dT, cf, ct, cF, cT)', async () => {
+ it('df{char}: executes delete-to-char, not a dangling operator', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
@@ -2354,8 +2354,8 @@ describe('useVim hook', () => {
},
])(
'$operator$findKey{char}: calls $mockFn (till=$till, insert=$entersInsert)',
- ({ operator, findKey, mockFn, till, entersInsert }) => {
- const { result } = renderVimHook();
+ async ({ operator, findKey, mockFn, till, entersInsert }) => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: operator }));
@@ -2373,8 +2373,8 @@ describe('useVim hook', () => {
},
);
- it('2df{char}: count is passed through to vimDeleteToCharForward', () => {
- const { result } = renderVimHook();
+ it('2df{char}: count is passed through to vimDeleteToCharForward', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '2' }));
@@ -2396,9 +2396,9 @@ describe('useVim hook', () => {
});
});
- describe('Yank and paste (y/p/P)', () => {
- it('should handle yy (yank line)', () => {
- const { result } = renderVimHook();
+ describe('Yank and paste (y/p/P)', async () => {
+ it('should handle yy (yank line)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'y' }));
@@ -2409,8 +2409,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankLine).toHaveBeenCalledWith(1);
});
- it('should handle 2yy (yank 2 lines)', () => {
- const { result } = renderVimHook();
+ it('should handle 2yy (yank 2 lines)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '2' }));
@@ -2424,8 +2424,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankLine).toHaveBeenCalledWith(2);
});
- it('should handle Y (yank to end of line, equivalent to y$)', () => {
- const { result } = renderVimHook();
+ it('should handle Y (yank to end of line, equivalent to y$)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'Y' }));
@@ -2433,8 +2433,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankToEndOfLine).toHaveBeenCalledWith(1);
});
- it('should handle yw (yank word forward)', () => {
- const { result } = renderVimHook();
+ it('should handle yw (yank word forward)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'y' }));
@@ -2445,8 +2445,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankWordForward).toHaveBeenCalledWith(1);
});
- it('should handle yW (yank big word forward)', () => {
- const { result } = renderVimHook();
+ it('should handle yW (yank big word forward)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'y' }));
@@ -2457,8 +2457,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankBigWordForward).toHaveBeenCalledWith(1);
});
- it('should handle ye (yank to end of word)', () => {
- const { result } = renderVimHook();
+ it('should handle ye (yank to end of word)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'y' }));
@@ -2469,8 +2469,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankWordEnd).toHaveBeenCalledWith(1);
});
- it('should handle yE (yank to end of big word)', () => {
- const { result } = renderVimHook();
+ it('should handle yE (yank to end of big word)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'y' }));
@@ -2481,8 +2481,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankBigWordEnd).toHaveBeenCalledWith(1);
});
- it('should handle y$ (yank to end of line)', () => {
- const { result } = renderVimHook();
+ it('should handle y$ (yank to end of line)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'y' }));
@@ -2493,8 +2493,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimYankToEndOfLine).toHaveBeenCalledWith(1);
});
- it('should handle p (paste after)', () => {
- const { result } = renderVimHook();
+ it('should handle p (paste after)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'p' }));
@@ -2502,8 +2502,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimPasteAfter).toHaveBeenCalledWith(1);
});
- it('should handle 2p (paste after, count 2)', () => {
- const { result } = renderVimHook();
+ it('should handle 2p (paste after, count 2)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: '2' }));
@@ -2514,8 +2514,8 @@ describe('useVim hook', () => {
expect(mockBuffer.vimPasteAfter).toHaveBeenCalledWith(2);
});
- it('should handle P (paste before)', () => {
- const { result } = renderVimHook();
+ it('should handle P (paste before)', async () => {
+ const { result } = await renderVimHook();
exitInsertMode(result);
act(() => {
result.current.handleInput(createKey({ sequence: 'P' }));
@@ -2524,7 +2524,7 @@ describe('useVim hook', () => {
});
// Integration tests using actual textBufferReducer to verify full state changes
- it('should duplicate a line below with yy then p', () => {
+ it('should duplicate a line below with yy then p', async () => {
const initialState = createMockTextBufferState({
lines: ['hello', 'world'],
cursorRow: 0,
@@ -2548,7 +2548,7 @@ describe('useVim hook', () => {
expect(state.cursorCol).toBe(0);
});
- it('should paste a yanked word after cursor with yw then p', () => {
+ it('should paste a yanked word after cursor with yw then p', async () => {
const initialState = createMockTextBufferState({
lines: ['hello world'],
cursorRow: 0,
@@ -2573,7 +2573,7 @@ describe('useVim hook', () => {
expect(state.lines[0]).toContain('hello ');
});
- it('should move a word forward with dw then p', () => {
+ it('should move a word forward with dw then p', async () => {
const initialState = createMockTextBufferState({
lines: ['hello world'],
cursorRow: 0,
diff --git a/packages/cli/src/ui/layouts/DefaultAppLayout.test.tsx b/packages/cli/src/ui/layouts/DefaultAppLayout.test.tsx
index 2fe34e4428..43b970da8e 100644
--- a/packages/cli/src/ui/layouts/DefaultAppLayout.test.tsx
+++ b/packages/cli/src/ui/layouts/DefaultAppLayout.test.tsx
@@ -106,8 +106,7 @@ describe('', () => {
mockUIState.activeBackgroundShellPid = 123;
mockUIState.backgroundShellHeight = 5;
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -118,8 +117,7 @@ describe('', () => {
mockUIState.backgroundShellHeight = 5;
mockUIState.streamingState = StreamingState.WaitingForConfirmation;
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -130,8 +128,7 @@ describe('', () => {
mockUIState.backgroundShellHeight = 5;
mockUIState.streamingState = StreamingState.Responding;
- const { lastFrame, waitUntilReady, unmount } = render();
- await waitUntilReady();
+ const { lastFrame, unmount } = await render();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
diff --git a/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.test.tsx b/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.test.tsx
index d98dab8f04..a6fa1ab626 100644
--- a/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.test.tsx
+++ b/packages/cli/src/ui/privacy/CloudFreePrivacyNotice.test.tsx
@@ -82,10 +82,9 @@ describe('CloudFreePrivacyNotice', () => {
updateDataCollectionOptIn,
});
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(expectedText);
unmount();
@@ -115,10 +114,9 @@ describe('CloudFreePrivacyNotice', () => {
updateDataCollectionOptIn,
});
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
@@ -145,10 +143,9 @@ describe('CloudFreePrivacyNotice', () => {
])(
'calls correct functions on selecting "$label"',
async ({ selection }) => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const onSelectHandler =
mockedRadioButtonSelect.mock.calls[0][0].onSelect;
diff --git a/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.test.tsx b/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.test.tsx
index 7ac6f70ef9..41d468433a 100644
--- a/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.test.tsx
+++ b/packages/cli/src/ui/privacy/CloudPaidPrivacyNotice.test.tsx
@@ -25,10 +25,9 @@ describe('CloudPaidPrivacyNotice', () => {
});
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Vertex AI Notice');
expect(lastFrame()).toContain('Service Specific Terms');
@@ -37,10 +36,9 @@ describe('CloudPaidPrivacyNotice', () => {
});
it('exits on Escape', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
diff --git a/packages/cli/src/ui/privacy/GeminiPrivacyNotice.test.tsx b/packages/cli/src/ui/privacy/GeminiPrivacyNotice.test.tsx
index 1fbcf9efa2..ab916b1d1f 100644
--- a/packages/cli/src/ui/privacy/GeminiPrivacyNotice.test.tsx
+++ b/packages/cli/src/ui/privacy/GeminiPrivacyNotice.test.tsx
@@ -25,10 +25,9 @@ describe('GeminiPrivacyNotice', () => {
});
it('renders correctly', async () => {
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain('Gemini API Key Notice');
expect(lastFrame()).toContain('By using the Gemini API');
@@ -37,10 +36,9 @@ describe('GeminiPrivacyNotice', () => {
});
it('exits on Escape', async () => {
- const { waitUntilReady, unmount } = render(
+ const { waitUntilReady, unmount } = await render(
,
);
- await waitUntilReady();
const keypressHandler = mockedUseKeypress.mock.calls[0][0];
await act(async () => {
diff --git a/packages/cli/src/ui/privacy/PrivacyNotice.test.tsx b/packages/cli/src/ui/privacy/PrivacyNotice.test.tsx
index e3a4e5d6de..4a2a882980 100644
--- a/packages/cli/src/ui/privacy/PrivacyNotice.test.tsx
+++ b/packages/cli/src/ui/privacy/PrivacyNotice.test.tsx
@@ -69,10 +69,9 @@ describe('PrivacyNotice', () => {
authType,
} as unknown as ContentGeneratorConfig);
- const { lastFrame, waitUntilReady, unmount } = render(
+ const { lastFrame, unmount } = await render(
,
);
- await waitUntilReady();
expect(lastFrame()).toContain(expectedComponent);
unmount();
diff --git a/packages/cli/src/ui/utils/CodeColorizer.test.tsx b/packages/cli/src/ui/utils/CodeColorizer.test.tsx
index 2628a36d0a..c647491ec9 100644
--- a/packages/cli/src/ui/utils/CodeColorizer.test.tsx
+++ b/packages/cli/src/ui/utils/CodeColorizer.test.tsx
@@ -35,10 +35,7 @@ describe('colorizeCode', () => {
hideLineNumbers: true,
});
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
- <>{result}>,
- );
- await waitUntilReady();
+ const { lastFrame, unmount } = await renderWithProviders(<>{result}>);
// We expect the output to preserve the empty line.
// If the bug exists, it might look like "line 1\nline 3"
// If fixed, it should look like "line 1\n \nline 3" (if we use space) or just have the newline.
diff --git a/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx b/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx
index cd730af398..ed68adb9c5 100644
--- a/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx
+++ b/packages/cli/src/ui/utils/MarkdownDisplay.test.tsx
@@ -21,20 +21,18 @@ describe('', () => {
});
it('renders nothing for empty text', async () => {
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame({ allowEmpty: true })).toMatchSnapshot();
unmount();
});
it('renders a simple paragraph', async () => {
const text = 'Hello, world.';
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -52,10 +50,9 @@ describe('', () => {
### Header 3
#### Header 4
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -65,30 +62,27 @@ describe('', () => {
/\n/g,
eol,
);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('renders a fenced code block without a language', async () => {
const text = '```\nplain text\n```'.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
it('handles unclosed (pending) code blocks', async () => {
const text = '```typescript\nlet y = 2;'.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -99,10 +93,9 @@ describe('', () => {
* item B
+ item C
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -113,10 +106,9 @@ describe('', () => {
* Level 2
* Level 3
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -126,10 +118,9 @@ describe('', () => {
1. First item
2. Second item
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -142,10 +133,9 @@ World
***
Test
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -157,10 +147,9 @@ Test
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -171,10 +160,9 @@ Some text before.
| A | B |
|---|
| 1 | 2 |`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -183,10 +171,9 @@ Some text before.
const text = `Paragraph 1.
Paragraph 2.`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -206,10 +193,9 @@ some code
Another paragraph.
`.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
unmount();
});
@@ -229,11 +215,10 @@ Another paragraph.
[],
);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
{ settings },
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame()).not.toContain('1 const x = 1;');
unmount();
@@ -241,10 +226,9 @@ Another paragraph.
it('shows line numbers in code blocks by default', async () => {
const text = '```javascript\nconst x = 1;\n```'.replace(/\n/g, eol);
- const { lastFrame, waitUntilReady, unmount } = await renderWithProviders(
+ const { lastFrame, unmount } = await renderWithProviders(
,
);
- await waitUntilReady();
expect(lastFrame()).toMatchSnapshot();
expect(lastFrame()).toContain('1 const x = 1;');
unmount();
diff --git a/packages/cli/src/ui/utils/TableRenderer.test.tsx b/packages/cli/src/ui/utils/TableRenderer.test.tsx
index 2df991d36c..4735f682b8 100644
--- a/packages/cli/src/ui/utils/TableRenderer.test.tsx
+++ b/packages/cli/src/ui/utils/TableRenderer.test.tsx
@@ -24,9 +24,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toContain('Header 1');
expect(output).toContain('Row 1, Col 1');
@@ -56,9 +54,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
// Since terminalWidth is 80 and headers are long, they might be truncated.
// We just check for some of the content.
@@ -86,9 +82,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toContain('This is a very');
expect(output).toContain('long cell');
@@ -114,9 +108,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toContain('wrapping in');
await expect(renderResult).toMatchSvgSnapshot();
@@ -141,9 +133,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toContain('Tiny');
expect(output).toContain('definitely needs');
@@ -170,9 +160,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toContain('Start. Stop.');
await expect(renderResult).toMatchSvgSnapshot();
@@ -191,9 +179,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
// The output should NOT contain the literal '**'
expect(output).not.toContain('**Bold Header**');
@@ -218,9 +204,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
// Markers should be gone
expect(output).not.toContain('**');
@@ -263,9 +247,7 @@ describe('TableRenderer', () => {
/>,
{ width: terminalWidth },
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toContain('Comprehensive Architectural');
expect(output).toContain('protocol buffers');
@@ -333,9 +315,7 @@ describe('TableRenderer', () => {
/>,
{ width: terminalWidth },
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expected.forEach((text) => {
expect(output).toContain(text);
@@ -367,9 +347,7 @@ describe('TableRenderer', () => {
terminalWidth={terminalWidth}
/>,
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
-
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expected.forEach((text) => {
expect(output).toContain(text);
@@ -496,8 +474,7 @@ describe('TableRenderer', () => {
/>,
{ width: terminalWidth },
);
- const { lastFrame, waitUntilReady, unmount } = renderResult;
- await waitUntilReady();
+ const { lastFrame, unmount } = renderResult;
const output = lastFrame();
expect(output).toBeDefined();