From 4f2974dbfe36638915f1b08448d2563c64f88644 Mon Sep 17 00:00:00 2001 From: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:55:53 -0700 Subject: feat(ui): Improve UI layout adaptation for narrow terminals (#5651) Co-authored-by: Jacob Richman --- .../src/ui/components/LoadingIndicator.test.tsx | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'packages/cli/src/ui/components/LoadingIndicator.test.tsx') diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx index 9e647208..e55cc090 100644 --- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx +++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx @@ -11,6 +11,7 @@ import { LoadingIndicator } from './LoadingIndicator.js'; import { StreamingContext } from '../contexts/StreamingContext.js'; import { StreamingState } from '../types.js'; import { vi } from 'vitest'; +import * as useTerminalSize from '../hooks/useTerminalSize.js'; // Mock GeminiRespondingSpinner vi.mock('./GeminiRespondingSpinner.js', () => ({ @@ -29,10 +30,18 @@ vi.mock('./GeminiRespondingSpinner.js', () => ({ }, })); +vi.mock('../hooks/useTerminalSize.js', () => ({ + useTerminalSize: vi.fn(), +})); + +const useTerminalSizeMock = vi.mocked(useTerminalSize.useTerminalSize); + const renderWithContext = ( ui: React.ReactElement, streamingStateValue: StreamingState, + width = 120, ) => { + useTerminalSizeMock.mockReturnValue({ columns: width, rows: 24 }); const contextValue: StreamingState = streamingStateValue; return render( @@ -223,4 +232,65 @@ describe('', () => { expect(output).toContain('This should be displayed'); expect(output).not.toContain('This should not be displayed'); }); + + describe('responsive layout', () => { + it('should render on a single line on a wide terminal', () => { + const { lastFrame } = renderWithContext( + Right} + />, + StreamingState.Responding, + 120, + ); + const output = lastFrame(); + // Check for single line output + expect(output?.includes('\n')).toBe(false); + expect(output).toContain('Loading...'); + expect(output).toContain('(esc to cancel, 5s)'); + expect(output).toContain('Right'); + }); + + it('should render on multiple lines on a narrow terminal', () => { + const { lastFrame } = renderWithContext( + Right} + />, + StreamingState.Responding, + 79, + ); + const output = lastFrame(); + const lines = output?.split('\n'); + // Expecting 3 lines: + // 1. Spinner + Primary Text + // 2. Cancel + Timer + // 3. Right Content + expect(lines).toHaveLength(3); + if (lines) { + expect(lines[0]).toContain('Loading...'); + expect(lines[0]).not.toContain('(esc to cancel, 5s)'); + expect(lines[1]).toContain('(esc to cancel, 5s)'); + expect(lines[2]).toContain('Right'); + } + }); + + it('should use wide layout at 80 columns', () => { + const { lastFrame } = renderWithContext( + , + StreamingState.Responding, + 80, + ); + expect(lastFrame()?.includes('\n')).toBe(false); + }); + + it('should use narrow layout at 79 columns', () => { + const { lastFrame } = renderWithContext( + , + StreamingState.Responding, + 79, + ); + expect(lastFrame()?.includes('\n')).toBe(true); + }); + }); }); -- cgit v1.2.3