mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix: add test code again
This commit is contained in:
@@ -736,16 +736,16 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
// With the current implementation, fragmented data gets processed differently
|
||||||
|
// The first fragment '\x1b[20' gets processed as individual characters
|
||||||
|
// The second fragment '0~content\x1b[2' gets processed as paste + individual chars
|
||||||
|
// The third fragment '01~' gets processed as individual characters
|
||||||
|
expect(keyHandler).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Should properly reconstruct and handle the paste sequence
|
// The current implementation processes fragmented paste markers as separate events
|
||||||
expect(keyHandler).toHaveBeenCalledWith(
|
// rather than reconstructing them into a single paste event
|
||||||
expect.objectContaining({
|
expect(keyHandler.mock.calls.length).toBeGreaterThan(1);
|
||||||
paste: true,
|
|
||||||
sequence: 'content',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -812,15 +812,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||||||
stdin.emit('data', Buffer.from('a'));
|
stdin.emit('data', Buffer.from('a'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Should not emit immediately
|
// With the current implementation, single characters are processed immediately
|
||||||
expect(keyHandler).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Advance timer to trigger timeout
|
|
||||||
act(() => {
|
|
||||||
vi.advanceTimersByTime(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should emit after timeout
|
|
||||||
expect(keyHandler).toHaveBeenCalledWith(
|
expect(keyHandler).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
name: 'a',
|
name: 'a',
|
||||||
@@ -860,22 +852,36 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||||||
stdin.emit('data', Buffer.from('lo'));
|
stdin.emit('data', Buffer.from('lo'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Should not have emitted yet
|
// With the current implementation, data is processed as it arrives
|
||||||
expect(keyHandler).not.toHaveBeenCalled();
|
// First chunk 'hel' is treated as paste (multi-character)
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
// Complete the timeout
|
1,
|
||||||
act(() => {
|
|
||||||
vi.advanceTimersByTime(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should emit as single paste event (multi-character data treated as paste)
|
|
||||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
||||||
expect(keyHandler).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
paste: true,
|
paste: true,
|
||||||
sequence: 'hello',
|
sequence: 'hel',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Second chunk 'lo' is processed as individual characters
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'l',
|
||||||
|
sequence: 'l',
|
||||||
|
paste: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
|
3,
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'o',
|
||||||
|
sequence: 'o',
|
||||||
|
paste: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(keyHandler).toHaveBeenCalledTimes(3);
|
||||||
} finally {
|
} finally {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
}
|
}
|
||||||
@@ -951,20 +957,7 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||||||
stdin.emit('data', Buffer.from('b'));
|
stdin.emit('data', Buffer.from('b'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Advance by the original remaining time (1ms)
|
// With the current implementation, both characters are processed immediately
|
||||||
act(() => {
|
|
||||||
vi.advanceTimersByTime(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Should not have emitted yet because timeout was reset
|
|
||||||
expect(keyHandler).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
// Complete the new timeout period
|
|
||||||
act(() => {
|
|
||||||
vi.advanceTimersByTime(7);
|
|
||||||
});
|
|
||||||
|
|
||||||
// The current implementation emits 2 individual keypress events for 'a' and 'b'
|
|
||||||
expect(keyHandler).toHaveBeenCalledTimes(2);
|
expect(keyHandler).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
// First event should be 'a', second should be 'b'
|
// First event should be 'a', second should be 'b'
|
||||||
@@ -1060,20 +1053,48 @@ describe('KeypressContext - Kitty Protocol', () => {
|
|||||||
stdin.emit('data', Buffer.from('o'));
|
stdin.emit('data', Buffer.from('o'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Should not have emitted yet
|
// With the current implementation, each character is processed immediately
|
||||||
expect(keyHandler).not.toHaveBeenCalled();
|
expect(keyHandler).toHaveBeenCalledTimes(5);
|
||||||
|
|
||||||
// Complete timeout
|
// Each character should be processed as individual keypress events
|
||||||
act(() => {
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
vi.advanceTimersByTime(8);
|
1,
|
||||||
});
|
|
||||||
|
|
||||||
// Should emit as single paste event (multi-character data treated as paste)
|
|
||||||
expect(keyHandler).toHaveBeenCalledTimes(1);
|
|
||||||
expect(keyHandler).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
paste: true,
|
name: 'h',
|
||||||
sequence: 'hello',
|
sequence: 'h',
|
||||||
|
paste: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'e',
|
||||||
|
sequence: 'e',
|
||||||
|
paste: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
|
3,
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'l',
|
||||||
|
sequence: 'l',
|
||||||
|
paste: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
|
4,
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'l',
|
||||||
|
sequence: 'l',
|
||||||
|
paste: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(keyHandler).toHaveBeenNthCalledWith(
|
||||||
|
5,
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'o',
|
||||||
|
sequence: 'o',
|
||||||
|
paste: false,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ import { FOCUS_IN, FOCUS_OUT } from '../hooks/useFocus.js';
|
|||||||
const ESC = '\u001B';
|
const ESC = '\u001B';
|
||||||
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
|
export const PASTE_MODE_PREFIX = `${ESC}[200~`;
|
||||||
export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
|
export const PASTE_MODE_SUFFIX = `${ESC}[201~`;
|
||||||
const RAW_PASTE_DEBOUNCE_MS = 8; // Debounce window to coalesce fragmented paste chunks
|
|
||||||
const RAW_PASTE_BUFFER_LIMIT = 32;
|
|
||||||
|
|
||||||
export interface Key {
|
export interface Key {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -437,16 +435,18 @@ export function KeypressProvider({
|
|||||||
// Buffer the incoming data
|
// Buffer the incoming data
|
||||||
rawDataBuffer = Buffer.concat([rawDataBuffer, data]);
|
rawDataBuffer = Buffer.concat([rawDataBuffer, data]);
|
||||||
|
|
||||||
// If buffered data exceeds limit, flush immediately
|
|
||||||
if (rawDataBuffer.length > RAW_PASTE_BUFFER_LIMIT) {
|
|
||||||
clearRawFlushTimeout();
|
|
||||||
flushRawBuffer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearRawFlushTimeout();
|
clearRawFlushTimeout();
|
||||||
|
|
||||||
rawFlushTimeout = setTimeout(flushRawBuffer, RAW_PASTE_DEBOUNCE_MS);
|
// On some Windows terminals, during a paste, the terminal might send a
|
||||||
|
// single return character chunk. In this case, we need to wait a time period
|
||||||
|
// to know if it is part of a paste or just a return character.
|
||||||
|
const isReturnChar =
|
||||||
|
rawDataBuffer.length <= 2 && rawDataBuffer.includes(0x0d);
|
||||||
|
if (isReturnChar) {
|
||||||
|
rawFlushTimeout = setTimeout(flushRawBuffer, 100);
|
||||||
|
} else {
|
||||||
|
flushRawBuffer();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let rl: readline.Interface;
|
let rl: readline.Interface;
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ vi.mock('readline', () => {
|
|||||||
class MockStdin extends EventEmitter {
|
class MockStdin extends EventEmitter {
|
||||||
isTTY = true;
|
isTTY = true;
|
||||||
setRawMode = vi.fn();
|
setRawMode = vi.fn();
|
||||||
on = this.addListener;
|
override on = this.addListener;
|
||||||
removeListener = this.removeListener;
|
override removeListener = super.removeListener;
|
||||||
write = vi.fn();
|
write = vi.fn();
|
||||||
resume = vi.fn();
|
resume = vi.fn();
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ describe('useKeypress', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
stdin = new MockStdin();
|
stdin = new MockStdin();
|
||||||
(useStdin as vi.Mock).mockReturnValue({
|
(useStdin as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||||
stdin,
|
stdin,
|
||||||
setRawMode: mockSetRawMode,
|
setRawMode: mockSetRawMode,
|
||||||
});
|
});
|
||||||
@@ -278,23 +278,16 @@ describe('useKeypress', () => {
|
|||||||
// Unmounting should trigger the flush
|
// Unmounting should trigger the flush
|
||||||
unmount();
|
unmount();
|
||||||
|
|
||||||
if (isLegacy) {
|
// Both legacy and modern modes now flush partial paste content on unmount
|
||||||
// In legacy/passthrough mode, partial paste content may not be flushed on unmount
|
expect(onKeypress).toHaveBeenCalledTimes(1);
|
||||||
// due to the asynchronous nature of the raw data processing pipeline.
|
expect(onKeypress).toHaveBeenCalledWith({
|
||||||
// The current implementation doesn't reliably flush partial pastes in legacy mode
|
name: '',
|
||||||
expect(onKeypress).not.toHaveBeenCalled();
|
ctrl: false,
|
||||||
} else {
|
meta: false,
|
||||||
// In modern Node mode, partial paste content should be flushed on unmount
|
shift: false,
|
||||||
expect(onKeypress).toHaveBeenCalledTimes(1);
|
paste: true,
|
||||||
expect(onKeypress).toHaveBeenCalledWith({
|
sequence: pasteText,
|
||||||
name: '',
|
});
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
paste: true,
|
|
||||||
sequence: pasteText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user