diff --git a/.github/workflows/ci-bundling-trial.yml b/.github/workflows/ci-bundling-trial.yml index 3d247a888b..af4a8a2cd6 100644 --- a/.github/workflows/ci-bundling-trial.yml +++ b/.github/workflows/ci-bundling-trial.yml @@ -27,7 +27,7 @@ jobs: mode: 'source' - name: 'Run Suspect Tests' - run: 'npx vitest run --pool=forks packages/cli/src/test-utils/render.test.tsx packages/cli/src/ui/App.test.tsx packages/cli/src/test-utils/AppRig.test.tsx packages/cli/src/ui/components/SettingsDialog.test.tsx packages/cli/src/ui/components/messages/DiffRenderer.test.tsx packages/cli/src/ui/components/messages/ThinkingMessage.test.tsx packages/cli/src/ui/components/messages/ToolResultDisplay.test.tsx packages/cli/src/ui/components/messages/ToolResultDisplayOverflow.test.tsx packages/cli/src/ui/components/messages/UserMessage.test.tsx packages/cli/src/ui/components/shared/SearchableList.test.tsx packages/cli/src/ui/components/shared/TextInput.test.tsx packages/cli/src/ui/components/views/ExtensionRegistryView.test.tsx' + run: 'npx vitest run --pool=forks packages/cli/src/ui/App.test.tsx' shell: 'bash' diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx index 4280fdf4cf..1dc52a5111 100644 --- a/packages/cli/src/test-utils/render.tsx +++ b/packages/cli/src/test-utils/render.tsx @@ -10,7 +10,6 @@ import { type RenderOptions, } from 'ink'; import { EventEmitter } from 'node:events'; -import { Writable } from 'node:stream'; import { Box } from 'ink'; import { Terminal } from '@xterm/headless'; import { vi } from 'vitest'; @@ -110,6 +109,7 @@ class XtermStdout extends EventEmitter { renderCount = 0; private queue: { promise: Promise }; isTTY = true; + public clearScreenOnRender = true; getColorDepth(): number { return 24; @@ -118,10 +118,11 @@ class XtermStdout extends EventEmitter { private lastRenderOutput: string | undefined = undefined; private lastRenderStaticContent: string | undefined = undefined; - constructor(state: TerminalState, queue: { promise: Promise }) { + constructor(state: TerminalState, queue: { promise: Promise }, clearScreenOnRender = true) { super(); this.state = state; this.queue = queue; + this.clearScreenOnRender = clearScreenOnRender; } get columns() { @@ -160,8 +161,11 @@ class XtermStdout extends EventEmitter { this.renderCount++; this.lastRenderStaticContent = staticContent; this.lastRenderOutput = output; - // Clear screen and move cursor to home before writing the new frame - this.write('\x1b[2J\x1b[H' + staticContent + output); + if (this.clearScreenOnRender) { + this.write('\x1b[2J\x1b[H' + staticContent + output); + } else { + this.write(staticContent + output); + } this.emit('render'); }; @@ -405,6 +409,7 @@ export const render = async ( terminalWidth?: number, terminalHeight?: number, allowEmptyFrame = false, + clearScreenOnRender = true, ): Promise< Omit > => { @@ -426,21 +431,16 @@ export const render = async ( cols, rows, }; - const writeQueue = { promise: Promise.resolve() }; - const stdout = new XtermStdout(state, writeQueue); - const stderr = new XtermStderr(state, writeQueue); + const queue = { promise: Promise.resolve() }; + const stdout = new XtermStdout(state, queue, clearScreenOnRender); + const stderr = new XtermStderr(state, queue); const stdin = new XtermStdin(); let instance!: InkInstance; stdout.clear(); - const dummyStdout = new Writable({ - write(_chunk, _encoding, callback) { - callback(); - }, - }); act(() => { instance = inkRenderDirect(tree, { - stdout: dummyStdout as unknown as NodeJS.WriteStream, + stdout: stdout as unknown as NodeJS.WriteStream, stderr: stderr as unknown as NodeJS.WriteStream, @@ -654,6 +654,7 @@ export const renderWithProviders = async ( toolActions, persistentState, appState = mockAppState, + clearScreenOnRender = true, }: { shellFocus?: boolean; settings?: LoadedSettings; @@ -675,6 +676,7 @@ export const renderWithProviders = async ( set?: typeof persistentStateMock.set; }; appState?: AppState; + clearScreenOnRender?: boolean; } = {}, ): Promise => { const baseState: UIState = new Proxy( @@ -837,6 +839,8 @@ export const renderWithProviders = async ( wrapWithProviders(component), terminalWidth, terminalHeight, + false, + clearScreenOnRender, ); return { diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index 24633f8154..de2f6d32de 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -11,6 +11,10 @@ import { createMockSettings } from '../test-utils/settings.js'; import { Text, useIsScreenReaderEnabled, type DOMElement } from 'ink'; import { App } from './App.js'; import { type UIState } from './contexts/UIStateContext.js'; + +vi.mock('./hooks/useTips.js', () => ({ + useTips: () => ({ showTips: true }), +})); import { StreamingState } from './types.js'; import { makeFakeConfig, CoreToolCallStatus } from '@google/gemini-cli-core'; @@ -95,11 +99,27 @@ describe.sequential('App', () => { const { lastFrame, unmount } = await renderWithProviders(, { uiState: mockUIState, settings: createMockSettings({ ui: { useAlternateBuffer: false } }), + clearScreenOnRender: false, }); - expect(lastFrame()).toContain('Tips for getting started'); - expect(lastFrame()).toContain('Notifications'); - expect(lastFrame()).toContain('Composer'); + let attempts = 0; + let frame = ''; + while (attempts < 100) { + frame = lastFrame(); + if ( + frame.includes('Tips for getting started') && + frame.includes('Notifications') && + frame.includes('Composer') + ) { + break; + } + await new Promise((resolve) => setTimeout(resolve, 10)); + attempts++; + } + + expect(frame).toContain('Tips for getting started'); + expect(frame).toContain('Notifications'); + expect(frame).toContain('Composer'); unmount(); }); diff --git a/packages/cli/src/ui/__snapshots__/App.test.tsx.snap b/packages/cli/src/ui/__snapshots__/App.test.tsx.snap index e1683de18b..21387ebb17 100644 --- a/packages/cli/src/ui/__snapshots__/App.test.tsx.snap +++ b/packages/cli/src/ui/__snapshots__/App.test.tsx.snap @@ -37,24 +37,45 @@ Tips for getting started: -Notifications - -Composer - - ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ - ▝▜▄ █▌ █▙▟ ▐█▝█▛▐█ ▐█ ▐█▝█▖█▌ █▌ - ▗▟▀ ▜▙ ▝█▛ █▌▝ ▖▐█ ▐█ ▐█ ▐█ ▝██▌ █▌ - ▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀ - - Gemini CLI v1.2.3 -Tips for getting started: -1. Create GEMINI.md files to customize your interactions -2. /help for more information -3. Ask coding questions, edit code or run commands -4. Be specific for the best results + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -95,23 +116,6 @@ Footer -Tips for getting started: -1. Create GEMINI.md files to customize your interactions -2. /help for more information -3. Ask coding questions, edit code or run commands -4. Be specific for the best results -ComposerNotifications -Footer - - ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ - ▝▜▄ █▌ █▙▟ ▐█▝█▛▐█ ▐█ ▐█▝█▖█▌ █▌ - ▗▟▀ ▜▙ ▝█▛ █▌▝ ▖▐█ ▐█ ▐█ ▐█ ▝██▌ █▌ - ▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀ - - Gemini CLI v1.2.3 - - - Tips for getting started: 1. Create GEMINI.md files to customize your interactions 2. /help for more information @@ -132,6 +136,34 @@ exports[`App > Snapshots > renders with dialogs visible 1`] = ` +Tips for getting started: +1. Create GEMINI.md files to customize your interactions +2. /help for more information +3. Ask coding questions, edit code or run commands +4. Be specific for the best results + + + + + + + + + + + + + + + + + + + + + + + @@ -158,16 +190,9 @@ exports[`App > Snapshots > renders with dialogs visible 1`] = ` -Notifications -DialogManager - ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ - ▝▜▄ █▌ █▙▟ ▐█▝█▛▐█ ▐█ ▐█▝█▖█▌ █▌ - ▗▟▀ ▜▙ ▝█▛ █▌▝ ▖▐█ ▐█ ▐█ ▐█ ▝██▌ █▌ - ▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀ - Gemini CLI v1.2.3 @@ -220,59 +245,80 @@ Tips for getting started: 3. Ask coding questions, edit code or run commands 4. Be specific for the best results HistoryItemDisplay -╭─────────────────────────────────────────────────────────────────────────────── -│ Action Required -│ -│ ? ls list directory -│ -│ ╭───────────────────────────────────────────────────────────────────────────── -│ │ ls -│ ╰───────────────────────────────────────────────────────────────────────────── -│ Allow execution of [ls]? -│ -│ ● 1. Allow once -│ 2. Allow for this session -│ 3. No, suggest changes (esc) -╰─────────────────────────────────────────────────────────────────────────────── +╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Action Required │ +│ │ +│ ? ls list directory │ +│ │ +│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │ +│ │ ls │ │ +│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │ +│ Allow execution of [ls]? │ +│ │ +│ ● 1. Allow once │ +│ 2. Allow for this session │ +│ 3. No, suggest changes (esc) │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -Notifications - -Composer - - ▝▜▄ ▗█▀▀▜▙▝█▛▀▀▌▜██▖▟██▘▜█▘▜██▖▝█▛▝█▛ - ▝▜▄ █▌ █▙▟ ▐█▝█▛▐█ ▐█ ▐█▝█▖█▌ █▌ - ▗▟▀ ▜▙ ▝█▛ █▌▝ ▖▐█ ▐█ ▐█ ▐█ ▝██▌ █▌ - ▝▀ ▀▀▀▀▘▝▀▀▀▀▘▀▀▘ ▀▀▘▀▀▘▀▀▘ ▝▀▀▝▀▀ - - Gemini CLI v1.2.3 -Tips for getting started: -1. Create GEMINI.md files to customize your interactions -2. /help for more information -3. Ask coding questions, edit code or run commands -4. Be specific for the best results -HistoryItemDisplay -╭─────────────────────────────────────────────────────────────────────────────── -│ Action Required -│ -│ ? ls list directory -│ -│ ╭───────────────────────────────────────────────────────────────────────────── -│ │ ls -│ ╰───────────────────────────────────────────────────────────────────────────── -│ Allow execution of [ls]? -│ -│ ● 1. Allow once -│ 2. Allow for this session -│ 3. No, suggest changes (esc) -╰───────────────────────────────────────────────────────────────────────────────