mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Fixes for Ink 6.4.0 (#12352)
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { render as inkRender } from 'ink-testing-library';
|
||||
import { Box } from 'ink';
|
||||
import type React from 'react';
|
||||
import { act } from 'react';
|
||||
import { LoadedSettings, type Settings } from '../config/settings.js';
|
||||
@@ -22,6 +23,7 @@ import { type Config } from '@google/gemini-cli-core';
|
||||
// Wrapper around ink-testing-library's render that ensures act() is called
|
||||
export const render = (
|
||||
tree: React.ReactElement,
|
||||
terminalWidth?: number,
|
||||
): ReturnType<typeof inkRender> => {
|
||||
let renderResult: ReturnType<typeof inkRender> =
|
||||
undefined as unknown as ReturnType<typeof inkRender>;
|
||||
@@ -29,6 +31,19 @@ export const render = (
|
||||
renderResult = inkRender(tree);
|
||||
});
|
||||
|
||||
if (terminalWidth !== undefined && renderResult?.stdout) {
|
||||
// Override the columns getter on the stdout instance provided by ink-testing-library
|
||||
Object.defineProperty(renderResult.stdout, 'columns', {
|
||||
get: () => terminalWidth,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
// Trigger a rerender so Ink can pick up the new terminal width
|
||||
act(() => {
|
||||
renderResult.rerender(tree);
|
||||
});
|
||||
}
|
||||
|
||||
const originalUnmount = renderResult.unmount;
|
||||
const originalRerender = renderResult.rerender;
|
||||
|
||||
@@ -148,13 +163,21 @@ export const renderWithProviders = (
|
||||
<VimModeProvider settings={settings}>
|
||||
<ShellFocusContext.Provider value={shellFocus}>
|
||||
<KeypressProvider kittyProtocolEnabled={kittyProtocolEnabled}>
|
||||
{component}
|
||||
<Box
|
||||
width={terminalWidth}
|
||||
flexShrink={0}
|
||||
flexGrow={0}
|
||||
flexDirection="column"
|
||||
>
|
||||
{component}
|
||||
</Box>
|
||||
</KeypressProvider>
|
||||
</ShellFocusContext.Provider>
|
||||
</VimModeProvider>
|
||||
</UIStateContext.Provider>
|
||||
</SettingsContext.Provider>
|
||||
</ConfigContext.Provider>,
|
||||
terminalWidth,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ const renderWithContext = (
|
||||
<StreamingContext.Provider value={contextValue}>
|
||||
{ui}
|
||||
</StreamingContext.Provider>,
|
||||
width,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -67,7 +67,10 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
</Text>
|
||||
)}
|
||||
{!isNarrow && cancelAndTimerContent && (
|
||||
<Text color={theme.text.secondary}> {cancelAndTimerContent}</Text>
|
||||
<>
|
||||
<Box flexShrink={0} width={1} />
|
||||
<Text color={theme.text.secondary}>{cancelAndTimerContent}</Text>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{!isNarrow && <Box flexGrow={1}>{/* Spacer */}</Box>}
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('LoopDetectionConfirmation', () => {
|
||||
it('renders correctly', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<LoopDetectionConfirmation onComplete={onComplete} />,
|
||||
{ width: 101 },
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
@@ -21,6 +22,7 @@ describe('LoopDetectionConfirmation', () => {
|
||||
it('contains the expected options', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<LoopDetectionConfirmation onComplete={onComplete} />,
|
||||
{ width: 100 },
|
||||
);
|
||||
const output = lastFrame()!.toString();
|
||||
|
||||
|
||||
@@ -50,37 +50,39 @@ export function LoopDetectionConfirmation({
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={theme.status.warning}
|
||||
width="100%"
|
||||
marginLeft={1}
|
||||
>
|
||||
<Box paddingX={1} paddingY={0} flexDirection="column">
|
||||
<Box minHeight={1}>
|
||||
<Box minWidth={3}>
|
||||
<Text color={theme.status.warning} aria-label="Loop detected:">
|
||||
?
|
||||
</Text>
|
||||
<Box width="100%" flexDirection="row">
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={theme.status.warning}
|
||||
flexGrow={1}
|
||||
marginLeft={1}
|
||||
>
|
||||
<Box paddingX={1} paddingY={0} flexDirection="column">
|
||||
<Box minHeight={1}>
|
||||
<Box minWidth={3}>
|
||||
<Text color={theme.status.warning} aria-label="Loop detected:">
|
||||
?
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text wrap="truncate-end">
|
||||
<Text color={theme.text.primary} bold>
|
||||
A potential loop was detected
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text wrap="truncate-end">
|
||||
<Text color={theme.text.primary} bold>
|
||||
A potential loop was detected
|
||||
</Text>{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box width="100%" marginTop={1}>
|
||||
<Box flexDirection="column">
|
||||
<Text color={theme.text.secondary}>
|
||||
This can happen due to repetitive tool calls or other model
|
||||
behavior. Do you want to keep loop detection enabled or disable it
|
||||
for this session?
|
||||
</Text>
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect items={OPTIONS} onSelect={onComplete} />
|
||||
<Box marginTop={1}>
|
||||
<Box flexDirection="column">
|
||||
<Text color={theme.text.secondary}>
|
||||
This can happen due to repetitive tool calls or other model
|
||||
behavior. Do you want to keep loop detection enabled or disable
|
||||
it for this session?
|
||||
</Text>
|
||||
<Box marginTop={1}>
|
||||
<RadioButtonSelect items={OPTIONS} onSelect={onComplete} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -73,13 +73,14 @@ describe('PrepareLabel', () => {
|
||||
textColor={color}
|
||||
isExpanded={true}
|
||||
/>,
|
||||
100,
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('creates centered window around match when collapsed', () => {
|
||||
const prefix = 'cd /very/long/path/that/keeps/going/'.repeat(3);
|
||||
const prefix = 'cd_/very/long/path/that/keeps/going/'.repeat(3);
|
||||
const core = 'search-here';
|
||||
const suffix = '/and/then/some/more/components/'.repeat(3);
|
||||
const label = prefix + core + suffix;
|
||||
@@ -92,6 +93,7 @@ describe('PrepareLabel', () => {
|
||||
textColor={color}
|
||||
isExpanded={false}
|
||||
/>,
|
||||
100,
|
||||
);
|
||||
const out = lastFrame();
|
||||
const f = flat(out);
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('ShellConfirmationDialog', () => {
|
||||
it('renders correctly', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ShellConfirmationDialog request={request} />,
|
||||
{ width: 101 },
|
||||
);
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
@@ -45,6 +46,7 @@ describe('ShellConfirmationDialog', () => {
|
||||
it('calls onConfirm with Cancel when "No (esc)" is selected', () => {
|
||||
const { lastFrame } = renderWithProviders(
|
||||
<ShellConfirmationDialog request={request} />,
|
||||
{ width: 100 },
|
||||
);
|
||||
const select = lastFrame()!.toString();
|
||||
// Simulate selecting the third option
|
||||
|
||||
@@ -68,41 +68,43 @@ export const ShellConfirmationDialog: React.FC<
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={theme.status.warning}
|
||||
padding={1}
|
||||
width="100%"
|
||||
marginLeft={1}
|
||||
>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Shell Command Execution
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
A custom command wants to run the following shell commands:
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={theme.border.default}
|
||||
paddingX={1}
|
||||
marginTop={1}
|
||||
>
|
||||
{commands.map((cmd) => (
|
||||
<Text key={cmd} color={theme.text.link}>
|
||||
<RenderInline text={cmd} />
|
||||
</Text>
|
||||
))}
|
||||
<Box flexDirection="row" width="100%">
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={theme.status.warning}
|
||||
padding={1}
|
||||
flexGrow={1}
|
||||
marginLeft={1}
|
||||
>
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Shell Command Execution
|
||||
</Text>
|
||||
<Text color={theme.text.primary}>
|
||||
A custom command wants to run the following shell commands:
|
||||
</Text>
|
||||
<Box
|
||||
flexDirection="column"
|
||||
borderStyle="round"
|
||||
borderColor={theme.border.default}
|
||||
paddingX={1}
|
||||
marginTop={1}
|
||||
>
|
||||
{commands.map((cmd) => (
|
||||
<Text key={cmd} color={theme.text.link}>
|
||||
<RenderInline text={cmd} />
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box marginBottom={1}>
|
||||
<Text color={theme.text.primary}>Do you want to proceed?</Text>
|
||||
</Box>
|
||||
<Box marginBottom={1}>
|
||||
<Text color={theme.text.primary}>Do you want to proceed?</Text>
|
||||
</Box>
|
||||
|
||||
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused />
|
||||
<RadioButtonSelect items={options} onSelect={handleSelect} isFocused />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -112,7 +112,7 @@ export function SuggestionsDisplay({
|
||||
</Box>
|
||||
)}
|
||||
{isActive && isLong && (
|
||||
<Box>
|
||||
<Box width={3} flexShrink={0}>
|
||||
<Text color={Colors.Gray}>{isExpanded ? ' ← ' : ' → '}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and collapses long suggestion via Right/Left arrows > command-search-render-collapsed-match 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ (r:) Type your message or @path/to/file │
|
||||
│ (r:) Type your message or @path/to/file │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll →
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
@@ -11,7 +11,7 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and c
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and collapses long suggestion via Right/Left arrows > command-search-render-expanded-match 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ (r:) Type your message or @path/to/file │
|
||||
│ (r:) Type your message or @path/to/file │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll ←
|
||||
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
|
||||
@@ -20,38 +20,38 @@ exports[`InputPrompt > command search (Ctrl+R when not in shell) > expands and c
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-render-collapsed-match 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ (r:) commit │
|
||||
│ (r:) commit │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
git commit -m "feat: add search" in src/app"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > command search (Ctrl+R when not in shell) > renders match window and expanded view (snapshots) > command-search-render-expanded-match 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ (r:) commit │
|
||||
│ (r:) commit │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
git commit -m "feat: add search" in src/app"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ > Type your message or @path/to/file │
|
||||
│ > Type your message or @path/to/file │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly in shell mode 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ! Type your message or @path/to/file │
|
||||
│ ! Type your message or @path/to/file │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly in yolo mode 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ * Type your message or @path/to/file │
|
||||
│ * Type your message or @path/to/file │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`InputPrompt > snapshots > should render correctly when accepting edits 1`] = `
|
||||
"╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ > Type your message or @path/to/file │
|
||||
│ > Type your message or @path/to/file │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<LoadingIndicator /> > should truncate long primary text instead of wrapping 1`] = `
|
||||
"MockResponding This is an extremely long loading phrase that should be truncated in t (esc to
|
||||
Spinner cancel, 5s)"
|
||||
"MockRespondin This is an extremely long loading phrase that shoul… (esc to
|
||||
gSpinner cancel, 5s)"
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`PrepareLabel > creates centered window around match when collapsed 1`] = `
|
||||
"...ry/long/path/that/keeps/going/cd /very/long/path/that/keeps/going/search-here/and/then/some/more/
|
||||
"...ry/long/path/that/keeps/going/cd_/very/long/path/that/keeps/going/search-here/and/then/some/more/
|
||||
components//and/then/some/more/components//and/..."
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user