/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import React from 'react'; import { render } from 'ink-testing-library'; import { Text } from 'ink'; import { LoadingIndicator } from './LoadingIndicator.js'; import { StreamingContext, StreamingContextType, } from '../contexts/StreamingContext.js'; import { StreamingState } from '../types.js'; import { vi } from 'vitest'; // Mock GeminiRespondingSpinner vi.mock('./GeminiRespondingSpinner.js', () => ({ GeminiRespondingSpinner: ({ nonRespondingDisplay, }: { nonRespondingDisplay?: string; }) => { const { streamingState } = React.useContext(StreamingContext)!; if (streamingState === StreamingState.Responding) { return MockRespondingSpinner; } else if (nonRespondingDisplay) { return {nonRespondingDisplay}; } return null; }, })); const renderWithContext = ( ui: React.ReactElement, streamingStateValue: StreamingState, ) => { const contextValue: StreamingContextType = { streamingState: streamingStateValue, }; return render( {ui} , ); }; describe('', () => { const defaultProps = { currentLoadingPhrase: 'Loading...', elapsedTime: 5, }; it('should not render when streamingState is Idle', () => { const { lastFrame } = renderWithContext( , StreamingState.Idle, ); expect(lastFrame()).toBe(''); }); it('should render spinner, phrase, and time when streamingState is Responding', () => { const { lastFrame } = renderWithContext( , StreamingState.Responding, ); const output = lastFrame(); expect(output).toContain('MockRespondingSpinner'); expect(output).toContain('Loading...'); expect(output).toContain('(esc to cancel, 5s)'); }); it('should render spinner (static), phrase but no time/cancel when streamingState is WaitingForConfirmation', () => { const props = { currentLoadingPhrase: 'Confirm action', elapsedTime: 10, }; const { lastFrame } = renderWithContext( , StreamingState.WaitingForConfirmation, ); const output = lastFrame(); expect(output).toContain('⠏'); // Static char for WaitingForConfirmation expect(output).toContain('Confirm action'); expect(output).not.toContain('(esc to cancel)'); expect(output).not.toContain(', 10s'); }); it('should display the currentLoadingPhrase correctly', () => { const props = { currentLoadingPhrase: 'Processing data...', elapsedTime: 3, }; const { lastFrame } = renderWithContext( , StreamingState.Responding, ); expect(lastFrame()).toContain('Processing data...'); }); it('should display the elapsedTime correctly when Responding', () => { const props = { currentLoadingPhrase: 'Working...', elapsedTime: 8, }; const { lastFrame } = renderWithContext( , StreamingState.Responding, ); expect(lastFrame()).toContain('(esc to cancel, 8s)'); }); it('should render rightContent when provided', () => { const rightContent = Extra Info; const { lastFrame } = renderWithContext( , StreamingState.Responding, ); expect(lastFrame()).toContain('Extra Info'); }); it('should transition correctly between states using rerender', () => { const { lastFrame, rerender } = renderWithContext( , StreamingState.Idle, ); expect(lastFrame()).toBe(''); // Initial: Idle // Transition to Responding rerender( , ); let output = lastFrame(); expect(output).toContain('MockRespondingSpinner'); expect(output).toContain('Now Responding'); expect(output).toContain('(esc to cancel, 2s)'); // Transition to WaitingForConfirmation rerender( , ); output = lastFrame(); expect(output).toContain('⠏'); expect(output).toContain('Please Confirm'); expect(output).not.toContain('(esc to cancel)'); expect(output).not.toContain(', 15s'); // Transition back to Idle rerender( , ); expect(lastFrame()).toBe(''); }); });