mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 11:04:42 -07:00
Migrate tests to use avoid jsdom (#12118)
This commit is contained in:
@@ -4,10 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { waitFor, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { vi } from 'vitest';
|
||||
import { FolderTrustDialog } from './FolderTrustDialog.js';
|
||||
import * as processUtils from '../../utils/processUtils.js';
|
||||
@@ -56,12 +54,12 @@ describe('FolderTrustDialog', () => {
|
||||
stdin.write('\u001b[27u'); // Press kitty escape key
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain(
|
||||
'A folder trust level must be selected to continue. Exiting since escape was pressed.',
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedExit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
@@ -95,7 +93,7 @@ describe('FolderTrustDialog', () => {
|
||||
stdin.write('r');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedExit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { render } from 'ink-testing-library';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Help } from './Help.js';
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { render, cleanup } from '@testing-library/react';
|
||||
import { render, cleanup } from 'ink-testing-library';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
DEFAULT_GEMINI_FLASH_LITE_MODEL,
|
||||
@@ -82,12 +80,12 @@ describe('<ModelDialog />', () => {
|
||||
});
|
||||
|
||||
it('renders the title and help text', () => {
|
||||
const { getByText } = renderComponent();
|
||||
expect(getByText('Select Model')).toBeDefined();
|
||||
expect(getByText('(Press Esc to close)')).toBeDefined();
|
||||
expect(
|
||||
getByText('> To use a specific Gemini model, use the --model flag.'),
|
||||
).toBeDefined();
|
||||
const { lastFrame } = renderComponent();
|
||||
expect(lastFrame()).toContain('Select Model');
|
||||
expect(lastFrame()).toContain('(Press Esc to close)');
|
||||
expect(lastFrame()).toContain(
|
||||
'> To use a specific Gemini model, use the --model flag.',
|
||||
);
|
||||
});
|
||||
|
||||
it('passes all model options to DescriptiveRadioButtonSelect', () => {
|
||||
|
||||
@@ -4,16 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
/// <reference types="vitest/globals" />
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import type { Mock } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { PermissionsModifyTrustDialog } from './PermissionsModifyTrustDialog.js';
|
||||
import { TrustLevel } from '../../config/trustedFolders.js';
|
||||
import { waitFor, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import * as processUtils from '../../utils/processUtils.js';
|
||||
import { usePermissionsModifyTrust } from '../hooks/usePermissionsModifyTrust.js';
|
||||
|
||||
@@ -72,7 +68,7 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={vi.fn()} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('Modify Trust Level');
|
||||
expect(lastFrame()).toContain('Folder: /test/dir');
|
||||
expect(lastFrame()).toContain('Current Level: DO_NOT_TRUST');
|
||||
@@ -94,7 +90,7 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={vi.fn()} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain(
|
||||
'Note: This folder behaves as a trusted folder because one of the parent folders is trusted.',
|
||||
);
|
||||
@@ -116,7 +112,7 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={vi.fn()} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain(
|
||||
'Note: This folder behaves as a trusted folder because the connected IDE workspace is trusted.',
|
||||
);
|
||||
@@ -128,7 +124,7 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={vi.fn()} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('Trust this folder (dir)');
|
||||
expect(lastFrame()).toContain('Trust parent folder (test)');
|
||||
});
|
||||
@@ -140,13 +136,13 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={onExit} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
|
||||
await vi.waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
|
||||
|
||||
act(() => {
|
||||
stdin.write('\u001b[27u'); // Kitty escape key
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(onExit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -171,11 +167,11 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={onExit} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
|
||||
await vi.waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
|
||||
|
||||
act(() => stdin.write('r')); // Press 'r' to restart
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockCommitTrustLevelChange).toHaveBeenCalled();
|
||||
expect(mockRelaunchApp).toHaveBeenCalled();
|
||||
expect(onExit).toHaveBeenCalled();
|
||||
@@ -201,11 +197,11 @@ describe('PermissionsModifyTrustDialog', () => {
|
||||
<PermissionsModifyTrustDialog onExit={onExit} addItem={vi.fn()} />,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
|
||||
await vi.waitFor(() => expect(lastFrame()).not.toContain('Loading...'));
|
||||
|
||||
act(() => stdin.write('\u001b[27u')); // Press kitty escape key
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockCommitTrustLevelChange).not.toHaveBeenCalled();
|
||||
expect(onExit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
@@ -30,7 +28,6 @@ import { LoadedSettings, SettingScope } from '../../config/settings.js';
|
||||
import { VimModeProvider } from '../contexts/VimModeContext.js';
|
||||
import { KeypressProvider } from '../contexts/KeypressContext.js';
|
||||
import { act } from 'react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { saveModifiedSettings, TEST_ONLY } from '../../utils/settingsUtils.js';
|
||||
import {
|
||||
getSettingsSchema,
|
||||
@@ -408,7 +405,7 @@ describe('SettingsDialog', () => {
|
||||
const { stdin, unmount, lastFrame } = render(component);
|
||||
|
||||
// Wait for initial render and verify we're on Vim Mode (first setting)
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('● Vim Mode');
|
||||
});
|
||||
|
||||
@@ -416,7 +413,7 @@ describe('SettingsDialog', () => {
|
||||
act(() => {
|
||||
stdin.write(TerminalKeys.DOWN_ARROW as string);
|
||||
});
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('● Disable Auto Update');
|
||||
});
|
||||
|
||||
@@ -425,14 +422,14 @@ describe('SettingsDialog', () => {
|
||||
stdin.write(TerminalKeys.ENTER as string);
|
||||
});
|
||||
// Wait for the setting change to be processed
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(
|
||||
vi.mocked(saveModifiedSettings).mock.calls.length,
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Wait for the mock to be called
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -470,7 +467,7 @@ describe('SettingsDialog', () => {
|
||||
await wait();
|
||||
stdin.write(TerminalKeys.ENTER as string);
|
||||
await wait();
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -507,7 +504,7 @@ describe('SettingsDialog', () => {
|
||||
await wait();
|
||||
stdin.write(TerminalKeys.ENTER as string);
|
||||
await wait();
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(vi.mocked(saveModifiedSettings)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -596,7 +593,7 @@ describe('SettingsDialog', () => {
|
||||
);
|
||||
|
||||
// Wait for initial render
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('Vim Mode');
|
||||
});
|
||||
|
||||
@@ -668,7 +665,7 @@ describe('SettingsDialog', () => {
|
||||
);
|
||||
|
||||
// Wait for initial render
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('Hide Window Title');
|
||||
});
|
||||
|
||||
@@ -964,7 +961,7 @@ describe('SettingsDialog', () => {
|
||||
await wait(50);
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(
|
||||
vi.mocked(saveModifiedSettings).mock.calls.length,
|
||||
).toBeGreaterThan(0);
|
||||
@@ -1024,7 +1021,7 @@ describe('SettingsDialog', () => {
|
||||
await wait(30);
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(
|
||||
vi.mocked(saveModifiedSettings).mock.calls.length,
|
||||
).toBeGreaterThan(0);
|
||||
@@ -1141,7 +1138,7 @@ describe('SettingsDialog', () => {
|
||||
);
|
||||
|
||||
// Wait for initial render
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('Vim Mode');
|
||||
});
|
||||
|
||||
@@ -1203,7 +1200,7 @@ describe('SettingsDialog', () => {
|
||||
);
|
||||
|
||||
// Wait for initial render
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toContain('Vim Mode');
|
||||
});
|
||||
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import {
|
||||
BaseSelectionList,
|
||||
@@ -301,7 +298,7 @@ describe('BaseSelectionList', () => {
|
||||
|
||||
rerender(<BaseSelectionList {...componentProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame()).toBeTruthy();
|
||||
});
|
||||
};
|
||||
@@ -325,7 +322,7 @@ describe('BaseSelectionList', () => {
|
||||
// New visible window should be Items 2, 3, 4 (scroll offset 1).
|
||||
await updateActiveIndex(3);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).not.toContain('Item 1');
|
||||
expect(output).toContain('Item 2');
|
||||
@@ -339,7 +336,7 @@ describe('BaseSelectionList', () => {
|
||||
|
||||
await updateActiveIndex(4);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Item 3'); // Should see items 3, 4, 5
|
||||
expect(output).toContain('Item 5');
|
||||
@@ -350,7 +347,7 @@ describe('BaseSelectionList', () => {
|
||||
// This should trigger scroll up to show items 2, 3, 4
|
||||
await updateActiveIndex(1);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Item 2');
|
||||
expect(output).toContain('Item 4');
|
||||
@@ -364,7 +361,7 @@ describe('BaseSelectionList', () => {
|
||||
// Visible items: 8, 9, 10.
|
||||
const { lastFrame } = renderScrollableList(9);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Item 10');
|
||||
expect(output).toContain('Item 8');
|
||||
@@ -383,14 +380,14 @@ describe('BaseSelectionList', () => {
|
||||
expect(lastFrame()).toContain('Item 1');
|
||||
|
||||
await updateActiveIndex(3); // Should trigger scroll
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Item 2');
|
||||
expect(output).toContain('Item 4');
|
||||
expect(output).not.toContain('Item 1');
|
||||
});
|
||||
await updateActiveIndex(5); // Scroll further
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Item 4');
|
||||
expect(output).toContain('Item 6');
|
||||
@@ -417,7 +414,7 @@ describe('BaseSelectionList', () => {
|
||||
it('should correctly identify the selected item when scrolled (high index)', async () => {
|
||||
renderScrollableList(5);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
// Item 6 (index 5) should be selected
|
||||
expect(mockRenderItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ value: 'Item 6' }),
|
||||
@@ -475,7 +472,7 @@ describe('BaseSelectionList', () => {
|
||||
0,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
// At the top, should show first 3 items
|
||||
expect(output).toContain('Item 1');
|
||||
@@ -493,7 +490,7 @@ describe('BaseSelectionList', () => {
|
||||
5,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
// After scrolling to middle, should see items around index 5
|
||||
expect(output).toContain('Item 4');
|
||||
@@ -512,7 +509,7 @@ describe('BaseSelectionList', () => {
|
||||
9,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
const output = lastFrame();
|
||||
// At the end, should show last 3 items
|
||||
expect(output).toContain('Item 8');
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../../test-utils/render.js';
|
||||
import type {
|
||||
Viewport,
|
||||
TextBuffer,
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import type React from 'react';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import type { Mock } from 'vitest';
|
||||
import { vi } from 'vitest';
|
||||
import type { Key } from './KeypressContext.js';
|
||||
@@ -370,7 +369,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
||||
stdin.write(PASTE_END);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
// Expect the handler to be called exactly once for the entire paste
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -399,7 +398,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
||||
stdin.write(PASTE_END);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -427,7 +426,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
||||
stdin.write(PASTE_END.slice(3));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -1193,7 +1192,7 @@ describe('Kitty Sequence Parsing', () => {
|
||||
}
|
||||
|
||||
// Should parse once complete
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(keyHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'escape',
|
||||
|
||||
@@ -4,17 +4,40 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { type MutableRefObject } from 'react';
|
||||
import { type MutableRefObject, Component, type ReactNode } from 'react';
|
||||
import { render } from 'ink-testing-library';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { act } from 'react';
|
||||
import type { SessionMetrics } from './SessionContext.js';
|
||||
import { SessionStatsProvider, useSessionStats } from './SessionContext.js';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { uiTelemetryService } from '@google/gemini-cli-core';
|
||||
|
||||
class ErrorBoundary extends Component<
|
||||
{ children: ReactNode; onError: (error: Error) => void },
|
||||
{ hasError: boolean }
|
||||
> {
|
||||
constructor(props: { children: ReactNode; onError: (error: Error) => void }) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(_error: Error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
override componentDidCatch(error: Error) {
|
||||
this.props.onError(error);
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.state.hasError) {
|
||||
return null;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A test harness component that uses the hook and exposes the context value
|
||||
* via a mutable ref. This allows us to interact with the context's functions
|
||||
@@ -208,16 +231,22 @@ describe('SessionStatsContext', () => {
|
||||
});
|
||||
|
||||
it('should throw an error when useSessionStats is used outside of a provider', () => {
|
||||
// Suppress console.error for this test since we expect an error
|
||||
const onError = vi.fn();
|
||||
// Suppress console.error from React for this test
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
try {
|
||||
// Expect renderHook itself to throw when the hook is used outside a provider
|
||||
expect(() => {
|
||||
renderHook(() => useSessionStats());
|
||||
}).toThrow('useSessionStats must be used within a SessionStatsProvider');
|
||||
} finally {
|
||||
consoleSpy.mockRestore();
|
||||
}
|
||||
render(
|
||||
<ErrorBoundary onError={onError}>
|
||||
<TestHarness contextRef={{ current: undefined }} />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
message: 'useSessionStats must be used within a SessionStatsProvider',
|
||||
}),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,16 +4,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { renderHook, waitFor, act } from '@testing-library/react';
|
||||
import { act, useState } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useAtCompletion } from './useAtCompletion.js';
|
||||
import type { Config, FileSearch } from '@google/gemini-cli-core';
|
||||
import { FileSearchFactory } from '@google/gemini-cli-core';
|
||||
import type { FileSystemStructure } from '@google/gemini-cli-test-utils';
|
||||
import { createTmpDir, cleanupTmpDir } from '@google/gemini-cli-test-utils';
|
||||
import { useState } from 'react';
|
||||
import type { Suggestion } from '../components/SuggestionsDisplay.js';
|
||||
|
||||
// Test harness to capture the state from the hook's callbacks.
|
||||
@@ -76,7 +74,7 @@ describe('useAtCompletion', () => {
|
||||
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -106,7 +104,7 @@ describe('useAtCompletion', () => {
|
||||
useTestHarnessForAtCompletion(true, 'src/', mockConfig, testRootDir),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -129,7 +127,7 @@ describe('useAtCompletion', () => {
|
||||
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -166,7 +164,7 @@ describe('useAtCompletion', () => {
|
||||
);
|
||||
|
||||
// The hook should find 'cRaZycAsE.txt' even though the pattern is 'CrAzYCaSe'.
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'cRaZycAsE.txt',
|
||||
]);
|
||||
@@ -177,15 +175,29 @@ describe('useAtCompletion', () => {
|
||||
describe('UI State and Loading Behavior', () => {
|
||||
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
|
||||
const mockFileSearch = {
|
||||
initialize: vi.fn().mockImplementation(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}),
|
||||
search: vi.fn().mockResolvedValue([]),
|
||||
};
|
||||
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(
|
||||
mockFileSearch as unknown as FileSearch,
|
||||
);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
|
||||
);
|
||||
|
||||
// It's initially true because the effect runs synchronously.
|
||||
expect(result.current.isLoadingSuggestions).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isLoadingSuggestions).toBe(true);
|
||||
});
|
||||
|
||||
// Wait for the loading to complete.
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isLoadingSuggestions).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -200,7 +212,7 @@ describe('useAtCompletion', () => {
|
||||
{ initialProps: { pattern: 'a' } },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'a.txt',
|
||||
]);
|
||||
@@ -210,7 +222,7 @@ describe('useAtCompletion', () => {
|
||||
rerender({ pattern: 'b' });
|
||||
|
||||
// Wait for the final result
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'b.txt',
|
||||
]);
|
||||
@@ -253,7 +265,7 @@ describe('useAtCompletion', () => {
|
||||
);
|
||||
|
||||
// Wait for the initial search to complete (using real timers)
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'a.txt',
|
||||
]);
|
||||
@@ -283,7 +295,7 @@ describe('useAtCompletion', () => {
|
||||
vi.useRealTimers();
|
||||
|
||||
// Wait for the search results to be processed
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'b.txt',
|
||||
]);
|
||||
@@ -314,7 +326,7 @@ describe('useAtCompletion', () => {
|
||||
);
|
||||
|
||||
// Wait for the hook to be ready (initialization is complete)
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockFileSearch.search).toHaveBeenCalledWith(
|
||||
'a',
|
||||
expect.any(Object),
|
||||
@@ -330,7 +342,7 @@ describe('useAtCompletion', () => {
|
||||
expect(abortSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Wait for the final result, which should be from the second, faster search.
|
||||
await waitFor(
|
||||
await vi.waitFor(
|
||||
() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual(['b']);
|
||||
},
|
||||
@@ -357,7 +369,7 @@ describe('useAtCompletion', () => {
|
||||
);
|
||||
|
||||
// Wait for the hook to be ready and have suggestions
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'a.txt',
|
||||
]);
|
||||
@@ -389,7 +401,7 @@ describe('useAtCompletion', () => {
|
||||
);
|
||||
|
||||
// Wait for the hook to enter the error state
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isLoadingSuggestions).toBe(false);
|
||||
});
|
||||
expect(result.current.suggestions).toEqual([]); // No suggestions on error
|
||||
@@ -420,7 +432,7 @@ describe('useAtCompletion', () => {
|
||||
useTestHarnessForAtCompletion(true, '', mockConfig, testRootDir),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -441,7 +453,7 @@ describe('useAtCompletion', () => {
|
||||
useTestHarnessForAtCompletion(true, '', undefined, testRootDir),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -469,7 +481,7 @@ describe('useAtCompletion', () => {
|
||||
);
|
||||
|
||||
// Wait for initial suggestions from the first directory
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'file1.txt',
|
||||
]);
|
||||
@@ -481,13 +493,13 @@ describe('useAtCompletion', () => {
|
||||
});
|
||||
|
||||
// After CWD changes, suggestions should be cleared and it should load again.
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isLoadingSuggestions).toBe(true);
|
||||
expect(result.current.suggestions).toEqual([]);
|
||||
});
|
||||
|
||||
// Wait for the new suggestions from the second directory
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.map((s) => s.value)).toEqual([
|
||||
'file2.txt',
|
||||
]);
|
||||
@@ -525,7 +537,7 @@ describe('useAtCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
@@ -15,7 +13,8 @@ import {
|
||||
type MockedFunction,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useAutoAcceptIndicator } from './useAutoAcceptIndicator.js';
|
||||
|
||||
import { Config, ApprovalMode } from '@google/gemini-cli-core';
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { vi, type Mock } from 'vitest';
|
||||
import { useFlickerDetector } from './useFlickerDetector.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
@@ -19,10 +17,15 @@ import { appEvents, AppEvent } from '../../utils/events.js';
|
||||
// Mock dependencies
|
||||
vi.mock('../contexts/ConfigContext.js');
|
||||
vi.mock('../contexts/UIStateContext.js');
|
||||
vi.mock('@google/gemini-cli-core', () => ({
|
||||
recordFlickerFrame: vi.fn(),
|
||||
GEMINI_DIR: '.gemini',
|
||||
}));
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
recordFlickerFrame: vi.fn(),
|
||||
GEMINI_DIR: '.gemini',
|
||||
};
|
||||
});
|
||||
vi.mock('ink', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('ink')>();
|
||||
return {
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { vi, type Mock, type MockInstance } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useFolderTrust } from './useFolderTrust.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { FolderTrustChoice } from '../components/FolderTrustDialog.js';
|
||||
@@ -30,7 +29,6 @@ vi.mock('node:process', async () => {
|
||||
describe('useFolderTrust', () => {
|
||||
let mockSettings: LoadedSettings;
|
||||
let mockTrustedFolders: LoadedTrustedFolders;
|
||||
let loadTrustedFoldersSpy: MockInstance;
|
||||
let isWorkspaceTrustedSpy: MockInstance;
|
||||
let onTrustChange: (isTrusted: boolean | undefined) => void;
|
||||
let addItem: Mock;
|
||||
@@ -51,9 +49,9 @@ describe('useFolderTrust', () => {
|
||||
setValue: vi.fn(),
|
||||
} as unknown as LoadedTrustedFolders;
|
||||
|
||||
loadTrustedFoldersSpy = vi
|
||||
.spyOn(trustedFolders, 'loadTrustedFolders')
|
||||
.mockReturnValue(mockTrustedFolders);
|
||||
vi.spyOn(trustedFolders, 'loadTrustedFolders').mockReturnValue(
|
||||
mockTrustedFolders,
|
||||
);
|
||||
isWorkspaceTrustedSpy = vi.spyOn(trustedFolders, 'isWorkspaceTrusted');
|
||||
mockedCwd.mockReturnValue('/test/path');
|
||||
onTrustChange = vi.fn();
|
||||
@@ -82,7 +80,7 @@ describe('useFolderTrust', () => {
|
||||
expect(onTrustChange).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should open dialog when folder trust is undefined', () => {
|
||||
it('should open dialog when folder trust is undefined', async () => {
|
||||
isWorkspaceTrustedSpy.mockReturnValue({
|
||||
isTrusted: undefined,
|
||||
source: undefined,
|
||||
@@ -90,7 +88,9 @@ describe('useFolderTrust', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFolderTrust(mockSettings, onTrustChange, addItem),
|
||||
);
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true);
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true);
|
||||
});
|
||||
expect(onTrustChange).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
@@ -112,26 +112,41 @@ describe('useFolderTrust', () => {
|
||||
expect(addItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle TRUST_FOLDER choice', () => {
|
||||
it('should handle TRUST_FOLDER choice', async () => {
|
||||
isWorkspaceTrustedSpy.mockReturnValue({
|
||||
isTrusted: undefined,
|
||||
source: undefined,
|
||||
});
|
||||
|
||||
(mockTrustedFolders.setValue as Mock).mockImplementation(() => {
|
||||
isWorkspaceTrustedSpy.mockReturnValue({
|
||||
isTrusted: true,
|
||||
source: 'file',
|
||||
});
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useFolderTrust(mockSettings, onTrustChange, addItem),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isTrusted).toBeUndefined();
|
||||
});
|
||||
|
||||
expect(loadTrustedFoldersSpy).toHaveBeenCalled();
|
||||
expect(mockTrustedFolders.setValue).toHaveBeenCalledWith(
|
||||
'/test/path',
|
||||
TrustLevel.TRUST_FOLDER,
|
||||
);
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(false);
|
||||
expect(onTrustChange).toHaveBeenLastCalledWith(true);
|
||||
await act(async () => {
|
||||
await result.current.handleFolderTrustSelect(
|
||||
FolderTrustChoice.TRUST_FOLDER,
|
||||
);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockTrustedFolders.setValue).toHaveBeenCalledWith(
|
||||
'/test/path',
|
||||
TrustLevel.TRUST_FOLDER,
|
||||
);
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(false);
|
||||
expect(onTrustChange).toHaveBeenLastCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle TRUST_PARENT choice', () => {
|
||||
@@ -177,7 +192,7 @@ describe('useFolderTrust', () => {
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('should do nothing for default choice', () => {
|
||||
it('should do nothing for default choice', async () => {
|
||||
isWorkspaceTrustedSpy.mockReturnValue({
|
||||
isTrusted: undefined,
|
||||
source: undefined,
|
||||
@@ -192,24 +207,40 @@ describe('useFolderTrust', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockTrustedFolders.setValue).not.toHaveBeenCalled();
|
||||
expect(mockSettings.setValue).not.toHaveBeenCalled();
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true);
|
||||
expect(onTrustChange).toHaveBeenCalledWith(undefined);
|
||||
await vi.waitFor(() => {
|
||||
expect(mockTrustedFolders.setValue).not.toHaveBeenCalled();
|
||||
expect(mockSettings.setValue).not.toHaveBeenCalled();
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true);
|
||||
expect(onTrustChange).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('should set isRestarting to true when trust status changes from false to true', () => {
|
||||
it('should set isRestarting to true when trust status changes from false to true', async () => {
|
||||
isWorkspaceTrustedSpy.mockReturnValue({ isTrusted: false, source: 'file' }); // Initially untrusted
|
||||
|
||||
(mockTrustedFolders.setValue as Mock).mockImplementation(() => {
|
||||
isWorkspaceTrustedSpy.mockReturnValue({
|
||||
isTrusted: true,
|
||||
source: 'file',
|
||||
});
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useFolderTrust(mockSettings, onTrustChange, addItem),
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isTrusted).toBe(false);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER);
|
||||
});
|
||||
|
||||
expect(result.current.isRestarting).toBe(true);
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true); // Dialog should stay open
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.isRestarting).toBe(true);
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(true); // Dialog should stay open
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set isRestarting to true when trust status does not change', () => {
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Mock, MockInstance } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useGeminiStream } from './useGeminiStream.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import * as atCommandProcessor from './atCommandProcessor.js';
|
||||
@@ -507,7 +506,7 @@ describe('useGeminiStream', () => {
|
||||
}
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockMarkToolsAsSubmitted).toHaveBeenCalledTimes(1);
|
||||
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -590,7 +589,7 @@ describe('useGeminiStream', () => {
|
||||
}
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockMarkToolsAsSubmitted).toHaveBeenCalledWith(['1']);
|
||||
expect(client.addHistory).toHaveBeenCalledWith({
|
||||
role: 'user',
|
||||
@@ -702,7 +701,7 @@ describe('useGeminiStream', () => {
|
||||
}
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
// The tools should be marked as submitted locally
|
||||
expect(mockMarkToolsAsSubmitted).toHaveBeenCalledWith([
|
||||
'cancel-1',
|
||||
@@ -840,7 +839,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// 5. Wait for submitQuery to be called
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
toolCallResponseParts,
|
||||
expect.any(AbortSignal),
|
||||
@@ -889,7 +888,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Wait for the first part of the response
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
});
|
||||
|
||||
@@ -897,7 +896,7 @@ describe('useGeminiStream', () => {
|
||||
simulateEscapeKeyPress();
|
||||
|
||||
// Verify cancellation message is added
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
@@ -1030,7 +1029,7 @@ describe('useGeminiStream', () => {
|
||||
result.current.submitQuery('long running query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
});
|
||||
|
||||
@@ -1138,7 +1137,7 @@ describe('useGeminiStream', () => {
|
||||
expect(mockCancelAllToolCalls).toHaveBeenCalled();
|
||||
|
||||
// A cancellation message should be added to history
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: 'Request cancelled.',
|
||||
@@ -1167,7 +1166,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('/memory add "test fact"');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockScheduleToolCalls).toHaveBeenCalledWith(
|
||||
[
|
||||
expect.objectContaining({
|
||||
@@ -1194,7 +1193,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('/help');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/help');
|
||||
expect(mockScheduleToolCalls).not.toHaveBeenCalled();
|
||||
expect(mockSendMessageStream).not.toHaveBeenCalled(); // No LLM call made
|
||||
@@ -1215,7 +1214,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('/my-custom-command');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockHandleSlashCommand).toHaveBeenCalledWith(
|
||||
'/my-custom-command',
|
||||
);
|
||||
@@ -1250,7 +1249,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('/emptycmd');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/emptycmd');
|
||||
expect(localMockSendMessageStream).toHaveBeenCalledWith(
|
||||
'',
|
||||
@@ -1268,7 +1267,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('// This is a line comment');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
||||
expect(localMockSendMessageStream).toHaveBeenCalledWith(
|
||||
'// This is a line comment',
|
||||
@@ -1286,7 +1285,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('/* This is a block comment */');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
||||
expect(localMockSendMessageStream).toHaveBeenCalledWith(
|
||||
'/* This is a block comment */',
|
||||
@@ -1324,7 +1323,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('/about');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1401,7 +1400,7 @@ describe('useGeminiStream', () => {
|
||||
}
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockPerformMemoryRefresh).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1457,7 +1456,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// 3. Assertion
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockParseAndFormatApiError).toHaveBeenCalledWith(
|
||||
'Rate limit exceeded',
|
||||
mockAuthType,
|
||||
@@ -1990,7 +1989,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Check that the info message was added
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: 'info',
|
||||
@@ -2050,7 +2049,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Check that the message was added without suggestion
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: 'info',
|
||||
@@ -2105,7 +2104,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Check that the message was added with suggestion
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: 'info',
|
||||
@@ -2161,7 +2160,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Check that onCancelSubmit was called
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(onCancelSubmitSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -2360,7 +2359,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery(`Test ${reason}`);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: 'info',
|
||||
@@ -2487,7 +2486,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Wait for the first response to complete
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'gemini',
|
||||
@@ -2520,7 +2519,7 @@ describe('useGeminiStream', () => {
|
||||
// We can verify this by checking that the LoadingIndicator would not show the previous thought
|
||||
// The actual thought state is internal to the hook, but we can verify the behavior
|
||||
// by ensuring the second response doesn't show the previous thought
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'gemini',
|
||||
@@ -2638,7 +2637,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Verify cancellation message was added
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'info',
|
||||
@@ -2696,7 +2695,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Verify error message was added
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'error',
|
||||
@@ -2747,7 +2746,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('test query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.loopDetectionConfirmationRequest).not.toBeNull();
|
||||
expect(
|
||||
typeof result.current.loopDetectionConfirmationRequest?.onComplete,
|
||||
@@ -2795,7 +2794,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Wait for confirmation request to be set
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.loopDetectionConfirmationRequest).not.toBeNull();
|
||||
});
|
||||
|
||||
@@ -2824,7 +2823,7 @@ describe('useGeminiStream', () => {
|
||||
);
|
||||
|
||||
// Verify that the request was retried
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
||||
expect(mockSendMessageStream).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
@@ -2860,7 +2859,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Wait for confirmation request to be set
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.loopDetectionConfirmationRequest).not.toBeNull();
|
||||
});
|
||||
|
||||
@@ -2907,7 +2906,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('first query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.loopDetectionConfirmationRequest).not.toBeNull();
|
||||
});
|
||||
|
||||
@@ -2957,7 +2956,7 @@ describe('useGeminiStream', () => {
|
||||
await result.current.submitQuery('second query');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.loopDetectionConfirmationRequest).not.toBeNull();
|
||||
});
|
||||
|
||||
@@ -2980,7 +2979,7 @@ describe('useGeminiStream', () => {
|
||||
);
|
||||
|
||||
// Verify that the request was retried
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockSendMessageStream).toHaveBeenCalledTimes(3); // 1st query, 2nd query, retry of 2nd query
|
||||
expect(mockSendMessageStream).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
@@ -3011,7 +3010,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Verify that the content was added to history before the loop detection dialog
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'gemini',
|
||||
@@ -3022,7 +3021,7 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
// Then verify loop detection confirmation request was set
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.loopDetectionConfirmationRequest).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useHistory } from './useHistoryManager.js';
|
||||
import type { HistoryItem } from '../types.js';
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useInputHistory } from './useInputHistory.js';
|
||||
|
||||
describe('useInputHistory', () => {
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useInputHistoryStore } from './useInputHistoryStore.js';
|
||||
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
/// <reference types="vitest/globals" />
|
||||
|
||||
import {
|
||||
describe,
|
||||
it,
|
||||
@@ -17,7 +13,8 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { usePermissionsModifyTrust } from './usePermissionsModifyTrust.js';
|
||||
import { TrustLevel } from '../../config/trustedFolders.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import {
|
||||
usePhraseCycler,
|
||||
WITTY_LOADING_PHRASES,
|
||||
PHRASE_CHANGE_INTERVAL_MS,
|
||||
} from './usePhraseCycler.js';
|
||||
|
||||
describe('usePhraseCycler', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize with a witty phrase when not active and not waiting', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(() => usePhraseCycler(false, false));
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should show "Waiting for user confirmation..." when isWaiting is true', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
|
||||
{ initialProps: { isActive: true, isWaiting: false } },
|
||||
);
|
||||
rerender({ isActive: true, isWaiting: true });
|
||||
expect(result.current).toBe('Waiting for user confirmation...');
|
||||
});
|
||||
|
||||
it('should not cycle phrases if isActive is false and not waiting', () => {
|
||||
const { result } = renderHook(() => usePhraseCycler(false, false));
|
||||
const initialPhrase = result.current;
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS * 2);
|
||||
});
|
||||
expect(result.current).toBe(initialPhrase);
|
||||
});
|
||||
|
||||
it('should cycle through witty phrases when isActive is true and not waiting', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(() => usePhraseCycler(true, false));
|
||||
// Initial phrase should be one of the witty phrases
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
|
||||
});
|
||||
// Phrase should change and be one of the witty phrases
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
|
||||
});
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should reset to a witty phrase when isActive becomes true after being false (and not waiting)', () => {
|
||||
// Ensure there are at least two phrases for this test to be meaningful.
|
||||
if (WITTY_LOADING_PHRASES.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mock Math.random to make the test deterministic.
|
||||
const mockRandomValues = [
|
||||
0.5, // -> witty
|
||||
0, // -> index 0
|
||||
0.5, // -> witty
|
||||
1 / WITTY_LOADING_PHRASES.length, // -> index 1
|
||||
0.5, // -> witty
|
||||
0, // -> index 0
|
||||
];
|
||||
let randomCallCount = 0;
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
const val = mockRandomValues[randomCallCount % mockRandomValues.length];
|
||||
randomCallCount++;
|
||||
return val;
|
||||
});
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
|
||||
{ initialProps: { isActive: false, isWaiting: false } },
|
||||
);
|
||||
|
||||
// Activate
|
||||
rerender({ isActive: true, isWaiting: false });
|
||||
const firstActivePhrase = result.current;
|
||||
expect(WITTY_LOADING_PHRASES).toContain(firstActivePhrase);
|
||||
// With our mock, this should be the first phrase.
|
||||
expect(firstActivePhrase).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
|
||||
});
|
||||
|
||||
// Phrase should change to the second phrase.
|
||||
expect(result.current).not.toBe(firstActivePhrase);
|
||||
expect(result.current).toBe(WITTY_LOADING_PHRASES[1]);
|
||||
|
||||
// Set to inactive - should reset to the default initial phrase
|
||||
rerender({ isActive: false, isWaiting: false });
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
|
||||
// Set back to active - should pick a random witty phrase (which our mock controls)
|
||||
act(() => {
|
||||
rerender({ isActive: true, isWaiting: false });
|
||||
});
|
||||
// The random mock will now return 0, so it should be the first phrase again.
|
||||
expect(result.current).toBe(WITTY_LOADING_PHRASES[0]);
|
||||
});
|
||||
|
||||
it('should clear phrase interval on unmount when active', () => {
|
||||
const { unmount } = renderHook(() => usePhraseCycler(true, false));
|
||||
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
|
||||
unmount();
|
||||
expect(clearIntervalSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should use custom phrases when provided', () => {
|
||||
const customPhrases = ['Custom Phrase 1', 'Custom Phrase 2'];
|
||||
let callCount = 0;
|
||||
const randomMock = vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
const val = callCount % 2;
|
||||
callCount++;
|
||||
return val / customPhrases.length;
|
||||
});
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting, customPhrases: phrases }) =>
|
||||
usePhraseCycler(isActive, isWaiting, phrases),
|
||||
{
|
||||
initialProps: {
|
||||
isActive: true,
|
||||
isWaiting: false,
|
||||
customPhrases,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current).toBe(customPhrases[0]);
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
|
||||
});
|
||||
|
||||
expect(result.current).toBe(customPhrases[1]);
|
||||
|
||||
// Test fallback to default phrases.
|
||||
randomMock.mockRestore();
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
|
||||
rerender({ isActive: true, isWaiting: false, customPhrases: [] });
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should fall back to witty phrases if custom phrases are an empty array', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result } = renderHook(
|
||||
({ isActive, isWaiting, customPhrases: phrases }) =>
|
||||
usePhraseCycler(isActive, isWaiting, phrases),
|
||||
{
|
||||
initialProps: {
|
||||
isActive: true,
|
||||
isWaiting: false,
|
||||
customPhrases: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
|
||||
it('should reset to a witty phrase when transitioning from waiting to active', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { result, rerender } = renderHook(
|
||||
({ isActive, isWaiting }) => usePhraseCycler(isActive, isWaiting),
|
||||
{ initialProps: { isActive: true, isWaiting: false } },
|
||||
);
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
|
||||
// Cycle to a different phrase (potentially)
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(PHRASE_CHANGE_INTERVAL_MS);
|
||||
});
|
||||
if (WITTY_LOADING_PHRASES.length > 1) {
|
||||
// This check is probabilistic with random selection
|
||||
}
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
|
||||
// Go to waiting state
|
||||
rerender({ isActive: false, isWaiting: true });
|
||||
expect(result.current).toBe('Waiting for user confirmation...');
|
||||
|
||||
// Go back to active cycling - should pick a random witty phrase
|
||||
rerender({ isActive: true, isWaiting: false });
|
||||
expect(WITTY_LOADING_PHRASES).toContain(result.current);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { render } from 'ink-testing-library';
|
||||
import { Text } from 'ink';
|
||||
import {
|
||||
usePhraseCycler,
|
||||
WITTY_LOADING_PHRASES,
|
||||
PHRASE_CHANGE_INTERVAL_MS,
|
||||
} from './usePhraseCycler.js';
|
||||
|
||||
// Test component to consume the hook
|
||||
const TestComponent = ({
|
||||
isActive,
|
||||
isWaiting,
|
||||
customPhrases,
|
||||
}: {
|
||||
isActive: boolean;
|
||||
isWaiting: boolean;
|
||||
customPhrases?: string[];
|
||||
}) => {
|
||||
const phrase = usePhraseCycler(isActive, isWaiting, customPhrases);
|
||||
return <Text>{phrase}</Text>;
|
||||
};
|
||||
|
||||
describe('usePhraseCycler', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize with a witty phrase when not active and not waiting', () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { lastFrame } = render(
|
||||
<TestComponent isActive={false} isWaiting={false} />,
|
||||
);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
});
|
||||
|
||||
it('should show "Waiting for user confirmation..." when isWaiting is true', async () => {
|
||||
const { lastFrame, rerender } = render(
|
||||
<TestComponent isActive={true} isWaiting={false} />,
|
||||
);
|
||||
rerender(<TestComponent isActive={true} isWaiting={true} />);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(lastFrame()).toBe('Waiting for user confirmation...');
|
||||
});
|
||||
|
||||
it('should not cycle phrases if isActive is false and not waiting', async () => {
|
||||
const { lastFrame } = render(
|
||||
<TestComponent isActive={false} isWaiting={false} />,
|
||||
);
|
||||
const initialPhrase = lastFrame();
|
||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS * 2);
|
||||
expect(lastFrame()).toBe(initialPhrase);
|
||||
});
|
||||
|
||||
it('should cycle through witty phrases when isActive is true and not waiting', async () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { lastFrame } = render(
|
||||
<TestComponent isActive={true} isWaiting={false} />,
|
||||
);
|
||||
// Initial phrase should be one of the witty phrases
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
||||
});
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
|
||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
});
|
||||
|
||||
it('should reset to a phrase when isActive becomes true after being false', async () => {
|
||||
const customPhrases = ['Phrase A', 'Phrase B'];
|
||||
let callCount = 0;
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => {
|
||||
// For custom phrases, only 1 Math.random call is made per update.
|
||||
// 0 -> index 0 ('Phrase A')
|
||||
// 0.99 -> index 1 ('Phrase B')
|
||||
const val = callCount % 2 === 0 ? 0 : 0.99;
|
||||
callCount++;
|
||||
return val;
|
||||
});
|
||||
|
||||
const { lastFrame, rerender } = render(
|
||||
<TestComponent
|
||||
isActive={false}
|
||||
isWaiting={false}
|
||||
customPhrases={customPhrases}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Activate -> callCount 0 -> returns 0 -> 'Phrase A'
|
||||
rerender(
|
||||
<TestComponent
|
||||
isActive={true}
|
||||
isWaiting={false}
|
||||
customPhrases={customPhrases}
|
||||
/>,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(lastFrame()).toBe('Phrase A');
|
||||
|
||||
// Interval -> callCount 1 -> returns 0.99 -> 'Phrase B'
|
||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
||||
expect(lastFrame()).toBe('Phrase B');
|
||||
|
||||
// Deactivate -> resets to customPhrases[0] -> 'Phrase A'
|
||||
rerender(
|
||||
<TestComponent
|
||||
isActive={false}
|
||||
isWaiting={false}
|
||||
customPhrases={customPhrases}
|
||||
/>,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(lastFrame()).toBe('Phrase A');
|
||||
|
||||
// Activate again -> callCount 2 -> returns 0 -> 'Phrase A'
|
||||
rerender(
|
||||
<TestComponent
|
||||
isActive={true}
|
||||
isWaiting={false}
|
||||
customPhrases={customPhrases}
|
||||
/>,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(lastFrame()).toBe('Phrase A');
|
||||
});
|
||||
|
||||
it('should clear phrase interval on unmount when active', () => {
|
||||
const { unmount } = render(
|
||||
<TestComponent isActive={true} isWaiting={false} />,
|
||||
);
|
||||
const clearIntervalSpy = vi.spyOn(global, 'clearInterval');
|
||||
unmount();
|
||||
expect(clearIntervalSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('should use custom phrases when provided', async () => {
|
||||
const customPhrases = ['Custom Phrase 1', 'Custom Phrase 2'];
|
||||
const randomMock = vi.spyOn(Math, 'random');
|
||||
randomMock.mockReturnValue(0);
|
||||
|
||||
const { lastFrame, rerender } = render(
|
||||
<TestComponent
|
||||
isActive={true}
|
||||
isWaiting={false}
|
||||
customPhrases={customPhrases}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toBe('Custom Phrase 1');
|
||||
|
||||
randomMock.mockReturnValue(0.99);
|
||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS + 100);
|
||||
|
||||
expect(lastFrame()).toBe('Custom Phrase 2');
|
||||
|
||||
// Test fallback to default phrases.
|
||||
randomMock.mockRestore();
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.5); // Always witty
|
||||
|
||||
rerender(
|
||||
<TestComponent isActive={true} isWaiting={false} customPhrases={[]} />,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
});
|
||||
|
||||
it('should fall back to witty phrases if custom phrases are an empty array', async () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { lastFrame } = render(
|
||||
<TestComponent isActive={true} isWaiting={false} customPhrases={[]} />,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
});
|
||||
|
||||
it('should reset to a witty phrase when transitioning from waiting to active', async () => {
|
||||
vi.spyOn(Math, 'random').mockImplementation(() => 0.5); // Always witty
|
||||
const { lastFrame, rerender } = render(
|
||||
<TestComponent isActive={true} isWaiting={false} />,
|
||||
);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
|
||||
// Cycle to a different phrase (potentially)
|
||||
await vi.advanceTimersByTimeAsync(PHRASE_CHANGE_INTERVAL_MS);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
|
||||
// Go to waiting state
|
||||
rerender(<TestComponent isActive={false} isWaiting={true} />);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(lastFrame()).toBe('Waiting for user confirmation...');
|
||||
|
||||
// Go back to active cycling - should pick a random witty phrase
|
||||
rerender(<TestComponent isActive={true} isWaiting={false} />);
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
expect(WITTY_LOADING_PHRASES).toContain(lastFrame());
|
||||
});
|
||||
});
|
||||
@@ -4,8 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import {
|
||||
vi,
|
||||
describe,
|
||||
@@ -15,7 +13,8 @@ import {
|
||||
afterEach,
|
||||
type Mock,
|
||||
} from 'vitest';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import {
|
||||
type Config,
|
||||
type FallbackModelHandler,
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { CoreToolScheduler } from '@google/gemini-cli-core';
|
||||
import type { Config } from '@google/gemini-cli-core';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useReactToolScheduler } from './useReactToolScheduler.js';
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useReverseSearchCompletion } from './useReverseSearchCompletion.js';
|
||||
import { useTextBuffer } from '../components/shared/text-buffer.js';
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useShellHistory } from './useShellHistory.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import * as crypto from 'node:crypto';
|
||||
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||
|
||||
@@ -19,7 +18,14 @@ vi.mock('node:fs/promises', () => ({
|
||||
writeFile: vi.fn(),
|
||||
mkdir: vi.fn(),
|
||||
}));
|
||||
vi.mock('node:os');
|
||||
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||
vi.mock('node:os', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:os')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: mockHomedir,
|
||||
};
|
||||
});
|
||||
vi.mock('node:crypto');
|
||||
vi.mock('node:fs', async (importOriginal) => {
|
||||
const actualFs = await importOriginal<typeof import('node:fs')>();
|
||||
@@ -33,6 +39,9 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
const path = await import('node:path');
|
||||
class Storage {
|
||||
static getGlobalSettingsPath(): string {
|
||||
return '/test/home/.gemini/settings.json';
|
||||
}
|
||||
getProjectTempDir(): string {
|
||||
return path.join('/test/home/', actual.GEMINI_DIR, 'tmp', 'mocked_hash');
|
||||
}
|
||||
@@ -68,7 +77,6 @@ const MOCKED_HISTORY_FILE = path.join(MOCKED_HISTORY_DIR, 'shell_history');
|
||||
|
||||
describe('useShellHistory', () => {
|
||||
const mockedFs = vi.mocked(fs);
|
||||
const mockedOs = vi.mocked(os);
|
||||
const mockedCrypto = vi.mocked(crypto);
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -77,7 +85,7 @@ describe('useShellHistory', () => {
|
||||
mockedFs.readFile.mockResolvedValue('');
|
||||
mockedFs.writeFile.mockResolvedValue(undefined);
|
||||
mockedFs.mkdir.mockResolvedValue(undefined);
|
||||
mockedOs.homedir.mockReturnValue(MOCKED_HOME_DIR);
|
||||
mockHomedir.mockReturnValue(MOCKED_HOME_DIR);
|
||||
|
||||
const hashMock = {
|
||||
update: vi.fn().mockReturnThis(),
|
||||
@@ -90,7 +98,7 @@ describe('useShellHistory', () => {
|
||||
mockedFs.readFile.mockResolvedValue('cmd1\ncmd2');
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalledWith(
|
||||
MOCKED_HISTORY_FILE,
|
||||
'utf-8',
|
||||
@@ -113,7 +121,7 @@ describe('useShellHistory', () => {
|
||||
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -128,13 +136,15 @@ describe('useShellHistory', () => {
|
||||
it('should add a command and write to the history file', async () => {
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
|
||||
await waitFor(() => expect(mockedFs.readFile).toHaveBeenCalled());
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.addCommandToHistory('new_command');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.mkdir).toHaveBeenCalledWith(MOCKED_HISTORY_DIR, {
|
||||
recursive: true,
|
||||
});
|
||||
@@ -156,7 +166,9 @@ describe('useShellHistory', () => {
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
|
||||
// Wait for history to be loaded: ['cmd3', 'cmd2', 'cmd1']
|
||||
await waitFor(() => expect(mockedFs.readFile).toHaveBeenCalled());
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
let command: string | null = null;
|
||||
|
||||
@@ -200,7 +212,10 @@ describe('useShellHistory', () => {
|
||||
|
||||
it('should not add empty or whitespace-only commands to history', async () => {
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
await waitFor(() => expect(mockedFs.readFile).toHaveBeenCalled());
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.addCommandToHistory(' ');
|
||||
@@ -214,14 +229,18 @@ describe('useShellHistory', () => {
|
||||
mockedFs.readFile.mockResolvedValue(oldCommands.join('\n'));
|
||||
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
await waitFor(() => expect(mockedFs.readFile).toHaveBeenCalled());
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.addCommandToHistory('new_cmd');
|
||||
});
|
||||
|
||||
// Wait for the async write to happen and then inspect the arguments.
|
||||
await waitFor(() => expect(mockedFs.writeFile).toHaveBeenCalled());
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.writeFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// The hook stores history newest-first.
|
||||
// Initial state: ['old_cmd_119', ..., 'old_cmd_0']
|
||||
@@ -240,15 +259,20 @@ describe('useShellHistory', () => {
|
||||
const { result } = renderHook(() => useShellHistory(MOCKED_PROJECT_ROOT));
|
||||
|
||||
// Initial state: ['cmd3', 'cmd2', 'cmd1']
|
||||
await waitFor(() => expect(mockedFs.readFile).toHaveBeenCalled());
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.addCommandToHistory('cmd1');
|
||||
});
|
||||
|
||||
// After re-adding 'cmd1': ['cmd1', 'cmd3', 'cmd2']
|
||||
// Written to file (reversed): ['cmd2', 'cmd3', 'cmd1']
|
||||
await waitFor(() => expect(mockedFs.writeFile).toHaveBeenCalled());
|
||||
expect(mockedFs.readFile).toHaveBeenCalled();
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(mockedFs.writeFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const writtenContent = mockedFs.writeFile.mock.calls[0][1] as string;
|
||||
const writtenLines = writtenContent.split('\n');
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import { useSlashCompletion } from './useSlashCompletion.js';
|
||||
import type { CommandContext, SlashCommand } from '../commands/types.js';
|
||||
import { CommandKind } from '../commands/types.js';
|
||||
@@ -205,10 +203,12 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current.suggestions.length).toBe(slashCommands.length);
|
||||
expect(result.current.suggestions.map((s) => s.label)).toEqual(
|
||||
expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']),
|
||||
);
|
||||
await vi.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']),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter commands based on partial input', async () => {
|
||||
@@ -224,7 +224,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toEqual([
|
||||
{
|
||||
label: 'memory',
|
||||
@@ -253,7 +253,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toEqual([
|
||||
{
|
||||
label: 'stats',
|
||||
@@ -369,8 +369,10 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current.suggestions.length).toBe(1);
|
||||
expect(result.current.suggestions[0].label).toBe('visible');
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions.length).toBe(1);
|
||||
expect(result.current.suggestions[0].label).toBe('visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -390,29 +392,31 @@ describe('useSlashCompletion', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/memory',
|
||||
'/memory ',
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current.suggestions).toHaveLength(2);
|
||||
expect(result.current.suggestions).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
label: 'show',
|
||||
value: 'show',
|
||||
description: 'Show memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
{
|
||||
label: 'add',
|
||||
value: 'add',
|
||||
description: 'Add to memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
]),
|
||||
);
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toHaveLength(2);
|
||||
expect(result.current.suggestions).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
label: 'show',
|
||||
value: 'show',
|
||||
description: 'Show memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
{
|
||||
label: 'add',
|
||||
value: 'add',
|
||||
description: 'Add to memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should suggest all sub-commands when the query ends with the parent command and a space', async () => {
|
||||
@@ -435,23 +439,25 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
expect(result.current.suggestions).toHaveLength(2);
|
||||
expect(result.current.suggestions).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
label: 'show',
|
||||
value: 'show',
|
||||
description: 'Show memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
{
|
||||
label: 'add',
|
||||
value: 'add',
|
||||
description: 'Add to memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
]),
|
||||
);
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toHaveLength(2);
|
||||
expect(result.current.suggestions).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
label: 'show',
|
||||
value: 'show',
|
||||
description: 'Show memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
{
|
||||
label: 'add',
|
||||
value: 'add',
|
||||
description: 'Add to memory',
|
||||
commandKind: CommandKind.BUILT_IN,
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter sub-commands by prefix', async () => {
|
||||
@@ -474,7 +480,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toEqual([
|
||||
{
|
||||
label: 'add',
|
||||
@@ -547,7 +553,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockCompletionFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
invocation: {
|
||||
@@ -560,7 +566,7 @@ describe('useSlashCompletion', () => {
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.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' },
|
||||
@@ -596,7 +602,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(mockCompletionFn).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
invocation: {
|
||||
@@ -609,7 +615,7 @@ describe('useSlashCompletion', () => {
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
@@ -639,9 +645,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.suggestions).toHaveLength(0);
|
||||
});
|
||||
expect(result.current.suggestions).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -714,7 +718,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toEqual([
|
||||
{
|
||||
label: 'summarize',
|
||||
@@ -795,7 +799,7 @@ describe('useSlashCompletion', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
await vi.waitFor(() => {
|
||||
expect(result.current.suggestions).toEqual([
|
||||
{
|
||||
label: 'custom-script',
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/** @vitest-environment jsdom */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Mock } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { renderHook } from '../../test-utils/render.js';
|
||||
import {
|
||||
useReactToolScheduler,
|
||||
mapToDisplay,
|
||||
@@ -38,7 +37,14 @@ import { ToolCallStatus } from '../types.js';
|
||||
|
||||
// Mocks
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual('@google/gemini-cli-core');
|
||||
const actual = await vi.importActual<any>('@google/gemini-cli-core');
|
||||
// Patch CoreToolScheduler to have cancelAll if it's missing in the test environment
|
||||
if (
|
||||
actual.CoreToolScheduler &&
|
||||
!actual.CoreToolScheduler.prototype.cancelAll
|
||||
) {
|
||||
actual.CoreToolScheduler.prototype.cancelAll = vi.fn();
|
||||
}
|
||||
return {
|
||||
...actual,
|
||||
ToolRegistry: vi.fn(),
|
||||
@@ -153,13 +159,13 @@ describe('useReactToolScheduler in YOLO Mode', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync(); // Process validation
|
||||
await vi.advanceTimersByTimeAsync(0); // Process validation
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync(); // Process scheduling
|
||||
await vi.advanceTimersByTimeAsync(0); // Process scheduling
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync(); // Process execution
|
||||
await vi.advanceTimersByTimeAsync(0); // Process execution
|
||||
});
|
||||
|
||||
// Check that execute WAS called
|
||||
@@ -270,13 +276,13 @@ describe('useReactToolScheduler', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(mockTool.execute).toHaveBeenCalledWith(request.args);
|
||||
@@ -341,13 +347,13 @@ describe('useReactToolScheduler', () => {
|
||||
|
||||
// Let the new call finish.
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
expect(onComplete).toHaveBeenCalled();
|
||||
});
|
||||
@@ -375,11 +381,11 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
}); // validation
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
}); // scheduling
|
||||
await vi.advanceTimersByTimeAsync(0); // Process scheduling
|
||||
});
|
||||
|
||||
// At this point, the tool is 'executing' and waiting on the promise.
|
||||
expect(result.current[0][0].status).toBe('executing');
|
||||
@@ -390,7 +396,7 @@ describe('useReactToolScheduler', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith([
|
||||
@@ -423,10 +429,10 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(completedToolCalls).toHaveLength(1);
|
||||
@@ -462,10 +468,10 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(completedToolCalls).toHaveLength(1);
|
||||
@@ -497,13 +503,13 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(completedToolCalls).toHaveLength(1);
|
||||
@@ -532,7 +538,7 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
const waitingCall = result.current[0][0] as any;
|
||||
@@ -545,13 +551,13 @@ describe('useReactToolScheduler', () => {
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith(
|
||||
@@ -590,7 +596,7 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
const waitingCall = result.current[0][0] as any;
|
||||
@@ -602,10 +608,10 @@ describe('useReactToolScheduler', () => {
|
||||
await capturedOnConfirmForTest?.(ToolConfirmationOutcome.Cancel);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(mockOnUserConfirmForToolConfirmation).toHaveBeenCalledWith(
|
||||
@@ -665,7 +671,7 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(liveUpdateFn).toBeDefined();
|
||||
@@ -675,14 +681,14 @@ describe('useReactToolScheduler', () => {
|
||||
liveUpdateFn?.('Live output 1');
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
liveUpdateFn?.('Live output 2');
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
@@ -692,10 +698,10 @@ describe('useReactToolScheduler', () => {
|
||||
} as ToolResult);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(onComplete).toHaveBeenCalledWith([
|
||||
@@ -753,16 +759,16 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(requests, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
expect(onComplete).toHaveBeenCalledTimes(1);
|
||||
@@ -845,16 +851,16 @@ describe('useReactToolScheduler', () => {
|
||||
schedule(request1, new AbortController().signal);
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
schedule(request2, new AbortController().signal);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
});
|
||||
expect(onComplete).toHaveBeenCalledWith([
|
||||
@@ -867,9 +873,9 @@ describe('useReactToolScheduler', () => {
|
||||
// Wait for request2 to complete
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(50);
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
await act(async () => {
|
||||
await vi.runAllTimersAsync();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
});
|
||||
expect(onComplete).toHaveBeenCalledWith([
|
||||
|
||||
Reference in New Issue
Block a user