Use raw writes to stdin where possible in tests (#11837)

This commit is contained in:
Tommaso Sciortino
2025-10-23 14:41:21 -07:00
committed by GitHub
parent 5e70a7dd46
commit aa6ae954ef
2 changed files with 122 additions and 523 deletions

View File

@@ -31,33 +31,29 @@ vi.mock('ink', async (importOriginal) => {
};
});
const PASTE_START = '\x1B[200~';
const PASTE_END = '\x1B[201~';
class MockStdin extends EventEmitter {
isTTY = true;
setRawMode = vi.fn();
override on = this.addListener;
override removeListener = super.removeListener;
write = vi.fn();
resume = vi.fn();
pause = vi.fn();
// Helper to simulate a keypress event
write(text: string) {
this.emit('data', Buffer.from(text));
}
/**
* Used to directly simulate keyPress events. Certain keypress events might
* be impossible to fire in certain versions of node. This allows us to
* sidestep readline entirely and just emit the keypress we want.
*/
pressKey(key: Partial<Key>) {
this.emit('keypress', null, key);
}
// Helper to simulate a kitty protocol sequence
sendKittySequence(sequence: string) {
this.emit('data', Buffer.from(sequence));
}
// Helper to simulate a paste event
sendPaste(text: string) {
const PASTE_MODE_PREFIX = `\x1b[200~`;
const PASTE_MODE_SUFFIX = `\x1b[201~`;
this.emit('data', Buffer.from(PASTE_MODE_PREFIX));
this.emit('data', Buffer.from(text));
this.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
}
}
describe('KeypressContext - Kitty Protocol', () => {
@@ -94,13 +90,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: true }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for regular enter: ESC[13u
act(() => {
stdin.sendKittySequence(`\x1b[13u`);
stdin.write(`\x1b[13u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -122,13 +116,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: true }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for numpad enter: ESC[57414u
act(() => {
stdin.sendKittySequence(`\x1b[57414u`);
stdin.write(`\x1b[57414u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -150,13 +142,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: true }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for numpad enter with Shift (modifier 2): ESC[57414;2u
act(() => {
stdin.sendKittySequence(`\x1b[57414;2u`);
stdin.write(`\x1b[57414;2u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -178,13 +168,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: true }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for numpad enter with Ctrl (modifier 5): ESC[57414;5u
act(() => {
stdin.sendKittySequence(`\x1b[57414;5u`);
stdin.write(`\x1b[57414;5u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -206,13 +194,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: true }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for numpad enter with Alt (modifier 3): ESC[57414;3u
act(() => {
stdin.sendKittySequence(`\x1b[57414;3u`);
stdin.write(`\x1b[57414;3u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -234,13 +220,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: false }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for numpad enter
act(() => {
stdin.sendKittySequence(`\x1b[57414u`);
stdin.write(`\x1b[57414u`);
});
// When kitty protocol is disabled, the sequence should be passed through
@@ -263,13 +247,11 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper({ children, kittyProtocolEnabled: true }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send kitty protocol sequence for escape: ESC[27u
act(() => {
stdin.sendKittySequence('\x1b[27u');
stdin.write('\x1b[27u');
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -288,7 +270,7 @@ describe('KeypressContext - Kitty Protocol', () => {
act(() => result.current.subscribe(keyHandler));
act(() => {
stdin.sendKittySequence(`\x1b[9u`);
stdin.write(`\x1b[9u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -307,7 +289,7 @@ describe('KeypressContext - Kitty Protocol', () => {
// Modifier 2 is Shift
act(() => {
stdin.sendKittySequence(`\x1b[9;2u`);
stdin.write(`\x1b[9;2u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -325,7 +307,7 @@ describe('KeypressContext - Kitty Protocol', () => {
act(() => result.current.subscribe(keyHandler));
act(() => {
stdin.sendKittySequence(`\x1b[127u`);
stdin.write(`\x1b[127u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -344,7 +326,7 @@ describe('KeypressContext - Kitty Protocol', () => {
// Modifier 3 is Alt/Option
act(() => {
stdin.sendKittySequence(`\x1b[127;3u`);
stdin.write(`\x1b[127;3u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -363,7 +345,7 @@ describe('KeypressContext - Kitty Protocol', () => {
// Modifier 5 is Ctrl
act(() => {
stdin.sendKittySequence(`\x1b[127;5u`);
stdin.write(`\x1b[127;5u`);
});
expect(keyHandler).toHaveBeenCalledWith(
@@ -385,13 +367,13 @@ describe('KeypressContext - Kitty Protocol', () => {
wrapper,
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Simulate a bracketed paste event
act(() => {
stdin.sendPaste(pastedText);
stdin.write(PASTE_START);
stdin.write(pastedText);
stdin.write(PASTE_END);
});
await waitFor(() => {
@@ -437,13 +419,11 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send a kitty sequence
act(() => {
stdin.sendKittySequence('\x1b[27u');
stdin.write('\x1b[27u');
});
expect(keyHandler).toHaveBeenCalled();
@@ -466,14 +446,10 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send a complete kitty sequence for escape
act(() => {
stdin.sendKittySequence('\x1b[27u');
});
act(() => stdin.write('\x1b[27u'));
expect(consoleLogSpy).toHaveBeenCalledWith(
'[DEBUG] Kitty buffer accumulating:',
@@ -502,15 +478,11 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send a long sequence starting with a valid kitty prefix to trigger overflow
const longSequence = '\x1b[1;' + '1'.repeat(100);
act(() => {
stdin.sendKittySequence(longSequence);
});
act(() => stdin.write(longSequence));
expect(consoleLogSpy).toHaveBeenCalledWith(
'[DEBUG] Kitty buffer overflow, clearing:',
@@ -532,31 +504,13 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: '\x1b[1',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1' }));
// Send Ctrl+C
act(() => {
stdin.pressKey({
name: 'c',
ctrl: true,
meta: false,
shift: false,
sequence: '\x03',
});
});
act(() => stdin.write('\x03'));
expect(consoleLogSpy).toHaveBeenCalledWith(
'[DEBUG] Kitty buffer cleared on Ctrl+C:',
@@ -586,21 +540,11 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
const sequence = '\x1b[12';
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence,
});
});
act(() => stdin.pressKey({ sequence }));
// Verify debug logging for accumulation
expect(consoleLogSpy).toHaveBeenCalledWith(
@@ -629,6 +573,9 @@ describe('KeypressContext - Kitty Protocol', () => {
{ sequence: `\x1b[1~`, expected: { name: 'home' } },
{ sequence: `\x1b[4~`, expected: { name: 'end' } },
{ sequence: `\x1b[2~`, expected: { name: 'insert' } },
// Reverse tabs
{ sequence: `\x1b[Z`, expected: { name: 'tab', shift: true } },
{ sequence: `\x1b[1;2Z`, expected: { name: 'tab', shift: true } },
// Legacy Arrows
{
sequence: `\x1b[A`,
@@ -662,7 +609,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => result.current.subscribe(keyHandler));
act(() => stdin.sendKittySequence(sequence));
act(() => stdin.write(sequence));
expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining(expected),
@@ -671,33 +618,14 @@ describe('KeypressContext - Kitty Protocol', () => {
);
});
describe('Shift+Tab forms', () => {
it.each([
{ sequence: `\x1b[Z`, description: 'legacy reverse Tab' },
{ sequence: `\x1b[1;2Z`, description: 'parameterized reverse Tab' },
])(
'should recognize $description "$sequence" as Shift+Tab',
({ sequence }) => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => result.current.subscribe(keyHandler));
act(() => stdin.sendKittySequence(sequence));
expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining({ name: 'tab', shift: true }),
);
},
);
});
describe('Double-tap and batching', () => {
it('should emit two delete events for double-tap CSI[3~', async () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => result.current.subscribe(keyHandler));
act(() => stdin.sendKittySequence(`\x1b[3~`));
act(() => stdin.sendKittySequence(`\x1b[3~`));
act(() => stdin.write(`\x1b[3~`));
act(() => stdin.write(`\x1b[3~`));
expect(keyHandler).toHaveBeenNthCalledWith(
1,
@@ -714,7 +642,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => result.current.subscribe(keyHandler));
act(() => stdin.sendKittySequence(`\x1b[3~\x1b[5~`));
act(() => stdin.write(`\x1b[3~\x1b[5~`));
expect(keyHandler).toHaveBeenCalledWith(
expect.objectContaining({ name: 'delete' }),
@@ -732,15 +660,9 @@ describe('KeypressContext - Kitty Protocol', () => {
// Incomplete ESC sequence then a complete Delete
act(() => {
// Provide an incomplete ESC sequence chunk with a real ESC character
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
sequence: '\x1b[1;',
});
stdin.write('\x1b[1;');
});
act(() => stdin.sendKittySequence(`\x1b[3~`));
act(() => stdin.write(`\x1b[3~`));
expect(keyHandler).toHaveBeenCalledTimes(1);
expect(keyHandler).toHaveBeenCalledWith(
@@ -786,20 +708,9 @@ describe('Drag and Drop Handling', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: SINGLE_QUOTE,
});
});
act(() => stdin.write(SINGLE_QUOTE));
expect(keyHandler).not.toHaveBeenCalled();
});
@@ -809,20 +720,9 @@ describe('Drag and Drop Handling', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: DOUBLE_QUOTE,
});
});
act(() => stdin.write(DOUBLE_QUOTE));
expect(keyHandler).not.toHaveBeenCalled();
});
@@ -834,33 +734,13 @@ describe('Drag and Drop Handling', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Start by single quote
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: SINGLE_QUOTE,
});
});
act(() => stdin.write(SINGLE_QUOTE));
// Send single character
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: 'a',
});
});
act(() => stdin.write('a'));
// Character should not be immediately broadcast
expect(keyHandler).not.toHaveBeenCalled();
@@ -885,66 +765,16 @@ describe('Drag and Drop Handling', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Start by single quote
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: SINGLE_QUOTE,
});
});
act(() => stdin.write(SINGLE_QUOTE));
// Send multiple characters
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: 'p',
});
});
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: 'a',
});
});
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: 't',
});
});
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: 'h',
});
});
act(() => stdin.write('p'));
act(() => stdin.write('a'));
act(() => stdin.write('t'));
act(() => stdin.write('h'));
// Characters should not be immediately broadcast
expect(keyHandler).not.toHaveBeenCalled();
@@ -1101,7 +931,7 @@ describe('Kitty Sequence Parsing', () => {
act(() => result.current.subscribe(keyHandler));
if (kittySequence) {
act(() => stdin.sendKittySequence(kittySequence));
act(() => stdin.write(kittySequence));
} else if (input) {
act(() => stdin.pressKey(input));
}
@@ -1126,16 +956,7 @@ describe('Kitty Sequence Parsing', () => {
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => result.current.subscribe(keyHandler));
act(() =>
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\\',
}),
);
act(() => stdin.write('\\'));
// Advance timers to trigger the backslash timeout
act(() => {
@@ -1155,29 +976,16 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[1;',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1;' }));
// Should not broadcast immediately
expect(keyHandler).not.toHaveBeenCalled();
// Advance time just before timeout
act(() => {
vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5);
});
act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5));
// Still shouldn't broadcast
expect(keyHandler).not.toHaveBeenCalled();
@@ -1201,22 +1009,11 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send a CSI sequence that doesn't match kitty patterns
// ESC[m is SGR reset, not a kitty sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[m',
});
});
act(() => stdin.write('\x1b[m'));
// Should broadcast immediately as it's not a valid kitty pattern
expect(keyHandler).toHaveBeenCalledWith(
@@ -1232,21 +1029,10 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send complete kitty sequence for Ctrl+A
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[97;5u',
});
});
act(() => stdin.write('\x1b[97;5u'));
// Should parse and broadcast immediately
expect(keyHandler).toHaveBeenCalledWith(
@@ -1262,21 +1048,10 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send multiple kitty sequences at once
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[97;5u\x1b[98;5u', // Ctrl+a followed by Ctrl+b
});
});
// Send Ctrl+a followed by Ctrl+b
act(() => stdin.write('\x1b[97;5u\x1b[98;5u'));
// Should parse both sequences
expect(keyHandler).toHaveBeenCalledTimes(2);
@@ -1302,38 +1077,16 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[1;',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1;' }));
// Press Ctrl+C
act(() => {
stdin.pressKey({
name: 'c',
ctrl: true,
meta: false,
shift: false,
paste: false,
sequence: '\x03',
});
});
act(() => stdin.write('\x03'));
// Advance past timeout
act(() => {
vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10);
});
act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
// Should only have received Ctrl+C, not the incomplete sequence
expect(keyHandler).toHaveBeenCalledTimes(1);
@@ -1349,21 +1102,11 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send valid kitty sequence followed by invalid CSI
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[13u\x1b[!', // Valid enter, then invalid sequence
});
});
// Valid enter, then invalid sequence
act(() => stdin.write('\x1b[13u\x1b[!'));
// Should parse valid sequence and flush invalid immediately
expect(keyHandler).toHaveBeenCalledTimes(2);
@@ -1390,21 +1133,10 @@ describe('Kitty Sequence Parsing', () => {
wrapper({ children, kittyProtocolEnabled: false }),
});
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send what would be a kitty sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[13u',
});
});
act(() => stdin.write('\x1b[13u'));
// Should pass through without parsing
expect(keyHandler).toHaveBeenCalledWith(
@@ -1454,58 +1186,25 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Start incomplete sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '\x1b[1',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1' }));
// Advance time partway
act(() => {
vi.advanceTimersByTime(30);
});
act(() => vi.advanceTimersByTime(30));
// Add more to sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: '3',
});
});
act(() => stdin.write('3'));
// Advance time from the first timeout point
act(() => {
vi.advanceTimersByTime(25);
});
act(() => vi.advanceTimersByTime(25));
// Should not have timed out yet (timeout restarted)
expect(keyHandler).not.toHaveBeenCalled();
// Complete the sequence
act(() => {
stdin.pressKey({
name: undefined,
ctrl: false,
meta: false,
shift: false,
paste: false,
sequence: 'u',
});
});
act(() => stdin.write('u'));
// Should now parse as complete enter key
expect(keyHandler).toHaveBeenCalledWith(
@@ -1520,27 +1219,16 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
act(() => {
stdin.pressKey({
sequence: '\x1b[1;',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1;' }));
// Incomplete sequence should be buffered, not broadcast
expect(keyHandler).not.toHaveBeenCalled();
// Send FOCUS_IN event
const FOCUS_IN = '\x1b[I';
act(() => {
stdin.pressKey({
sequence: FOCUS_IN,
});
});
act(() => stdin.write('\x1b[I'));
// The buffered sequence should be flushed
expect(keyHandler).toHaveBeenCalledTimes(1);
@@ -1557,27 +1245,16 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
act(() => {
stdin.pressKey({
sequence: '\x1b[1;',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1;' }));
// Incomplete sequence should be buffered, not broadcast
expect(keyHandler).not.toHaveBeenCalled();
// Send FOCUS_OUT event
const FOCUS_OUT = '\x1b[O';
act(() => {
stdin.pressKey({
sequence: FOCUS_OUT,
});
});
act(() => stdin.write('\x1b[O'));
// The buffered sequence should be flushed
expect(keyHandler).toHaveBeenCalledTimes(1);
@@ -1595,25 +1272,16 @@ describe('Kitty Sequence Parsing', () => {
const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { wrapper });
act(() => {
result.current.subscribe(keyHandler);
});
act(() => result.current.subscribe(keyHandler));
// Send incomplete kitty sequence
act(() => {
stdin.pressKey({
sequence: '\x1b[1;',
});
});
act(() => stdin.pressKey({ sequence: '\x1b[1;' }));
// Incomplete sequence should be buffered, not broadcast
expect(keyHandler).not.toHaveBeenCalled();
// Send paste start sequence
const PASTE_MODE_PREFIX = `\x1b[200~`;
act(() => {
stdin.emit('data', Buffer.from(PASTE_MODE_PREFIX));
});
act(() => stdin.write(`\x1b[200~`));
// The buffered sequence should be flushed
expect(keyHandler).toHaveBeenCalledTimes(1);
@@ -1629,13 +1297,11 @@ describe('Kitty Sequence Parsing', () => {
const pastedText = 'hello';
const PASTE_MODE_SUFFIX = `\x1b[201~`;
act(() => {
stdin.emit('data', Buffer.from(pastedText));
stdin.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
stdin.write(pastedText);
stdin.write(PASTE_MODE_SUFFIX);
});
act(() => {
vi.runAllTimers();
});
act(() => vi.runAllTimers());
// The paste event should be broadcast
expect(keyHandler).toHaveBeenCalledTimes(2);