mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 02:24:09 -07:00
feat: add click-to-focus support for interactive shell (#13341)
This commit is contained in:
@@ -104,6 +104,10 @@ describe('InputPrompt', () => {
|
||||
useReverseSearchCompletion,
|
||||
);
|
||||
const mockedUseKittyKeyboardProtocol = vi.mocked(useKittyKeyboardProtocol);
|
||||
const mockSetEmbeddedShellFocused = vi.fn();
|
||||
const uiActions = {
|
||||
setEmbeddedShellFocused: mockSetEmbeddedShellFocused,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
@@ -240,7 +244,9 @@ describe('InputPrompt', () => {
|
||||
|
||||
it('should call shellHistory.getPreviousCommand on up arrow in shell mode', async () => {
|
||||
props.shellModeActive = true;
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[A');
|
||||
@@ -253,7 +259,9 @@ describe('InputPrompt', () => {
|
||||
|
||||
it('should call shellHistory.getNextCommand on down arrow in shell mode', async () => {
|
||||
props.shellModeActive = true;
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[B');
|
||||
@@ -269,7 +277,9 @@ describe('InputPrompt', () => {
|
||||
vi.mocked(mockShellHistory.getPreviousCommand).mockReturnValue(
|
||||
'previous command',
|
||||
);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[A');
|
||||
@@ -284,7 +294,9 @@ describe('InputPrompt', () => {
|
||||
it('should call shellHistory.addCommandToHistory on submit in shell mode', async () => {
|
||||
props.shellModeActive = true;
|
||||
props.buffer.setText('ls -l');
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
@@ -300,7 +312,9 @@ describe('InputPrompt', () => {
|
||||
|
||||
it('should NOT call shell history methods when not in shell mode', async () => {
|
||||
props.buffer.setText('some text');
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[A'); // Up arrow
|
||||
@@ -339,7 +353,9 @@ describe('InputPrompt', () => {
|
||||
|
||||
props.buffer.setText('/mem');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
// Test up arrow
|
||||
await act(async () => {
|
||||
@@ -371,7 +387,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
props.buffer.setText('/mem');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
// Test down arrow
|
||||
await act(async () => {
|
||||
@@ -398,7 +416,9 @@ describe('InputPrompt', () => {
|
||||
showSuggestions: false,
|
||||
});
|
||||
props.buffer.setText('some text');
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\u001B[A'); // Up arrow
|
||||
@@ -628,7 +648,9 @@ describe('InputPrompt', () => {
|
||||
activeSuggestionIndex: activeIndex,
|
||||
});
|
||||
props.buffer.setText(bufferText);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => stdin.write('\t'));
|
||||
await waitFor(() =>
|
||||
@@ -648,7 +670,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
props.buffer.setText('/mem');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
@@ -680,7 +704,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
props.buffer.setText('/?');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\t'); // Press Tab for autocomplete
|
||||
@@ -694,7 +720,9 @@ describe('InputPrompt', () => {
|
||||
it('should not submit on Enter when the buffer is empty or only contains whitespace', async () => {
|
||||
props.buffer.setText(' '); // Set buffer to whitespace
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r'); // Press Enter
|
||||
@@ -714,7 +742,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
props.buffer.setText('/clear');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
@@ -731,7 +761,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
props.buffer.setText('/clear');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
@@ -749,7 +781,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
props.buffer.setText('@src/components/');
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
@@ -767,7 +801,9 @@ describe('InputPrompt', () => {
|
||||
mockBuffer.cursor = [0, 11];
|
||||
mockBuffer.lines = ['first line\\'];
|
||||
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\r');
|
||||
@@ -785,7 +821,9 @@ describe('InputPrompt', () => {
|
||||
await act(async () => {
|
||||
props.buffer.setText('some text to clear');
|
||||
});
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\x03'); // Ctrl+C character
|
||||
@@ -800,7 +838,9 @@ describe('InputPrompt', () => {
|
||||
|
||||
it('should NOT clear the buffer on Ctrl+C if it is empty', async () => {
|
||||
props.buffer.text = '';
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\x03'); // Ctrl+C character
|
||||
@@ -813,7 +853,9 @@ describe('InputPrompt', () => {
|
||||
});
|
||||
|
||||
it('should call setBannerVisible(false) when clear screen key is pressed', async () => {
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { stdin, unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
stdin.write('\x0C'); // Ctrl+L
|
||||
@@ -918,7 +960,9 @@ describe('InputPrompt', () => {
|
||||
: [],
|
||||
});
|
||||
|
||||
const { unmount } = renderWithProviders(<InputPrompt {...props} />);
|
||||
const { unmount } = renderWithProviders(<InputPrompt {...props} />, {
|
||||
uiActions,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedUseCommandCompletion).toHaveBeenCalledWith(
|
||||
@@ -1988,7 +2032,7 @@ describe('InputPrompt', () => {
|
||||
|
||||
const { stdin, stdout, unmount } = renderWithProviders(
|
||||
<InputPrompt {...props} />,
|
||||
{ mouseEventsEnabled: true },
|
||||
{ mouseEventsEnabled: true, uiActions },
|
||||
);
|
||||
|
||||
// Wait for initial render
|
||||
@@ -2012,6 +2056,33 @@ describe('InputPrompt', () => {
|
||||
unmount();
|
||||
},
|
||||
);
|
||||
|
||||
it('should unfocus embedded shell on click', async () => {
|
||||
props.buffer.text = 'hello';
|
||||
props.buffer.lines = ['hello'];
|
||||
props.buffer.viewportVisualLines = ['hello'];
|
||||
props.buffer.visualToLogicalMap = [[0, 0]];
|
||||
props.isEmbeddedShellFocused = true;
|
||||
|
||||
const { stdin, stdout, unmount } = renderWithProviders(
|
||||
<InputPrompt {...props} />,
|
||||
{ mouseEventsEnabled: true, uiActions },
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(stdout.lastFrame()).toContain('hello');
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
// Click somewhere in the prompt
|
||||
stdin.write(`\x1b[<0;5;2M`);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSetEmbeddedShellFocused).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe('queued message editing', () => {
|
||||
|
||||
Reference in New Issue
Block a user