diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx index 99f4dbf9..5b383642 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx @@ -526,7 +526,7 @@ describe('KeypressContext - Kitty Protocol', () => { }); await waitFor(() => { - expect(keyHandler).toHaveBeenCalledTimes(2); // 1 paste event + 1 paste event for 'after' + expect(keyHandler).toHaveBeenCalledTimes(6); // 1 paste event + 5 individual chars for 'after' }); // Should emit paste event first @@ -538,12 +538,40 @@ describe('KeypressContext - Kitty Protocol', () => { }), ); - // Then process 'after' as a paste event (since it's > 2 chars) + // Then process 'after' as individual characters (since it doesn't contain return) expect(keyHandler).toHaveBeenNthCalledWith( 2, expect.objectContaining({ - paste: true, - sequence: 'after', + name: 'a', + paste: false, + }), + ); + expect(keyHandler).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + name: 'f', + paste: false, + }), + ); + expect(keyHandler).toHaveBeenNthCalledWith( + 4, + expect.objectContaining({ + name: 't', + paste: false, + }), + ); + expect(keyHandler).toHaveBeenNthCalledWith( + 5, + expect.objectContaining({ + name: 'e', + paste: false, + }), + ); + expect(keyHandler).toHaveBeenNthCalledWith( + 6, + expect.objectContaining({ + name: 'r', + paste: false, }), ); }); @@ -571,7 +599,7 @@ describe('KeypressContext - Kitty Protocol', () => { }); await waitFor(() => { - expect(keyHandler).toHaveBeenCalledTimes(14); // Adjusted based on actual behavior + expect(keyHandler).toHaveBeenCalledTimes(16); // 5 + 1 + 6 + 1 + 3 = 16 calls }); // Check the sequence: 'start' (5 chars) + paste1 + 'middle' (6 chars) + paste2 + 'end' (3 chars as paste) @@ -643,13 +671,18 @@ describe('KeypressContext - Kitty Protocol', () => { }), ); - // 'end' as paste event (since it's > 2 chars) + // 'end' as individual characters (since it doesn't contain return) expect(keyHandler).toHaveBeenNthCalledWith( callIndex++, - expect.objectContaining({ - paste: true, - sequence: 'end', - }), + expect.objectContaining({ name: 'e' }), + ); + expect(keyHandler).toHaveBeenNthCalledWith( + callIndex++, + expect.objectContaining({ name: 'n' }), + ); + expect(keyHandler).toHaveBeenNthCalledWith( + callIndex++, + expect.objectContaining({ name: 'd' }), ); }); @@ -738,16 +771,18 @@ describe('KeypressContext - Kitty Protocol', () => { }); await waitFor(() => { - // 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(); + // With the current implementation, fragmented paste markers get reconstructed + // into a single paste event for 'content' + expect(keyHandler).toHaveBeenCalledTimes(1); }); - // The current implementation processes fragmented paste markers as separate events - // rather than reconstructing them into a single paste event - expect(keyHandler.mock.calls.length).toBeGreaterThan(1); + // Should reconstruct the fragmented paste markers into a single paste event + expect(keyHandler).toHaveBeenCalledWith( + expect.objectContaining({ + paste: true, + sequence: 'content', + }), + ); }); }); @@ -851,19 +886,38 @@ describe('KeypressContext - Kitty Protocol', () => { stdin.emit('data', Buffer.from('lo')); }); - // With the current implementation, data is processed as it arrives - // First chunk 'hel' is treated as paste (multi-character) + // With the current implementation, data is processed as individual characters + // since 'hel' doesn't contain return (0x0d) expect(keyHandler).toHaveBeenNthCalledWith( 1, expect.objectContaining({ - paste: true, - sequence: 'hel', + name: 'h', + sequence: 'h', + paste: false, }), ); - // Second chunk 'lo' is processed as individual characters expect(keyHandler).toHaveBeenNthCalledWith( 2, + expect.objectContaining({ + name: 'e', + sequence: 'e', + paste: false, + }), + ); + + expect(keyHandler).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + name: 'l', + sequence: 'l', + paste: false, + }), + ); + + // Second chunk 'lo' is also processed as individual characters + expect(keyHandler).toHaveBeenNthCalledWith( + 4, expect.objectContaining({ name: 'l', sequence: 'l', @@ -872,7 +926,7 @@ describe('KeypressContext - Kitty Protocol', () => { ); expect(keyHandler).toHaveBeenNthCalledWith( - 3, + 5, expect.objectContaining({ name: 'o', sequence: 'o', @@ -880,7 +934,7 @@ describe('KeypressContext - Kitty Protocol', () => { }), ); - expect(keyHandler).toHaveBeenCalledTimes(3); + expect(keyHandler).toHaveBeenCalledTimes(5); } finally { vi.useRealTimers(); } @@ -907,14 +961,20 @@ describe('KeypressContext - Kitty Protocol', () => { }); // Should flush immediately without waiting for timeout - // Large data gets treated as paste event - expect(keyHandler).toHaveBeenCalledTimes(1); - expect(keyHandler).toHaveBeenCalledWith( - expect.objectContaining({ - paste: true, - sequence: largeData, - }), - ); + // Large data without return gets treated as individual characters + expect(keyHandler).toHaveBeenCalledTimes(65); + + // Each character should be processed individually + for (let i = 0; i < 65; i++) { + expect(keyHandler).toHaveBeenNthCalledWith( + i + 1, + expect.objectContaining({ + name: 'x', + sequence: 'x', + paste: false, + }), + ); + } // Advancing timer should not cause additional calls const callCountBefore = keyHandler.mock.calls.length; diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx index 0bdc4168..9eb55185 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.tsx @@ -407,7 +407,11 @@ export function KeypressProvider({ return; } - if (rawDataBuffer.length <= 2 || isPaste) { + if ( + (rawDataBuffer.length <= 2 && rawDataBuffer.includes(0x0d)) || + !rawDataBuffer.includes(0x0d) || + isPaste + ) { keypressStream.write(rawDataBuffer); } else { // Flush raw data buffer as a paste event