diff --git a/packages/cli/src/ui/components/MainContent.test.tsx b/packages/cli/src/ui/components/MainContent.test.tsx index 989a7cb866..7228e608f1 100644 --- a/packages/cli/src/ui/components/MainContent.test.tsx +++ b/packages/cli/src/ui/components/MainContent.test.tsx @@ -393,6 +393,33 @@ describe('MainContent', () => { unmount(); }); + it('renders BtwDisplay within ScrollableList in alternate buffer mode when btw is active', async () => { + vi.mocked(useAlternateBuffer).mockReturnValue(true); + const uiStateWithBtw = { + ...defaultMockUiState, + btwState: { + isActive: true, + query: 'test query', + response: 'test response', + isStreaming: false, + error: null, + }, + }; + const { lastFrame, unmount } = await renderWithProviders(, { + uiState: uiStateWithBtw as Partial, + }); + const output = lastFrame(); + // Verify ScrollableList is rendered (from our mock) + expect(output).toContain('ScrollableList'); + // Verify btw response is rendered + expect(output).toContain('test query'); + expect(output).toContain('test response'); + + expect(output).toMatchSnapshot(); + // Verify it rendered inside ScrollableList's items in the mock + unmount(); + }); + it('renders minimal header in minimal mode (alternate buffer)', async () => { vi.mocked(useAlternateBuffer).mockReturnValue(true); diff --git a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap index 7dab229ecd..097b3fc177 100644 --- a/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap +++ b/packages/cli/src/ui/components/__snapshots__/MainContent.test.tsx.snap @@ -92,6 +92,23 @@ exports[`MainContent > MainContent Tool Output Height Logic > 'Normal mode - Unc " `; +exports[`MainContent > renders BtwDisplay within ScrollableList in alternate buffer mode when btw is active 1`] = ` +"ScrollableList +AppHeader(full) +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ + > Hello +▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ +✦ Hi there +╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ BY THE WAY Press Esc, Enter or Space to dismiss │ +│ │ +│ Q: test query │ +│ │ +│ test response │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ +" +`; + exports[`MainContent > renders a ToolConfirmationQueue without an extra line when preceded by hidden tools 1`] = ` "AppHeader(full) ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ diff --git a/packages/cli/src/ui/hooks/useBtw.test.ts b/packages/cli/src/ui/hooks/useBtw.test.ts index 4ca69a0675..4af8495d51 100644 --- a/packages/cli/src/ui/hooks/useBtw.test.ts +++ b/packages/cli/src/ui/hooks/useBtw.test.ts @@ -83,8 +83,10 @@ describe('useBtw', () => { useBtw(mockGeminiClient as unknown as GeminiClient), ); + let submitPromise!: Promise; await act(async () => { - await result.current.submitBtw('test query'); + submitPromise = result.current.submitBtw('test query'); + await submitPromise; }); expect(result.current.error).toBe('API Error'); @@ -104,8 +106,10 @@ describe('useBtw', () => { useBtw(mockGeminiClient as unknown as GeminiClient), ); + let submitPromise!: Promise; await act(async () => { - await result.current.submitBtw('test query'); + submitPromise = result.current.submitBtw('test query'); + await submitPromise; }); expect(result.current.error).toBe('Direct string error'); @@ -124,8 +128,10 @@ describe('useBtw', () => { useBtw(mockGeminiClient as unknown as GeminiClient), ); + let submitPromise!: Promise; await act(async () => { - await result.current.submitBtw('test query'); + submitPromise = result.current.submitBtw('test query'); + await submitPromise; }); expect(result.current.error).toBe('Unknown error'); @@ -144,18 +150,25 @@ describe('useBtw', () => { useBtw(mockGeminiClient as unknown as GeminiClient), ); + let submitPromise!: Promise; await act(async () => { - await result.current.submitBtw('test query'); + submitPromise = result.current.submitBtw('test query'); + await submitPromise; }); expect(result.current.error).toBe('Just some raw string value'); }); it('should reset state on dismiss', async () => { + let resolveStream: (value: void) => void; + const streamGate = new Promise((resolve) => { + resolveStream = resolve; + }); + const mockStream = (async function* () { yield { type: GeminiEventType.Content, value: 'partial' }; // Hang - await new Promise(() => {}); + await streamGate; })(); mockGeminiClient.sendBtwStream.mockReturnValue(mockStream); @@ -163,14 +176,23 @@ describe('useBtw', () => { useBtw(mockGeminiClient as unknown as GeminiClient), ); - act(() => { - void result.current.submitBtw('test query'); + let submitPromise!: Promise; + await act(async () => { + submitPromise = result.current.submitBtw('test query'); }); expect(result.current.isActive).toBe(true); + expect(result.current.query).toBe('test query'); - act(() => { + await act(async () => { result.current.dismissBtw(); + resolveStream(); + // wait for the catch/finally blocks inside submitBtw to finish + try { + await submitPromise; + } catch (_e) { + // ignore AbortError + } }); expect(result.current.isActive).toBe(false); diff --git a/packages/cli/src/ui/hooks/useBtw.ts b/packages/cli/src/ui/hooks/useBtw.ts index 4654ef86ca..a1d5ecb130 100644 --- a/packages/cli/src/ui/hooks/useBtw.ts +++ b/packages/cli/src/ui/hooks/useBtw.ts @@ -87,6 +87,7 @@ export const useBtw = ( abortControllerRef.current.abort(); abortControllerRef.current = null; } + requestIdRef.current++; dispatch({ type: 'DISMISS' }); }, []);