mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-05 15:31:27 +00:00
refactor(cli): unify Escape key handling in AppContainer
Consolidate Escape key behavior to improve UX and prevent conflicts: - Move Escape handling from useGeminiStream to AppContainer - Input with content: double-press to clear, then single-press to cancel - Empty input: single-press immediately cancels ongoing request - Preserve embeddedShellFocused check to allow shell's own escape handling - Update tests to use cancelOngoingRequest directly instead of simulating keypress Fixes inconsistent escape behavior between input clearing and request cancellation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
76d31d50c4
commit
e7290c5d9a
3 changed files with 74 additions and 51 deletions
|
|
@ -9,7 +9,6 @@ import type { Mock, MockInstance } from 'vitest';
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useGeminiStream } from './useGeminiStream.js';
|
||||
import { useKeypress } from './useKeypress.js';
|
||||
import * as atCommandProcessor from './atCommandProcessor.js';
|
||||
import type {
|
||||
TrackedToolCall,
|
||||
|
|
@ -107,10 +106,6 @@ vi.mock('./useVisionAutoSwitch.js', () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
vi.mock('./useKeypress.js', () => ({
|
||||
useKeypress: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./shellCommandProcessor.js', () => ({
|
||||
useShellCommandProcessor: vi.fn().mockReturnValue({
|
||||
handleShellCommand: vi.fn(),
|
||||
|
|
@ -850,28 +845,8 @@ describe('useGeminiStream', () => {
|
|||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
});
|
||||
|
||||
describe('User Cancellation', () => {
|
||||
let keypressCallback: (key: any) => void;
|
||||
const mockUseKeypress = useKeypress as Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
// Capture the callback passed to useKeypress
|
||||
mockUseKeypress.mockImplementation((callback, options) => {
|
||||
if (options.isActive) {
|
||||
keypressCallback = callback;
|
||||
} else {
|
||||
keypressCallback = () => {};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const simulateEscapeKeyPress = () => {
|
||||
act(() => {
|
||||
keypressCallback({ name: 'escape' });
|
||||
});
|
||||
};
|
||||
|
||||
it('should cancel an in-progress stream when escape is pressed', async () => {
|
||||
describe('Cancellation', () => {
|
||||
it('should cancel an in-progress stream when cancelOngoingRequest is called', async () => {
|
||||
const mockStream = (async function* () {
|
||||
yield { type: 'content', value: 'Part 1' };
|
||||
// Keep the stream open
|
||||
|
|
@ -891,8 +866,10 @@ describe('useGeminiStream', () => {
|
|||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
});
|
||||
|
||||
// Simulate escape key press
|
||||
simulateEscapeKeyPress();
|
||||
// Call cancelOngoingRequest directly
|
||||
act(() => {
|
||||
result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
// Verify cancellation message is added
|
||||
await waitFor(() => {
|
||||
|
|
@ -909,7 +886,7 @@ describe('useGeminiStream', () => {
|
|||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
});
|
||||
|
||||
it('should call onCancelSubmit handler when escape is pressed', async () => {
|
||||
it('should call onCancelSubmit handler when cancelOngoingRequest is called', async () => {
|
||||
const cancelSubmitSpy = vi.fn();
|
||||
const mockStream = (async function* () {
|
||||
yield { type: 'content', value: 'Part 1' };
|
||||
|
|
@ -947,12 +924,14 @@ describe('useGeminiStream', () => {
|
|||
result.current.submitQuery('test query');
|
||||
});
|
||||
|
||||
simulateEscapeKeyPress();
|
||||
act(() => {
|
||||
result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
expect(cancelSubmitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call setShellInputFocused(false) when escape is pressed', async () => {
|
||||
it('should call setShellInputFocused(false) when cancelOngoingRequest is called', async () => {
|
||||
const setShellInputFocusedSpy = vi.fn();
|
||||
const mockStream = (async function* () {
|
||||
yield { type: 'content', value: 'Part 1' };
|
||||
|
|
@ -989,18 +968,22 @@ describe('useGeminiStream', () => {
|
|||
result.current.submitQuery('test query');
|
||||
});
|
||||
|
||||
simulateEscapeKeyPress();
|
||||
act(() => {
|
||||
result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
expect(setShellInputFocusedSpy).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should not do anything if escape is pressed when not responding', () => {
|
||||
it('should not do anything if cancelOngoingRequest is called when not responding', () => {
|
||||
const { result } = renderTestHook();
|
||||
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
|
||||
// Simulate escape key press
|
||||
simulateEscapeKeyPress();
|
||||
// Call cancelOngoingRequest
|
||||
act(() => {
|
||||
result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
// No change should happen, no cancellation message
|
||||
expect(mockAddItem).not.toHaveBeenCalledWith(
|
||||
|
|
@ -1035,7 +1018,9 @@ describe('useGeminiStream', () => {
|
|||
});
|
||||
|
||||
// Cancel the request
|
||||
simulateEscapeKeyPress();
|
||||
act(() => {
|
||||
result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
// Allow the stream to continue
|
||||
act(() => {
|
||||
|
|
@ -1083,7 +1068,9 @@ describe('useGeminiStream', () => {
|
|||
expect(result.current.streamingState).toBe(StreamingState.Responding);
|
||||
|
||||
// Try to cancel
|
||||
simulateEscapeKeyPress();
|
||||
act(() => {
|
||||
result.current.cancelOngoingRequest();
|
||||
});
|
||||
|
||||
// Nothing should happen because the state is not `Responding`
|
||||
expect(abortSpy).not.toHaveBeenCalled();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue