Support ctrl-C and Ctrl-D correctly Refactor so InputPrompt has priority over AppContainer for input handling. (#17993)

This commit is contained in:
Jacob Richman
2026-01-30 16:11:14 -08:00
committed by GitHub
parent 0fe8492569
commit 00fdb30211
6 changed files with 193 additions and 62 deletions
+62 -22
View File
@@ -371,7 +371,9 @@ describe('AppContainer State Management', () => {
mockedUseTextBuffer.mockReturnValue({
text: '',
setText: vi.fn(),
// Add other properties if AppContainer uses them
lines: [''],
cursor: [0, 0],
handleInput: vi.fn().mockReturnValue(false),
});
mockedUseLogger.mockReturnValue({
getPreviousUserMessages: vi.fn().mockResolvedValue([]),
@@ -1900,7 +1902,7 @@ describe('AppContainer State Management', () => {
});
describe('Keyboard Input Handling (CTRL+C / CTRL+D)', () => {
let handleGlobalKeypress: (key: Key) => void;
let handleGlobalKeypress: (key: Key) => boolean;
let mockHandleSlashCommand: Mock;
let mockCancelOngoingRequest: Mock;
let rerender: () => void;
@@ -1935,9 +1937,11 @@ describe('AppContainer State Management', () => {
beforeEach(() => {
// Capture the keypress handler from the AppContainer
mockedUseKeypress.mockImplementation((callback: (key: Key) => void) => {
handleGlobalKeypress = callback;
});
mockedUseKeypress.mockImplementation(
(callback: (key: Key) => boolean) => {
handleGlobalKeypress = callback;
},
);
// Mock slash command handler
mockHandleSlashCommand = vi.fn();
@@ -1961,6 +1965,9 @@ describe('AppContainer State Management', () => {
mockedUseTextBuffer.mockReturnValue({
text: '',
setText: vi.fn(),
lines: [''],
cursor: [0, 0],
handleInput: vi.fn().mockReturnValue(false),
});
vi.useFakeTimers();
@@ -2020,19 +2027,6 @@ describe('AppContainer State Management', () => {
});
describe('CTRL+D', () => {
it('should do nothing if text buffer is not empty', async () => {
mockedUseTextBuffer.mockReturnValue({
text: 'some text',
setText: vi.fn(),
});
await setupKeypressTest();
pressKey({ name: 'd', ctrl: true }, 2);
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
unmount();
});
it('should quit on second press if buffer is empty', async () => {
await setupKeypressTest();
@@ -2047,6 +2041,50 @@ describe('AppContainer State Management', () => {
unmount();
});
it('should NOT quit if buffer is not empty (bubbles from InputPrompt)', async () => {
mockedUseTextBuffer.mockReturnValue({
text: 'some text',
setText: vi.fn(),
lines: ['some text'],
cursor: [0, 9], // At the end
handleInput: vi.fn().mockReturnValue(false),
});
await setupKeypressTest();
// Capture return value
let result = true;
const originalPressKey = (key: Partial<Key>) => {
act(() => {
result = handleGlobalKeypress({
name: 'd',
shift: false,
alt: false,
ctrl: true,
cmd: false,
...key,
} as Key);
});
rerender();
};
originalPressKey({ name: 'd', ctrl: true });
// AppContainer's handler should return true if it reaches it
expect(result).toBe(true);
// But it should only be called once, so count is 1, not quitting yet.
expect(mockHandleSlashCommand).not.toHaveBeenCalled();
originalPressKey({ name: 'd', ctrl: true });
// Now count is 2, it should quit.
expect(mockHandleSlashCommand).toHaveBeenCalledWith(
'/quit',
undefined,
undefined,
false,
);
unmount();
});
it('should reset press count after a timeout', async () => {
await setupKeypressTest();
@@ -2066,7 +2104,7 @@ describe('AppContainer State Management', () => {
});
describe('Copy Mode (CTRL+S)', () => {
let handleGlobalKeypress: (key: Key) => void;
let handleGlobalKeypress: (key: Key) => boolean;
let rerender: () => void;
let unmount: () => void;
@@ -2096,9 +2134,11 @@ describe('AppContainer State Management', () => {
beforeEach(() => {
mocks.mockStdout.write.mockClear();
mockedUseKeypress.mockImplementation((callback: (key: Key) => void) => {
handleGlobalKeypress = callback;
});
mockedUseKeypress.mockImplementation(
(callback: (key: Key) => boolean) => {
handleGlobalKeypress = callback;
},
);
vi.useFakeTimers();
});