feat: text-buffer: input sanitization and delete character handling. (#1031)

This commit is contained in:
Jacob Richman
2025-06-16 06:25:11 +00:00
committed by GitHub
parent 742caa5dd8
commit 5d4f4f421c
2 changed files with 257 additions and 74 deletions

View File

@@ -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', () => {