summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks/useGeminiStream.test.tsx')
-rw-r--r--packages/cli/src/ui/hooks/useGeminiStream.test.tsx238
1 files changed, 237 insertions, 1 deletions
diff --git a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
index 7e45cab2..d7fd35c8 100644
--- a/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
+++ b/packages/cli/src/ui/hooks/useGeminiStream.test.tsx
@@ -16,7 +16,12 @@ import {
TrackedExecutingToolCall,
TrackedCancelledToolCall,
} from './useReactToolScheduler.js';
-import { Config, EditorType, AuthType } from '@google/gemini-cli-core';
+import {
+ Config,
+ EditorType,
+ AuthType,
+ GeminiEventType as ServerGeminiEventType,
+} from '@google/gemini-cli-core';
import { Part, PartListUnion } from '@google/genai';
import { UseHistoryManagerReturn } from './useHistoryManager.js';
import {
@@ -1178,4 +1183,235 @@ describe('useGeminiStream', () => {
});
});
});
+
+ describe('handleFinishedEvent', () => {
+ it('should add info message for MAX_TOKENS finish reason', async () => {
+ // Setup mock to return a stream with MAX_TOKENS finish reason
+ mockSendMessageStream.mockReturnValue(
+ (async function* () {
+ yield {
+ type: ServerGeminiEventType.Content,
+ value: 'This is a truncated response...',
+ };
+ yield { type: ServerGeminiEventType.Finished, value: 'MAX_TOKENS' };
+ })(),
+ );
+
+ const { result } = renderHook(() =>
+ useGeminiStream(
+ new MockedGeminiClientClass(mockConfig),
+ [],
+ mockAddItem,
+ mockSetShowHelp,
+ mockConfig,
+ mockOnDebugMessage,
+ mockHandleSlashCommand,
+ false,
+ () => 'vscode' as EditorType,
+ () => {},
+ () => Promise.resolve(),
+ false,
+ () => {},
+ ),
+ );
+
+ // Submit a query
+ await act(async () => {
+ await result.current.submitQuery('Generate long text');
+ });
+
+ // Check that the info message was added
+ await waitFor(() => {
+ expect(mockAddItem).toHaveBeenCalledWith(
+ {
+ type: 'info',
+ text: '⚠️ Response truncated due to token limits.',
+ },
+ expect.any(Number),
+ );
+ });
+ });
+
+ it('should not add message for STOP finish reason', async () => {
+ // Setup mock to return a stream with STOP finish reason
+ mockSendMessageStream.mockReturnValue(
+ (async function* () {
+ yield {
+ type: ServerGeminiEventType.Content,
+ value: 'Complete response',
+ };
+ yield { type: ServerGeminiEventType.Finished, value: 'STOP' };
+ })(),
+ );
+
+ const { result } = renderHook(() =>
+ useGeminiStream(
+ new MockedGeminiClientClass(mockConfig),
+ [],
+ mockAddItem,
+ mockSetShowHelp,
+ mockConfig,
+ mockOnDebugMessage,
+ mockHandleSlashCommand,
+ false,
+ () => 'vscode' as EditorType,
+ () => {},
+ () => Promise.resolve(),
+ false,
+ () => {},
+ ),
+ );
+
+ // Submit a query
+ await act(async () => {
+ await result.current.submitQuery('Test normal completion');
+ });
+
+ // Wait a bit to ensure no message is added
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ // Check that no info message was added for STOP
+ const infoMessages = mockAddItem.mock.calls.filter(
+ (call) => call[0].type === 'info',
+ );
+ expect(infoMessages).toHaveLength(0);
+ });
+
+ it('should not add message for FINISH_REASON_UNSPECIFIED', async () => {
+ // Setup mock to return a stream with FINISH_REASON_UNSPECIFIED
+ mockSendMessageStream.mockReturnValue(
+ (async function* () {
+ yield {
+ type: ServerGeminiEventType.Content,
+ value: 'Response with unspecified finish',
+ };
+ yield {
+ type: ServerGeminiEventType.Finished,
+ value: 'FINISH_REASON_UNSPECIFIED',
+ };
+ })(),
+ );
+
+ const { result } = renderHook(() =>
+ useGeminiStream(
+ new MockedGeminiClientClass(mockConfig),
+ [],
+ mockAddItem,
+ mockSetShowHelp,
+ mockConfig,
+ mockOnDebugMessage,
+ mockHandleSlashCommand,
+ false,
+ () => 'vscode' as EditorType,
+ () => {},
+ () => Promise.resolve(),
+ false,
+ () => {},
+ ),
+ );
+
+ // Submit a query
+ await act(async () => {
+ await result.current.submitQuery('Test unspecified finish');
+ });
+
+ // Wait a bit to ensure no message is added
+ await new Promise((resolve) => setTimeout(resolve, 100));
+
+ // Check that no info message was added
+ const infoMessages = mockAddItem.mock.calls.filter(
+ (call) => call[0].type === 'info',
+ );
+ expect(infoMessages).toHaveLength(0);
+ });
+
+ it('should add appropriate messages for other finish reasons', async () => {
+ const testCases = [
+ {
+ reason: 'SAFETY',
+ message: '⚠️ Response stopped due to safety reasons.',
+ },
+ {
+ reason: 'RECITATION',
+ message: '⚠️ Response stopped due to recitation policy.',
+ },
+ {
+ reason: 'LANGUAGE',
+ message: '⚠️ Response stopped due to unsupported language.',
+ },
+ {
+ reason: 'BLOCKLIST',
+ message: '⚠️ Response stopped due to forbidden terms.',
+ },
+ {
+ reason: 'PROHIBITED_CONTENT',
+ message: '⚠️ Response stopped due to prohibited content.',
+ },
+ {
+ reason: 'SPII',
+ message:
+ '⚠️ Response stopped due to sensitive personally identifiable information.',
+ },
+ { reason: 'OTHER', message: '⚠️ Response stopped for other reasons.' },
+ {
+ reason: 'MALFORMED_FUNCTION_CALL',
+ message: '⚠️ Response stopped due to malformed function call.',
+ },
+ {
+ reason: 'IMAGE_SAFETY',
+ message: '⚠️ Response stopped due to image safety violations.',
+ },
+ {
+ reason: 'UNEXPECTED_TOOL_CALL',
+ message: '⚠️ Response stopped due to unexpected tool call.',
+ },
+ ];
+
+ for (const { reason, message } of testCases) {
+ // Reset mocks for each test case
+ mockAddItem.mockClear();
+ mockSendMessageStream.mockReturnValue(
+ (async function* () {
+ yield {
+ type: ServerGeminiEventType.Content,
+ value: `Response for ${reason}`,
+ };
+ yield { type: ServerGeminiEventType.Finished, value: reason };
+ })(),
+ );
+
+ const { result } = renderHook(() =>
+ useGeminiStream(
+ new MockedGeminiClientClass(mockConfig),
+ [],
+ mockAddItem,
+ mockSetShowHelp,
+ mockConfig,
+ mockOnDebugMessage,
+ mockHandleSlashCommand,
+ false,
+ () => 'vscode' as EditorType,
+ () => {},
+ () => Promise.resolve(),
+ false,
+ () => {},
+ ),
+ );
+
+ await act(async () => {
+ await result.current.submitQuery(`Test ${reason}`);
+ });
+
+ await waitFor(() => {
+ expect(mockAddItem).toHaveBeenCalledWith(
+ {
+ type: 'info',
+ text: message,
+ },
+ expect.any(Number),
+ );
+ });
+ }
+ });
+ });
});