diff options
| author | Jacob Richman <[email protected]> | 2025-06-16 06:25:11 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-06-16 06:25:11 +0000 |
| commit | 5d4f4f421ce284b19cab0edb4e989f20e9cf08c9 (patch) | |
| tree | 3e3d5ab5bfd73ba132bece2301be9dfc1bf7b954 /packages/cli/src/ui/components/shared/text-buffer.test.ts | |
| parent | 742caa5dd887f3780affdec31b1dd592c3baf2b0 (diff) | |
feat: text-buffer: input sanitization and delete character handling. (#1031)
Diffstat (limited to 'packages/cli/src/ui/components/shared/text-buffer.test.ts')
| -rw-r--r-- | packages/cli/src/ui/components/shared/text-buffer.test.ts | 119 |
1 files changed, 116 insertions, 3 deletions
diff --git a/packages/cli/src/ui/components/shared/text-buffer.test.ts b/packages/cli/src/ui/components/shared/text-buffer.test.ts index 56f8e240..c5295fc1 100644 --- a/packages/cli/src/ui/components/shared/text-buffer.test.ts +++ b/packages/cli/src/ui/components/shared/text-buffer.test.ts @@ -585,6 +585,68 @@ describe('useTextBuffer', () => { expect(getBufferState(result).text).toBe(''); }); + it('should handle multiple delete characters in one input', () => { + const { result } = renderHook(() => + useTextBuffer({ + initialText: 'abcde', + viewport, + isValidPath: () => false, + }), + ); + act(() => result.current.move('end')); // cursor at the end + expect(getBufferState(result).cursor).toEqual([0, 5]); + + act(() => { + result.current.applyOperations([ + { type: 'backspace' }, + { type: 'backspace' }, + { type: 'backspace' }, + ]); + }); + expect(getBufferState(result).text).toBe('ab'); + expect(getBufferState(result).cursor).toEqual([0, 2]); + }); + + it('should handle inserts that contain delete characters ', () => { + const { result } = renderHook(() => + useTextBuffer({ + initialText: 'abcde', + viewport, + isValidPath: () => false, + }), + ); + act(() => result.current.move('end')); // cursor at the end + expect(getBufferState(result).cursor).toEqual([0, 5]); + + act(() => { + result.current.applyOperations([ + { type: 'insert', payload: '\x7f\x7f\x7f' }, + ]); + }); + expect(getBufferState(result).text).toBe('ab'); + expect(getBufferState(result).cursor).toEqual([0, 2]); + }); + + it('should handle inserts with a mix of regular and delete characters ', () => { + const { result } = renderHook(() => + useTextBuffer({ + initialText: 'abcde', + viewport, + isValidPath: () => false, + }), + ); + act(() => result.current.move('end')); // cursor at the end + expect(getBufferState(result).cursor).toEqual([0, 5]); + + act(() => { + result.current.applyOperations([ + { type: 'insert', payload: '\x7fI\x7f\x7fNEW' }, + ]); + }); + expect(getBufferState(result).text).toBe('abcNEW'); + expect(getBufferState(result).cursor).toEqual([0, 6]); + }); + it('should handle arrow keys for movement', () => { const { result } = renderHook(() => useTextBuffer({ @@ -632,9 +694,13 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots ); // Simulate pasting the long text multiple times - act(() => result.current.insertStr(longText)); - act(() => result.current.insertStr(longText)); - act(() => result.current.insertStr(longText)); + act(() => { + result.current.applyOperations([ + { type: 'insert', payload: longText }, + { type: 'insert', payload: longText }, + { type: 'insert', payload: longText }, + ]); + }); const state = getBufferState(result); // Check that the text is the result of three concatenations. @@ -792,6 +858,53 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots expect(state.cursor).toEqual([0, 3]); // After 'X' }); }); + + describe('Input Sanitization', () => { + it('should strip ANSI escape codes from input', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + const textWithAnsi = '\x1B[31mHello\x1B[0m'; + act(() => result.current.handleInput(textWithAnsi, {})); + expect(getBufferState(result).text).toBe('Hello'); + }); + + it('should strip control characters from input', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + const textWithControlChars = 'H\x07e\x08l\x0Bl\x0Co'; // BELL, BACKSPACE, VT, FF + act(() => result.current.handleInput(textWithControlChars, {})); + expect(getBufferState(result).text).toBe('Hello'); + }); + + it('should strip mixed ANSI and control characters from input', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + const textWithMixed = '\u001B[4mH\u001B[0mello'; + act(() => result.current.handleInput(textWithMixed, {})); + expect(getBufferState(result).text).toBe('Hello'); + }); + + it('should not strip standard characters or newlines', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + const validText = 'Hello World\nThis is a test.'; + act(() => result.current.handleInput(validText, {})); + expect(getBufferState(result).text).toBe(validText); + }); + + it('should sanitize pasted text via handleInput', () => { + const { result } = renderHook(() => + useTextBuffer({ viewport, isValidPath: () => false }), + ); + const pastedText = '\u001B[4mPasted\u001B[4m Text'; + act(() => result.current.handleInput(pastedText, {})); + expect(getBufferState(result).text).toBe('Pasted Text'); + }); + }); }); describe('offsetToLogicalPos', () => { |
