@google/gemini-cli 0.18.0-nightly.20251118.7cc5234b9 → 0.18.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1 -0
- package/dist/package.json +4 -4
- package/dist/src/commands/extensions/disable.test.js +19 -13
- package/dist/src/commands/extensions/disable.test.js.map +1 -1
- package/dist/src/commands/extensions/enable.test.js +16 -9
- package/dist/src/commands/extensions/enable.test.js.map +1 -1
- package/dist/src/commands/extensions/link.test.js +18 -11
- package/dist/src/commands/extensions/link.test.js.map +1 -1
- package/dist/src/commands/extensions/list.test.js +20 -13
- package/dist/src/commands/extensions/list.test.js.map +1 -1
- package/dist/src/commands/extensions/uninstall.test.js +26 -18
- package/dist/src/commands/extensions/uninstall.test.js.map +1 -1
- package/dist/src/commands/extensions/update.test.js +20 -13
- package/dist/src/commands/extensions/update.test.js.map +1 -1
- package/dist/src/commands/mcp/add.test.js +8 -3
- package/dist/src/commands/mcp/add.test.js.map +1 -1
- package/dist/src/commands/mcp/list.test.js +1 -6
- package/dist/src/commands/mcp/list.test.js.map +1 -1
- package/dist/src/commands/mcp/remove.test.js +23 -12
- package/dist/src/commands/mcp/remove.test.js.map +1 -1
- package/dist/src/config/config.d.ts +2 -1
- package/dist/src/config/config.js +38 -14
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +64 -10
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension.test.js +131 -125
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +2 -2
- package/dist/src/config/settingsSchema.js +3 -3
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/config/settingsSchema.test.js +1 -1
- package/dist/src/config/settingsSchema.test.js.map +1 -1
- package/dist/src/gemini.js +65 -7
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini.test.js +55 -15
- package/dist/src/gemini.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/nonInteractiveCli.js +4 -1
- package/dist/src/nonInteractiveCli.js.map +1 -1
- package/dist/src/nonInteractiveCli.test.js +2 -2
- package/dist/src/nonInteractiveCli.test.js.map +1 -1
- package/dist/src/services/CommandService.test.js +3 -2
- package/dist/src/services/CommandService.test.js.map +1 -1
- package/dist/src/services/McpPromptLoader.js +5 -3
- package/dist/src/services/McpPromptLoader.js.map +1 -1
- package/dist/src/services/McpPromptLoader.test.js +29 -1
- package/dist/src/services/McpPromptLoader.test.js.map +1 -1
- package/dist/src/test-utils/render.d.ts +26 -2
- package/dist/src/test-utils/render.js +76 -3
- package/dist/src/test-utils/render.js.map +1 -1
- package/dist/src/ui/AppContainer.js +57 -32
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +169 -102
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/auth/AuthDialog.test.js +2 -2
- package/dist/src/ui/auth/AuthDialog.test.js.map +1 -1
- package/dist/src/ui/commands/aboutCommand.js +8 -1
- package/dist/src/ui/commands/aboutCommand.js.map +1 -1
- package/dist/src/ui/commands/aboutCommand.test.js +4 -0
- package/dist/src/ui/commands/aboutCommand.test.js.map +1 -1
- package/dist/src/ui/commands/clearCommand.js +12 -0
- package/dist/src/ui/commands/clearCommand.js.map +1 -1
- package/dist/src/ui/commands/clearCommand.test.js +5 -0
- package/dist/src/ui/commands/clearCommand.test.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.js +1 -0
- package/dist/src/ui/commands/memoryCommand.js.map +1 -1
- package/dist/src/ui/commands/memoryCommand.test.js +3 -0
- package/dist/src/ui/commands/memoryCommand.test.js.map +1 -1
- package/dist/src/ui/commands/setupGithubCommand.d.ts +1 -0
- package/dist/src/ui/commands/setupGithubCommand.js +82 -45
- package/dist/src/ui/commands/setupGithubCommand.js.map +1 -1
- package/dist/src/ui/commands/setupGithubCommand.test.js +35 -5
- package/dist/src/ui/commands/setupGithubCommand.test.js.map +1 -1
- package/dist/src/ui/components/AboutBox.d.ts +1 -0
- package/dist/src/ui/components/AboutBox.js +1 -1
- package/dist/src/ui/components/AboutBox.js.map +1 -1
- package/dist/src/ui/components/AppHeader.js +3 -8
- package/dist/src/ui/components/AppHeader.js.map +1 -1
- package/dist/src/ui/components/Banner.d.ts +4 -2
- package/dist/src/ui/components/Banner.js +18 -4
- package/dist/src/ui/components/Banner.js.map +1 -1
- package/dist/src/ui/components/DebugProfiler.d.ts +1 -0
- package/dist/src/ui/components/DebugProfiler.js +23 -34
- package/dist/src/ui/components/DebugProfiler.js.map +1 -1
- package/dist/src/ui/components/DebugProfiler.test.js +1 -0
- package/dist/src/ui/components/DebugProfiler.test.js.map +1 -1
- package/dist/src/ui/components/DialogManager.js +1 -1
- package/dist/src/ui/components/DialogManager.js.map +1 -1
- package/dist/src/ui/components/GradientRegression.test.js +22 -1
- package/dist/src/ui/components/GradientRegression.test.js.map +1 -1
- package/dist/src/ui/components/Header.test.js +27 -2
- package/dist/src/ui/components/Header.test.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +16 -24
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +84 -24
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.js +2 -2
- package/dist/src/ui/components/ModelDialog.js.map +1 -1
- package/dist/src/ui/components/SettingsDialog.d.ts +3 -1
- package/dist/src/ui/components/SettingsDialog.js +4 -1
- package/dist/src/ui/components/SettingsDialog.js.map +1 -1
- package/dist/src/ui/components/ThemedGradient.js +2 -1
- package/dist/src/ui/components/ThemedGradient.js.map +1 -1
- package/dist/src/ui/components/messages/ShellToolMessage.d.ts +14 -0
- package/dist/src/ui/components/messages/ShellToolMessage.js +76 -0
- package/dist/src/ui/components/messages/ShellToolMessage.js.map +1 -0
- package/dist/src/ui/components/messages/ShellToolMessage.test.d.ts +6 -0
- package/dist/src/ui/components/messages/ShellToolMessage.test.js +123 -0
- package/dist/src/ui/components/messages/ShellToolMessage.test.js.map +1 -0
- package/dist/src/ui/components/messages/ToolGroupMessage.js +19 -1
- package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolMessage.d.ts +3 -6
- package/dist/src/ui/components/messages/ToolMessage.js +5 -121
- package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolMessage.test.js +13 -5
- package/dist/src/ui/components/messages/ToolMessage.test.js.map +1 -1
- package/dist/src/ui/components/messages/ToolResultDisplay.d.ts +13 -0
- package/dist/src/ui/components/messages/ToolResultDisplay.js +54 -0
- package/dist/src/ui/components/messages/ToolResultDisplay.js.map +1 -0
- package/dist/src/ui/components/messages/ToolShared.d.ts +23 -0
- package/dist/src/ui/components/messages/ToolShared.js +40 -0
- package/dist/src/ui/components/messages/ToolShared.js.map +1 -0
- package/dist/src/ui/components/shared/text-buffer.js +4 -1
- package/dist/src/ui/components/shared/text-buffer.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.js +21 -11
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +29 -10
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/UIActionsContext.d.ts +1 -0
- package/dist/src/ui/contexts/UIActionsContext.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.js +8 -4
- package/dist/src/ui/hooks/shellCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/shellCommandProcessor.test.js +4 -3
- package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.d.ts +1 -1
- package/dist/src/ui/hooks/slashCommandProcessor.js +5 -3
- package/dist/src/ui/hooks/slashCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/useBracketedPaste.js +3 -2
- package/dist/src/ui/hooks/useBracketedPaste.js.map +1 -1
- package/dist/src/ui/hooks/useCommandCompletion.test.js +5 -5
- package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.d.ts +0 -1
- package/dist/src/ui/hooks/useConsoleMessages.js +26 -1
- package/dist/src/ui/hooks/useConsoleMessages.js.map +1 -1
- package/dist/src/ui/hooks/useConsoleMessages.test.js +37 -5
- package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
- package/dist/src/ui/hooks/useEditorSettings.d.ts +2 -2
- package/dist/src/ui/hooks/useEditorSettings.js.map +1 -1
- package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.d.ts +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +48 -10
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +20 -21
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useHistoryManager.d.ts +5 -2
- package/dist/src/ui/hooks/useHistoryManager.js +39 -3
- package/dist/src/ui/hooks/useHistoryManager.js.map +1 -1
- package/dist/src/ui/hooks/useIncludeDirsTrust.js +0 -1
- package/dist/src/ui/hooks/useIncludeDirsTrust.js.map +1 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js +4 -1
- package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
- package/dist/src/ui/hooks/useKittyKeyboardProtocol.d.ts +0 -1
- package/dist/src/ui/hooks/useKittyKeyboardProtocol.js +1 -2
- package/dist/src/ui/hooks/useKittyKeyboardProtocol.js.map +1 -1
- package/dist/src/ui/hooks/useMouseClick.d.ts +12 -0
- package/dist/src/ui/hooks/useMouseClick.js +28 -0
- package/dist/src/ui/hooks/useMouseClick.js.map +1 -0
- package/dist/src/ui/hooks/useMouseClick.test.d.ts +6 -0
- package/dist/src/ui/hooks/useMouseClick.test.js +59 -0
- package/dist/src/ui/hooks/useMouseClick.test.js.map +1 -0
- package/dist/src/ui/hooks/useReactToolScheduler.d.ts +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.js +1 -8
- package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
- package/dist/src/ui/hooks/useReactToolScheduler.test.js +1 -8
- package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -1
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js +18 -12
- package/dist/src/ui/hooks/useReverseSearchCompletion.test.js.map +1 -1
- package/dist/src/ui/hooks/useSessionBrowser.js +13 -4
- package/dist/src/ui/hooks/useSessionBrowser.js.map +1 -1
- package/dist/src/ui/hooks/useSessionBrowser.test.js +37 -0
- package/dist/src/ui/hooks/useSessionBrowser.test.js.map +1 -1
- package/dist/src/ui/hooks/useSessionResume.js +1 -1
- package/dist/src/ui/hooks/useSessionResume.js.map +1 -1
- package/dist/src/ui/hooks/useSessionResume.test.js +4 -4
- package/dist/src/ui/hooks/useSessionResume.test.js.map +1 -1
- package/dist/src/ui/hooks/useThemeCommand.d.ts +2 -2
- package/dist/src/ui/hooks/useThemeCommand.js.map +1 -1
- package/dist/src/ui/hooks/useToolScheduler.test.js +2 -2
- package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
- package/dist/src/ui/themes/theme-manager.test.js +2 -1
- package/dist/src/ui/themes/theme-manager.test.js.map +1 -1
- package/dist/src/ui/types.d.ts +2 -0
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/ui/utils/ConsolePatcher.js +6 -9
- package/dist/src/ui/utils/ConsolePatcher.js.map +1 -1
- package/dist/src/ui/utils/kittyProtocolDetector.d.ts +6 -2
- package/dist/src/ui/utils/kittyProtocolDetector.js +48 -43
- package/dist/src/ui/utils/kittyProtocolDetector.js.map +1 -1
- package/dist/src/ui/utils/mouse.js +3 -3
- package/dist/src/ui/utils/mouse.js.map +1 -1
- package/dist/src/utils/cleanup.d.ts +2 -0
- package/dist/src/utils/cleanup.js +16 -0
- package/dist/src/utils/cleanup.js.map +1 -1
- package/dist/src/utils/errors.js +10 -0
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/events.d.ts +0 -2
- package/dist/src/utils/events.js +0 -1
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/installationInfo.test.js +8 -9
- package/dist/src/utils/installationInfo.test.js.map +1 -1
- package/dist/src/utils/sessionCleanup.test.js +4 -2
- package/dist/src/utils/sessionCleanup.test.js.map +1 -1
- package/dist/src/utils/sessionUtils.d.ts +5 -0
- package/dist/src/utils/sessionUtils.js +6 -1
- package/dist/src/utils/sessionUtils.js.map +1 -1
- package/dist/src/utils/sessions.test.d.ts +6 -0
- package/dist/src/utils/sessions.test.js +516 -0
- package/dist/src/utils/sessions.test.js.map +1 -0
- package/dist/src/utils/stdio.d.ts +32 -0
- package/dist/src/utils/stdio.js +85 -0
- package/dist/src/utils/stdio.js.map +1 -0
- package/dist/src/utils/stdio.test.d.ts +6 -0
- package/dist/src/utils/stdio.test.js +47 -0
- package/dist/src/utils/stdio.test.js.map +1 -0
- package/dist/src/validateNonInterActiveAuth.js +2 -0
- package/dist/src/validateNonInterActiveAuth.js.map +1 -1
- package/dist/src/validateNonInterActiveAuth.test.js +10 -6
- package/dist/src/validateNonInterActiveAuth.test.js.map +1 -1
- package/dist/src/zed-integration/schema.d.ts +30 -30
- package/dist/src/zed-integration/zedIntegration.d.ts +0 -7
- package/dist/src/zed-integration/zedIntegration.js +9 -21
- package/dist/src/zed-integration/zedIntegration.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/dist/google-gemini-cli-0.18.0-nightly.20251118.86828bb56.tgz +0 -0
|
@@ -6,6 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
8
8
|
import { render } from '../test-utils/render.js';
|
|
9
|
+
import { waitFor } from '../test-utils/async.js';
|
|
9
10
|
import { cleanup } from 'ink-testing-library';
|
|
10
11
|
import { act, useContext } from 'react';
|
|
11
12
|
import { AppContainer } from './AppContainer.js';
|
|
@@ -15,26 +16,34 @@ import { makeFakeConfig, CoreEvent, AuthType, } from '@google/gemini-cli-core';
|
|
|
15
16
|
const mockCoreEvents = vi.hoisted(() => ({
|
|
16
17
|
on: vi.fn(),
|
|
17
18
|
off: vi.fn(),
|
|
18
|
-
|
|
19
|
+
drainBacklogs: vi.fn(),
|
|
19
20
|
emit: vi.fn(),
|
|
20
21
|
}));
|
|
22
|
+
// Mock IdeClient
|
|
23
|
+
const mockIdeClient = vi.hoisted(() => ({
|
|
24
|
+
getInstance: vi.fn().mockReturnValue(new Promise(() => { })),
|
|
25
|
+
}));
|
|
26
|
+
// Mock stdout
|
|
27
|
+
const mocks = vi.hoisted(() => ({
|
|
28
|
+
mockStdout: { write: vi.fn() },
|
|
29
|
+
}));
|
|
21
30
|
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
|
22
31
|
const actual = await importOriginal();
|
|
23
32
|
return {
|
|
24
33
|
...actual,
|
|
25
34
|
coreEvents: mockCoreEvents,
|
|
35
|
+
IdeClient: mockIdeClient,
|
|
26
36
|
};
|
|
27
37
|
});
|
|
28
38
|
import { useQuotaAndFallback } from './hooks/useQuotaAndFallback.js';
|
|
29
39
|
import { UIStateContext } from './contexts/UIStateContext.js';
|
|
30
40
|
import { UIActionsContext, } from './contexts/UIActionsContext.js';
|
|
31
41
|
// Mock useStdout to capture terminal title writes
|
|
32
|
-
let mockStdout;
|
|
33
42
|
vi.mock('ink', async (importOriginal) => {
|
|
34
43
|
const actual = await importOriginal();
|
|
35
44
|
return {
|
|
36
45
|
...actual,
|
|
37
|
-
useStdout: () => ({ stdout: mockStdout }),
|
|
46
|
+
useStdout: () => ({ stdout: mocks.mockStdout }),
|
|
38
47
|
measureElement: vi.fn(),
|
|
39
48
|
};
|
|
40
49
|
});
|
|
@@ -86,6 +95,15 @@ vi.mock('./utils/mouse.js', () => ({
|
|
|
86
95
|
enableMouseEvents: vi.fn(),
|
|
87
96
|
disableMouseEvents: vi.fn(),
|
|
88
97
|
}));
|
|
98
|
+
vi.mock('../utils/stdio.js', () => ({
|
|
99
|
+
writeToStdout: vi.fn((...args) => process.stdout.write(...args)),
|
|
100
|
+
writeToStderr: vi.fn((...args) => process.stderr.write(...args)),
|
|
101
|
+
patchStdio: vi.fn(() => () => { }),
|
|
102
|
+
createInkStdio: vi.fn(() => ({
|
|
103
|
+
stdout: process.stdout,
|
|
104
|
+
stderr: process.stderr,
|
|
105
|
+
})),
|
|
106
|
+
}));
|
|
89
107
|
import { useHistory } from './hooks/useHistoryManager.js';
|
|
90
108
|
import { useThemeCommand } from './hooks/useThemeCommand.js';
|
|
91
109
|
import { useAuthCommand } from './auth/useAuth.js';
|
|
@@ -112,6 +130,7 @@ import { useTerminalSize } from './hooks/useTerminalSize.js';
|
|
|
112
130
|
import { ShellExecutionService } from '@google/gemini-cli-core';
|
|
113
131
|
import {} from '../config/extension-manager.js';
|
|
114
132
|
import { enableMouseEvents, disableMouseEvents } from './utils/mouse.js';
|
|
133
|
+
import { writeToStdout } from '../utils/stdio.js';
|
|
115
134
|
describe('AppContainer State Management', () => {
|
|
116
135
|
let mockConfig;
|
|
117
136
|
let mockSettings;
|
|
@@ -147,7 +166,7 @@ describe('AppContainer State Management', () => {
|
|
|
147
166
|
beforeEach(() => {
|
|
148
167
|
vi.clearAllMocks();
|
|
149
168
|
// Initialize mock stdout for terminal title tests
|
|
150
|
-
mockStdout
|
|
169
|
+
mocks.mockStdout.write.mockClear();
|
|
151
170
|
// Mock computeWindowTitle function to centralize title logic testing
|
|
152
171
|
vi.mock('../utils/windowTitle.js', async () => ({
|
|
153
172
|
computeWindowTitle: vi.fn((folderName) =>
|
|
@@ -246,7 +265,7 @@ describe('AppContainer State Management', () => {
|
|
|
246
265
|
// Add other properties if AppContainer uses them
|
|
247
266
|
});
|
|
248
267
|
mockedUseLogger.mockReturnValue({
|
|
249
|
-
getPreviousUserMessages: vi.fn().
|
|
268
|
+
getPreviousUserMessages: vi.fn().mockReturnValue(new Promise(() => { })),
|
|
250
269
|
});
|
|
251
270
|
mockedUseLoadingIndicator.mockReturnValue({
|
|
252
271
|
elapsedTime: '0.0s',
|
|
@@ -292,17 +311,13 @@ describe('AppContainer State Management', () => {
|
|
|
292
311
|
describe('Basic Rendering', () => {
|
|
293
312
|
it('renders without crashing with minimal props', async () => {
|
|
294
313
|
const { unmount } = renderAppContainer();
|
|
295
|
-
await
|
|
296
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
297
|
-
});
|
|
314
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
298
315
|
unmount();
|
|
299
316
|
});
|
|
300
317
|
it('renders with startup warnings', async () => {
|
|
301
318
|
const startupWarnings = ['Warning 1', 'Warning 2'];
|
|
302
319
|
const { unmount } = renderAppContainer({ startupWarnings });
|
|
303
|
-
await
|
|
304
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
305
|
-
});
|
|
320
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
306
321
|
unmount();
|
|
307
322
|
});
|
|
308
323
|
});
|
|
@@ -315,9 +330,7 @@ describe('AppContainer State Management', () => {
|
|
|
315
330
|
const { unmount } = renderAppContainer({
|
|
316
331
|
initResult: initResultWithError,
|
|
317
332
|
});
|
|
318
|
-
await
|
|
319
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
320
|
-
});
|
|
333
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
321
334
|
unmount();
|
|
322
335
|
});
|
|
323
336
|
it('handles debug mode state', () => {
|
|
@@ -331,31 +344,23 @@ describe('AppContainer State Management', () => {
|
|
|
331
344
|
describe('Context Providers', () => {
|
|
332
345
|
it('provides AppContext with correct values', async () => {
|
|
333
346
|
const { unmount } = renderAppContainer({ version: '2.0.0' });
|
|
334
|
-
await
|
|
335
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
336
|
-
});
|
|
347
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
337
348
|
// Should render and unmount cleanly
|
|
338
349
|
expect(() => unmount()).not.toThrow();
|
|
339
350
|
});
|
|
340
351
|
it('provides UIStateContext with state management', async () => {
|
|
341
352
|
const { unmount } = renderAppContainer();
|
|
342
|
-
await
|
|
343
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
344
|
-
});
|
|
353
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
345
354
|
unmount();
|
|
346
355
|
});
|
|
347
356
|
it('provides UIActionsContext with action handlers', async () => {
|
|
348
357
|
const { unmount } = renderAppContainer();
|
|
349
|
-
await
|
|
350
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
351
|
-
});
|
|
358
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
352
359
|
unmount();
|
|
353
360
|
});
|
|
354
361
|
it('provides ConfigContext with config object', async () => {
|
|
355
362
|
const { unmount } = renderAppContainer();
|
|
356
|
-
await
|
|
357
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
358
|
-
});
|
|
363
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
359
364
|
unmount();
|
|
360
365
|
});
|
|
361
366
|
});
|
|
@@ -370,9 +375,7 @@ describe('AppContainer State Management', () => {
|
|
|
370
375
|
},
|
|
371
376
|
};
|
|
372
377
|
const { unmount } = renderAppContainer({ settings: settingsAllHidden });
|
|
373
|
-
await
|
|
374
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
375
|
-
});
|
|
378
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
376
379
|
unmount();
|
|
377
380
|
});
|
|
378
381
|
it('handles settings with memory usage enabled', async () => {
|
|
@@ -385,18 +388,14 @@ describe('AppContainer State Management', () => {
|
|
|
385
388
|
},
|
|
386
389
|
};
|
|
387
390
|
const { unmount } = renderAppContainer({ settings: settingsWithMemory });
|
|
388
|
-
await
|
|
389
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
390
|
-
});
|
|
391
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
391
392
|
unmount();
|
|
392
393
|
});
|
|
393
394
|
});
|
|
394
395
|
describe('Version Handling', () => {
|
|
395
396
|
it.each(['1.0.0', '2.1.3-beta', '3.0.0-nightly'])('handles version format: %s', async (version) => {
|
|
396
397
|
const { unmount } = renderAppContainer({ version });
|
|
397
|
-
await
|
|
398
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
399
|
-
});
|
|
398
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
400
399
|
unmount();
|
|
401
400
|
});
|
|
402
401
|
});
|
|
@@ -408,9 +407,6 @@ describe('AppContainer State Management', () => {
|
|
|
408
407
|
});
|
|
409
408
|
// Should still render without crashing - errors should be handled internally
|
|
410
409
|
const { unmount } = renderAppContainer({ config: errorConfig });
|
|
411
|
-
await act(async () => {
|
|
412
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
413
|
-
});
|
|
414
410
|
unmount();
|
|
415
411
|
});
|
|
416
412
|
it('handles undefined settings gracefully', async () => {
|
|
@@ -418,9 +414,7 @@ describe('AppContainer State Management', () => {
|
|
|
418
414
|
merged: {},
|
|
419
415
|
};
|
|
420
416
|
const { unmount } = renderAppContainer({ settings: undefinedSettings });
|
|
421
|
-
await
|
|
422
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
423
|
-
});
|
|
417
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
424
418
|
unmount();
|
|
425
419
|
});
|
|
426
420
|
});
|
|
@@ -716,11 +710,10 @@ describe('AppContainer State Management', () => {
|
|
|
716
710
|
it('passes a null proQuotaRequest to UIStateContext by default', async () => {
|
|
717
711
|
// The default mock from beforeEach already sets proQuotaRequest to null
|
|
718
712
|
const { unmount } = renderAppContainer();
|
|
719
|
-
await
|
|
720
|
-
|
|
713
|
+
await waitFor(() => {
|
|
714
|
+
// Assert that the context value is as expected
|
|
715
|
+
expect(capturedUIState.proQuotaRequest).toBeNull();
|
|
721
716
|
});
|
|
722
|
-
// Assert that the context value is as expected
|
|
723
|
-
expect(capturedUIState.proQuotaRequest).toBeNull();
|
|
724
717
|
unmount();
|
|
725
718
|
});
|
|
726
719
|
it('passes a valid proQuotaRequest to UIStateContext when provided by the hook', async () => {
|
|
@@ -736,11 +729,10 @@ describe('AppContainer State Management', () => {
|
|
|
736
729
|
});
|
|
737
730
|
// Act: Render the container
|
|
738
731
|
const { unmount } = renderAppContainer();
|
|
739
|
-
await
|
|
740
|
-
|
|
732
|
+
await waitFor(() => {
|
|
733
|
+
// Assert: The mock request is correctly passed through the context
|
|
734
|
+
expect(capturedUIState.proQuotaRequest).toEqual(mockRequest);
|
|
741
735
|
});
|
|
742
|
-
// Assert: The mock request is correctly passed through the context
|
|
743
|
-
expect(capturedUIState.proQuotaRequest).toEqual(mockRequest);
|
|
744
736
|
unmount();
|
|
745
737
|
});
|
|
746
738
|
it('passes the handleProQuotaChoice function to UIActionsContext', async () => {
|
|
@@ -752,11 +744,10 @@ describe('AppContainer State Management', () => {
|
|
|
752
744
|
});
|
|
753
745
|
// Act: Render the container
|
|
754
746
|
const { unmount } = renderAppContainer();
|
|
755
|
-
await
|
|
756
|
-
|
|
747
|
+
await waitFor(() => {
|
|
748
|
+
// Assert: The action in the context is the mock handler we provided
|
|
749
|
+
expect(capturedUIActions.handleProQuotaChoice).toBe(mockHandler);
|
|
757
750
|
});
|
|
758
|
-
// Assert: The action in the context is the mock handler we provided
|
|
759
|
-
expect(capturedUIActions.handleProQuotaChoice).toBe(mockHandler);
|
|
760
751
|
// You can even verify that the plumbed function is callable
|
|
761
752
|
act(() => {
|
|
762
753
|
capturedUIActions.handleProQuotaChoice('retry_later');
|
|
@@ -768,7 +759,12 @@ describe('AppContainer State Management', () => {
|
|
|
768
759
|
describe('Terminal Title Update Feature', () => {
|
|
769
760
|
beforeEach(() => {
|
|
770
761
|
// Reset mock stdout for each test
|
|
771
|
-
mockStdout
|
|
762
|
+
mocks.mockStdout.write.mockClear();
|
|
763
|
+
});
|
|
764
|
+
it('verifies useStdout is mocked', async () => {
|
|
765
|
+
const { useStdout } = await import('ink');
|
|
766
|
+
const { stdout } = useStdout();
|
|
767
|
+
expect(stdout).toBe(mocks.mockStdout);
|
|
772
768
|
});
|
|
773
769
|
it('should not update terminal title when showStatusInTitle is false', () => {
|
|
774
770
|
// Arrange: Set up mock settings with showStatusInTitle disabled
|
|
@@ -788,7 +784,7 @@ describe('AppContainer State Management', () => {
|
|
|
788
784
|
settings: mockSettingsWithShowStatusFalse,
|
|
789
785
|
});
|
|
790
786
|
// Assert: Check that no title-related writes occurred
|
|
791
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
787
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
792
788
|
expect(titleWrites).toHaveLength(0);
|
|
793
789
|
unmount();
|
|
794
790
|
});
|
|
@@ -810,7 +806,7 @@ describe('AppContainer State Management', () => {
|
|
|
810
806
|
settings: mockSettingsWithHideTitleTrue,
|
|
811
807
|
});
|
|
812
808
|
// Assert: Check that no title-related writes occurred
|
|
813
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
809
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
814
810
|
expect(titleWrites).toHaveLength(0);
|
|
815
811
|
unmount();
|
|
816
812
|
});
|
|
@@ -842,7 +838,7 @@ describe('AppContainer State Management', () => {
|
|
|
842
838
|
settings: mockSettingsWithTitleEnabled,
|
|
843
839
|
});
|
|
844
840
|
// Assert: Check that title was updated with thought subject
|
|
845
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
841
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
846
842
|
expect(titleWrites).toHaveLength(1);
|
|
847
843
|
expect(titleWrites[0][0]).toBe(`\x1b]2;${thoughtSubject.padEnd(80, ' ')}\x07`);
|
|
848
844
|
unmount();
|
|
@@ -874,7 +870,7 @@ describe('AppContainer State Management', () => {
|
|
|
874
870
|
settings: mockSettingsWithTitleEnabled,
|
|
875
871
|
});
|
|
876
872
|
// Assert: Check that title was updated with default Idle text
|
|
877
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
873
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
878
874
|
expect(titleWrites).toHaveLength(1);
|
|
879
875
|
expect(titleWrites[0][0]).toBe(`\x1b]2;${'Gemini - workspace'.padEnd(80, ' ')}\x07`);
|
|
880
876
|
unmount();
|
|
@@ -907,7 +903,7 @@ describe('AppContainer State Management', () => {
|
|
|
907
903
|
settings: mockSettingsWithTitleEnabled,
|
|
908
904
|
});
|
|
909
905
|
// Assert: Check that title was updated with confirmation text
|
|
910
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
906
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
911
907
|
expect(titleWrites).toHaveLength(1);
|
|
912
908
|
expect(titleWrites[0][0]).toBe(`\x1b]2;${thoughtSubject.padEnd(80, ' ')}\x07`);
|
|
913
909
|
unmount();
|
|
@@ -940,7 +936,7 @@ describe('AppContainer State Management', () => {
|
|
|
940
936
|
settings: mockSettingsWithTitleEnabled,
|
|
941
937
|
});
|
|
942
938
|
// Assert: Check that title is padded to exactly 80 characters
|
|
943
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
939
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
944
940
|
expect(titleWrites).toHaveLength(1);
|
|
945
941
|
const calledWith = titleWrites[0][0];
|
|
946
942
|
const expectedTitle = shortTitle.padEnd(80, ' ');
|
|
@@ -978,7 +974,7 @@ describe('AppContainer State Management', () => {
|
|
|
978
974
|
settings: mockSettingsWithTitleEnabled,
|
|
979
975
|
});
|
|
980
976
|
// Assert: Check that the correct ANSI escape sequence is used
|
|
981
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
977
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
982
978
|
expect(titleWrites).toHaveLength(1);
|
|
983
979
|
const expectedEscapeSequence = `\x1b]2;${title.padEnd(80, ' ')}\x07`;
|
|
984
980
|
expect(titleWrites[0][0]).toBe(expectedEscapeSequence);
|
|
@@ -1013,7 +1009,7 @@ describe('AppContainer State Management', () => {
|
|
|
1013
1009
|
settings: mockSettingsWithTitleEnabled,
|
|
1014
1010
|
});
|
|
1015
1011
|
// Assert: Check that title was updated with CLI_TITLE value
|
|
1016
|
-
const titleWrites = mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
1012
|
+
const titleWrites = mocks.mockStdout.write.mock.calls.filter((call) => call[0].includes('\x1b]2;'));
|
|
1017
1013
|
expect(titleWrites).toHaveLength(1);
|
|
1018
1014
|
expect(titleWrites[0][0]).toBe(`\x1b]2;${'Custom Gemini Title'.padEnd(80, ' ')}\x07`);
|
|
1019
1015
|
unmount();
|
|
@@ -1094,12 +1090,7 @@ describe('AppContainer State Management', () => {
|
|
|
1094
1090
|
activePtyId: 'some-id',
|
|
1095
1091
|
});
|
|
1096
1092
|
const { unmount } = renderAppContainer();
|
|
1097
|
-
await
|
|
1098
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1099
|
-
});
|
|
1100
|
-
// Assert: The shell should be resized to a minimum height of 1, not a negative number.
|
|
1101
|
-
// The old code would have tried to set a negative height.
|
|
1102
|
-
expect(resizePtySpy).toHaveBeenCalled();
|
|
1093
|
+
await waitFor(() => expect(resizePtySpy).toHaveBeenCalled());
|
|
1103
1094
|
const lastCall = resizePtySpy.mock.calls[resizePtySpy.mock.calls.length - 1];
|
|
1104
1095
|
// Check the height argument specifically
|
|
1105
1096
|
expect(lastCall[2]).toBe(1);
|
|
@@ -1190,7 +1181,7 @@ describe('AppContainer State Management', () => {
|
|
|
1190
1181
|
await setupKeypressTest();
|
|
1191
1182
|
pressKey({ name: 'c', ctrl: true }, 2);
|
|
1192
1183
|
expect(mockCancelOngoingRequest).toHaveBeenCalledTimes(2);
|
|
1193
|
-
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit');
|
|
1184
|
+
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit', undefined, undefined, false);
|
|
1194
1185
|
unmount();
|
|
1195
1186
|
});
|
|
1196
1187
|
it('should reset press count after a timeout', async () => {
|
|
@@ -1220,7 +1211,7 @@ describe('AppContainer State Management', () => {
|
|
|
1220
1211
|
it('should quit on second press if buffer is empty', async () => {
|
|
1221
1212
|
await setupKeypressTest();
|
|
1222
1213
|
pressKey({ name: 'd', ctrl: true }, 2);
|
|
1223
|
-
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit');
|
|
1214
|
+
expect(mockHandleSlashCommand).toHaveBeenCalledWith('/quit', undefined, undefined, false);
|
|
1224
1215
|
unmount();
|
|
1225
1216
|
});
|
|
1226
1217
|
it('should reset press count after a timeout', async () => {
|
|
@@ -1261,7 +1252,7 @@ describe('AppContainer State Management', () => {
|
|
|
1261
1252
|
unmount = renderResult.unmount;
|
|
1262
1253
|
};
|
|
1263
1254
|
beforeEach(() => {
|
|
1264
|
-
mockStdout.write.mockClear();
|
|
1255
|
+
mocks.mockStdout.write.mockClear();
|
|
1265
1256
|
mockedUseKeypress.mockImplementation((callback) => {
|
|
1266
1257
|
handleGlobalKeypress = callback;
|
|
1267
1258
|
});
|
|
@@ -1284,7 +1275,7 @@ describe('AppContainer State Management', () => {
|
|
|
1284
1275
|
])('$modeName', ({ isAlternateMode, shouldEnable }) => {
|
|
1285
1276
|
it(`should ${shouldEnable ? 'toggle' : 'NOT toggle'} mouse off when Ctrl+S is pressed`, async () => {
|
|
1286
1277
|
await setupCopyModeTest(isAlternateMode);
|
|
1287
|
-
mockStdout.write.mockClear(); // Clear initial enable call
|
|
1278
|
+
mocks.mockStdout.write.mockClear(); // Clear initial enable call
|
|
1288
1279
|
act(() => {
|
|
1289
1280
|
handleGlobalKeypress({
|
|
1290
1281
|
name: 's',
|
|
@@ -1308,7 +1299,7 @@ describe('AppContainer State Management', () => {
|
|
|
1308
1299
|
if (shouldEnable) {
|
|
1309
1300
|
it('should toggle mouse back on when Ctrl+S is pressed again', async () => {
|
|
1310
1301
|
await setupCopyModeTest(isAlternateMode);
|
|
1311
|
-
|
|
1302
|
+
writeToStdout.mockClear();
|
|
1312
1303
|
// Turn it on (disable mouse)
|
|
1313
1304
|
act(() => {
|
|
1314
1305
|
handleGlobalKeypress({
|
|
@@ -1354,7 +1345,7 @@ describe('AppContainer State Management', () => {
|
|
|
1354
1345
|
});
|
|
1355
1346
|
});
|
|
1356
1347
|
rerender();
|
|
1357
|
-
|
|
1348
|
+
writeToStdout.mockClear();
|
|
1358
1349
|
// Press any other key
|
|
1359
1350
|
act(() => {
|
|
1360
1351
|
handleGlobalKeypress({
|
|
@@ -1383,9 +1374,7 @@ describe('AppContainer State Management', () => {
|
|
|
1383
1374
|
closeModelDialog: vi.fn(),
|
|
1384
1375
|
});
|
|
1385
1376
|
const { unmount } = renderAppContainer();
|
|
1386
|
-
await
|
|
1387
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1388
|
-
});
|
|
1377
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
1389
1378
|
expect(capturedUIState.isModelDialogOpen).toBe(true);
|
|
1390
1379
|
unmount();
|
|
1391
1380
|
});
|
|
@@ -1397,9 +1386,7 @@ describe('AppContainer State Management', () => {
|
|
|
1397
1386
|
closeModelDialog: mockCloseModelDialog,
|
|
1398
1387
|
});
|
|
1399
1388
|
const { unmount } = renderAppContainer();
|
|
1400
|
-
await
|
|
1401
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1402
|
-
});
|
|
1389
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
1403
1390
|
// Verify that the actions are correctly passed through context
|
|
1404
1391
|
act(() => {
|
|
1405
1392
|
capturedUIActions.closeModelDialog();
|
|
@@ -1411,26 +1398,20 @@ describe('AppContainer State Management', () => {
|
|
|
1411
1398
|
describe('CoreEvents Integration', () => {
|
|
1412
1399
|
it('subscribes to UserFeedback and drains backlog on mount', async () => {
|
|
1413
1400
|
const { unmount } = renderAppContainer();
|
|
1414
|
-
await
|
|
1415
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1416
|
-
});
|
|
1401
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
1417
1402
|
expect(mockCoreEvents.on).toHaveBeenCalledWith(CoreEvent.UserFeedback, expect.any(Function));
|
|
1418
|
-
expect(mockCoreEvents.
|
|
1403
|
+
expect(mockCoreEvents.drainBacklogs).toHaveBeenCalledTimes(1);
|
|
1419
1404
|
unmount();
|
|
1420
1405
|
});
|
|
1421
1406
|
it('unsubscribes from UserFeedback on unmount', async () => {
|
|
1422
1407
|
const { unmount } = renderAppContainer();
|
|
1423
|
-
await
|
|
1424
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1425
|
-
});
|
|
1408
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
1426
1409
|
unmount();
|
|
1427
1410
|
expect(mockCoreEvents.off).toHaveBeenCalledWith(CoreEvent.UserFeedback, expect.any(Function));
|
|
1428
1411
|
});
|
|
1429
1412
|
it('adds history item when UserFeedback event is received', async () => {
|
|
1430
1413
|
const { unmount } = renderAppContainer();
|
|
1431
|
-
await
|
|
1432
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1433
|
-
});
|
|
1414
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
1434
1415
|
// Get the registered handler
|
|
1435
1416
|
const handler = mockCoreEvents.on.mock.calls.find((call) => call[0] === CoreEvent.UserFeedback)?.[1];
|
|
1436
1417
|
expect(handler).toBeDefined();
|
|
@@ -1452,10 +1433,8 @@ describe('AppContainer State Management', () => {
|
|
|
1452
1433
|
// Arrange: Mock initial model
|
|
1453
1434
|
vi.spyOn(mockConfig, 'getModel').mockReturnValue('initial-model');
|
|
1454
1435
|
const { unmount } = renderAppContainer();
|
|
1455
|
-
await
|
|
1456
|
-
|
|
1457
|
-
expect(capturedUIState?.currentModel).toBe('initial-model');
|
|
1458
|
-
});
|
|
1436
|
+
await waitFor(() => {
|
|
1437
|
+
expect(capturedUIState?.currentModel).toBe('initial-model');
|
|
1459
1438
|
});
|
|
1460
1439
|
// Get the registered handler for ModelChanged
|
|
1461
1440
|
const handler = mockCoreEvents.on.mock.calls.find((call) => call[0] === CoreEvent.ModelChanged)?.[1];
|
|
@@ -1487,10 +1466,7 @@ describe('AppContainer State Management', () => {
|
|
|
1487
1466
|
});
|
|
1488
1467
|
// The main assertion is that the render does not throw.
|
|
1489
1468
|
const { unmount } = renderAppContainer();
|
|
1490
|
-
await
|
|
1491
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1492
|
-
});
|
|
1493
|
-
expect(resizePtySpy).toHaveBeenCalled();
|
|
1469
|
+
await waitFor(() => expect(resizePtySpy).toHaveBeenCalled());
|
|
1494
1470
|
unmount();
|
|
1495
1471
|
});
|
|
1496
1472
|
});
|
|
@@ -1502,14 +1478,105 @@ describe('AppContainer State Management', () => {
|
|
|
1502
1478
|
apiKey: 'fake-key',
|
|
1503
1479
|
});
|
|
1504
1480
|
const { unmount } = renderAppContainer();
|
|
1505
|
-
await
|
|
1506
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1507
|
-
});
|
|
1508
|
-
await vi.waitFor(() => {
|
|
1481
|
+
await waitFor(() => {
|
|
1509
1482
|
expect(capturedUIState.bannerData.defaultText).toBeDefined();
|
|
1510
1483
|
unmount();
|
|
1511
1484
|
});
|
|
1512
1485
|
});
|
|
1513
1486
|
});
|
|
1487
|
+
describe('onCancelSubmit Behavior', () => {
|
|
1488
|
+
let mockSetText;
|
|
1489
|
+
// Helper to extract arguments from the useGeminiStream hook call
|
|
1490
|
+
// This isolates the positional argument dependency to a single location
|
|
1491
|
+
const extractUseGeminiStreamArgs = (args) => ({
|
|
1492
|
+
onCancelSubmit: args[13],
|
|
1493
|
+
});
|
|
1494
|
+
beforeEach(() => {
|
|
1495
|
+
mockSetText = vi.fn();
|
|
1496
|
+
mockedUseTextBuffer.mockReturnValue({
|
|
1497
|
+
text: '',
|
|
1498
|
+
setText: mockSetText,
|
|
1499
|
+
});
|
|
1500
|
+
});
|
|
1501
|
+
it('clears the prompt when onCancelSubmit is called with shouldRestorePrompt=false', async () => {
|
|
1502
|
+
const { unmount } = renderAppContainer();
|
|
1503
|
+
await waitFor(() => expect(capturedUIState).toBeTruthy());
|
|
1504
|
+
const { onCancelSubmit } = extractUseGeminiStreamArgs(mockedUseGeminiStream.mock.lastCall);
|
|
1505
|
+
act(() => {
|
|
1506
|
+
onCancelSubmit(false);
|
|
1507
|
+
});
|
|
1508
|
+
expect(mockSetText).toHaveBeenCalledWith('');
|
|
1509
|
+
unmount();
|
|
1510
|
+
});
|
|
1511
|
+
it('restores the prompt when onCancelSubmit is called with shouldRestorePrompt=true (or undefined)', async () => {
|
|
1512
|
+
mockedUseLogger.mockReturnValue({
|
|
1513
|
+
getPreviousUserMessages: vi
|
|
1514
|
+
.fn()
|
|
1515
|
+
.mockResolvedValue(['previous message']),
|
|
1516
|
+
});
|
|
1517
|
+
const { unmount } = renderAppContainer();
|
|
1518
|
+
await waitFor(() => expect(capturedUIState.userMessages).toContain('previous message'));
|
|
1519
|
+
const { onCancelSubmit } = extractUseGeminiStreamArgs(mockedUseGeminiStream.mock.lastCall);
|
|
1520
|
+
await act(async () => {
|
|
1521
|
+
onCancelSubmit(true);
|
|
1522
|
+
});
|
|
1523
|
+
expect(mockSetText).toHaveBeenCalledWith('previous message');
|
|
1524
|
+
unmount();
|
|
1525
|
+
});
|
|
1526
|
+
it('correctly restores prompt even if userMessages is stale (race condition fix)', async () => {
|
|
1527
|
+
// Setup initial history with one message
|
|
1528
|
+
const initialHistory = [{ type: 'user', text: 'Previous Prompt' }];
|
|
1529
|
+
mockedUseHistory.mockReturnValue({
|
|
1530
|
+
history: initialHistory,
|
|
1531
|
+
addItem: vi.fn(),
|
|
1532
|
+
updateItem: vi.fn(),
|
|
1533
|
+
clearItems: vi.fn(),
|
|
1534
|
+
loadHistory: vi.fn(),
|
|
1535
|
+
});
|
|
1536
|
+
let resolveLoggerPromise;
|
|
1537
|
+
const loggerPromise = new Promise((resolve) => {
|
|
1538
|
+
resolveLoggerPromise = resolve;
|
|
1539
|
+
});
|
|
1540
|
+
// Mock logger to control when userMessages updates
|
|
1541
|
+
const getPreviousUserMessagesMock = vi
|
|
1542
|
+
.fn()
|
|
1543
|
+
.mockResolvedValueOnce([]) // Initial mount
|
|
1544
|
+
.mockReturnValueOnce(loggerPromise); // Second render (simulated update)
|
|
1545
|
+
mockedUseLogger.mockReturnValue({
|
|
1546
|
+
getPreviousUserMessages: getPreviousUserMessagesMock,
|
|
1547
|
+
});
|
|
1548
|
+
const { unmount, rerender } = renderAppContainer();
|
|
1549
|
+
// Wait for userMessages to be populated with 'Previous Prompt'
|
|
1550
|
+
await waitFor(() => expect(capturedUIState.userMessages).toContain('Previous Prompt'));
|
|
1551
|
+
// Simulate a new prompt being added (e.g., user sent it, but it overflowed)
|
|
1552
|
+
const newPrompt = 'Current Prompt that Overflowed';
|
|
1553
|
+
const newHistory = [...initialHistory, { type: 'user', text: newPrompt }];
|
|
1554
|
+
mockedUseHistory.mockReturnValue({
|
|
1555
|
+
history: newHistory,
|
|
1556
|
+
addItem: vi.fn(),
|
|
1557
|
+
updateItem: vi.fn(),
|
|
1558
|
+
clearItems: vi.fn(),
|
|
1559
|
+
loadHistory: vi.fn(),
|
|
1560
|
+
});
|
|
1561
|
+
// Rerender to reflect the history change.
|
|
1562
|
+
// This triggers the effect to update userMessages, but it hangs on loggerPromise.
|
|
1563
|
+
rerender(getAppContainer());
|
|
1564
|
+
const { onCancelSubmit } = extractUseGeminiStreamArgs(mockedUseGeminiStream.mock.lastCall);
|
|
1565
|
+
// Call onCancelSubmit immediately. userMessages is still stale (has only 'Previous Prompt')
|
|
1566
|
+
// because the effect is waiting on loggerPromise.
|
|
1567
|
+
act(() => {
|
|
1568
|
+
onCancelSubmit(true);
|
|
1569
|
+
});
|
|
1570
|
+
// Now resolve the promise to let the effect complete and update userMessages
|
|
1571
|
+
await act(async () => {
|
|
1572
|
+
resolveLoggerPromise([]);
|
|
1573
|
+
});
|
|
1574
|
+
// With the fix, it should have waited for userMessages to update and then set the new prompt
|
|
1575
|
+
await waitFor(() => {
|
|
1576
|
+
expect(mockSetText).toHaveBeenCalledWith(newPrompt);
|
|
1577
|
+
});
|
|
1578
|
+
unmount();
|
|
1579
|
+
});
|
|
1580
|
+
});
|
|
1514
1581
|
});
|
|
1515
1582
|
//# sourceMappingURL=AppContainer.test.js.map
|