fix(cli): ctrl c/ctrl d close cli when in dialogs (#8685)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
Co-authored-by: matt korwel <matt.korwel@gmail.com>
This commit is contained in:
fuyou
2025-09-19 14:52:29 +08:00
committed by GitHub
parent 938c850ed8
commit e48f61bdc7
4 changed files with 231 additions and 25 deletions
+160
View File
@@ -605,4 +605,164 @@ describe('AppContainer State Management', () => {
expect(lastCall[2]).toBe(1);
});
});
describe('Keyboard Input Handling', () => {
it('should block quit command during authentication', () => {
mockedUseAuthCommand.mockReturnValue({
authState: 'unauthenticated',
setAuthState: vi.fn(),
authError: null,
onAuthError: vi.fn(),
});
const mockHandleSlashCommand = vi.fn();
mockedUseSlashCommandProcessor.mockReturnValue({
handleSlashCommand: mockHandleSlashCommand,
slashCommands: [],
pendingHistoryItems: [],
commandContext: {},
shellConfirmationRequest: null,
confirmationRequest: null,
});
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
});
it('should prevent exit command when text buffer has content', () => {
mockedUseTextBuffer.mockReturnValue({
text: 'some user input',
setText: vi.fn(),
});
const mockHandleSlashCommand = vi.fn();
mockedUseSlashCommandProcessor.mockReturnValue({
handleSlashCommand: mockHandleSlashCommand,
slashCommands: [],
pendingHistoryItems: [],
commandContext: {},
shellConfirmationRequest: null,
confirmationRequest: null,
});
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
});
it('should require double Ctrl+C to exit when dialogs are open', () => {
vi.useFakeTimers();
mockedUseThemeCommand.mockReturnValue({
isThemeDialogOpen: true,
openThemeDialog: vi.fn(),
handleThemeSelect: vi.fn(),
handleThemeHighlight: vi.fn(),
});
const mockHandleSlashCommand = vi.fn();
mockedUseSlashCommandProcessor.mockReturnValue({
handleSlashCommand: mockHandleSlashCommand,
slashCommands: [],
pendingHistoryItems: [],
commandContext: {},
shellConfirmationRequest: null,
confirmationRequest: null,
});
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
vi.useRealTimers();
});
it('should cancel ongoing request on first Ctrl+C', () => {
const mockCancelOngoingRequest = vi.fn();
mockedUseGeminiStream.mockReturnValue({
streamingState: 'responding',
submitQuery: vi.fn(),
initError: null,
pendingHistoryItems: [],
thought: null,
cancelOngoingRequest: mockCancelOngoingRequest,
});
const mockHandleSlashCommand = vi.fn();
mockedUseSlashCommandProcessor.mockReturnValue({
handleSlashCommand: mockHandleSlashCommand,
slashCommands: [],
pendingHistoryItems: [],
commandContext: {},
shellConfirmationRequest: null,
confirmationRequest: null,
});
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
});
it('should reset Ctrl+C state after timeout', () => {
vi.useFakeTimers();
const mockHandleSlashCommand = vi.fn();
mockedUseSlashCommandProcessor.mockReturnValue({
handleSlashCommand: mockHandleSlashCommand,
slashCommands: [],
pendingHistoryItems: [],
commandContext: {},
shellConfirmationRequest: null,
confirmationRequest: null,
});
render(
<AppContainer
config={mockConfig}
settings={mockSettings}
version="1.0.0"
initializationResult={mockInitResult}
/>,
);
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
vi.advanceTimersByTime(1001);
expect(mockHandleSlashCommand).not.toHaveBeenCalledWith('/quit');
vi.useRealTimers();
});
});
});