mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 02:24:09 -07:00
refactor: push isValidPath() into parsePastedPaths() (#18664)
This commit is contained in:
committed by
GitHub
parent
9e41b2cd89
commit
1b98c1f806
@@ -7,10 +7,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { act } from 'react';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
import {
|
||||
renderHook,
|
||||
renderHookWithProviders,
|
||||
} from '../../../test-utils/render.js';
|
||||
|
||||
import type {
|
||||
Viewport,
|
||||
TextBuffer,
|
||||
@@ -738,9 +742,7 @@ describe('useTextBuffer', () => {
|
||||
|
||||
describe('Initialization', () => {
|
||||
it('should initialize with empty text and cursor at (0,0) by default', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const state = getBufferState(result);
|
||||
expect(state.text).toBe('');
|
||||
expect(state.lines).toEqual(['']);
|
||||
@@ -756,7 +758,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'hello',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -774,7 +775,6 @@ describe('useTextBuffer', () => {
|
||||
initialText: 'hello\nworld',
|
||||
initialCursorOffset: 7, // Should be at 'o' in 'world'
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -793,7 +793,6 @@ describe('useTextBuffer', () => {
|
||||
initialText: 'The quick brown fox jumps over the lazy dog.',
|
||||
initialCursorOffset: 2, // After '好'
|
||||
viewport: { width: 15, height: 4 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -810,7 +809,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'The quick brown fox jumps over the lazy dog.',
|
||||
viewport: { width: 15, height: 4 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -830,7 +828,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: '123456789012345ABCDEFG', // 4 chars, 12 bytes
|
||||
viewport: { width: 15, height: 2 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -846,7 +843,6 @@ describe('useTextBuffer', () => {
|
||||
initialText: '你好世界', // 4 chars, 12 bytes
|
||||
initialCursorOffset: 2, // After '好'
|
||||
viewport: { width: 5, height: 2 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -861,9 +857,7 @@ describe('useTextBuffer', () => {
|
||||
|
||||
describe('Basic Editing', () => {
|
||||
it('insert: should insert a character and update cursor', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => result.current.insert('a'));
|
||||
let state = getBufferState(result);
|
||||
expect(state.text).toBe('a');
|
||||
@@ -882,7 +876,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'abc',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('right'));
|
||||
@@ -893,9 +886,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('insert: should use placeholder for large text paste', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const largeText = '1\n2\n3\n4\n5\n6';
|
||||
act(() => result.current.insert(largeText, { paste: true }));
|
||||
const state = getBufferState(result);
|
||||
@@ -906,9 +897,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('insert: should NOT use placeholder for large text if NOT a paste', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const largeText = '1\n2\n3\n4\n5\n6';
|
||||
act(() => result.current.insert(largeText, { paste: false }));
|
||||
const state = getBufferState(result);
|
||||
@@ -916,9 +905,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('insert: should clean up pastedContent when placeholder is deleted', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const largeText = '1\n2\n3\n4\n5\n6';
|
||||
act(() => result.current.insert(largeText, { paste: true }));
|
||||
expect(result.current.pastedContent['[Pasted Text: 6 lines]']).toBe(
|
||||
@@ -931,9 +918,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('insert: should clean up pastedContent when placeholder is removed via atomic backspace', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const largeText = '1\n2\n3\n4\n5\n6';
|
||||
act(() => result.current.insert(largeText, { paste: true }));
|
||||
expect(result.current.pastedContent['[Pasted Text: 6 lines]']).toBe(
|
||||
@@ -955,7 +940,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'ab',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor at [0,2]
|
||||
@@ -974,7 +958,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'a\nb',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => {
|
||||
@@ -1002,7 +985,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'a\nb',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
// cursor at [0,0]
|
||||
@@ -1022,36 +1004,49 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
describe('Drag and Drop File Paths', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-cli-test-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('should prepend @ to a valid file path on insert', () => {
|
||||
const filePath = path.join(tempDir, 'file.txt');
|
||||
fs.writeFileSync(filePath, '');
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => true }),
|
||||
useTextBuffer({ viewport, escapePastedPaths: true }),
|
||||
);
|
||||
const filePath = '/path/to/a/valid/file.txt';
|
||||
act(() => result.current.insert(filePath, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(`@${filePath} `);
|
||||
});
|
||||
|
||||
it('should not prepend @ to an invalid file path on insert', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const notAPath = 'this is just some long text';
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const notAPath = path.join(tempDir, 'non_existent.txt');
|
||||
act(() => result.current.insert(notAPath, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(notAPath);
|
||||
});
|
||||
|
||||
it('should handle quoted paths', () => {
|
||||
const filePath = path.join(tempDir, 'file.txt');
|
||||
fs.writeFileSync(filePath, '');
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => true }),
|
||||
useTextBuffer({ viewport, escapePastedPaths: true }),
|
||||
);
|
||||
const filePath = "'/path/to/a/valid/file.txt'";
|
||||
act(() => result.current.insert(filePath, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(`@/path/to/a/valid/file.txt `);
|
||||
const quotedPath = `'${filePath}'`;
|
||||
act(() => result.current.insert(quotedPath, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(`@${filePath} `);
|
||||
});
|
||||
|
||||
it('should not prepend @ to short text that is not a path', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => true }),
|
||||
useTextBuffer({ viewport, escapePastedPaths: true }),
|
||||
);
|
||||
const shortText = 'ab';
|
||||
act(() => result.current.insert(shortText, { paste: true }));
|
||||
@@ -1059,43 +1054,51 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should prepend @ to multiple valid file paths on insert', () => {
|
||||
// Use Set to model reality: individual paths exist, combined string doesn't
|
||||
const validPaths = new Set(['/path/to/file1.txt', '/path/to/file2.txt']);
|
||||
const file1 = path.join(tempDir, 'file1.txt');
|
||||
const file2 = path.join(tempDir, 'file2.txt');
|
||||
fs.writeFileSync(file1, '');
|
||||
fs.writeFileSync(file2, '');
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: (p) => validPaths.has(p) }),
|
||||
useTextBuffer({ viewport, escapePastedPaths: true }),
|
||||
);
|
||||
const filePaths = '/path/to/file1.txt /path/to/file2.txt';
|
||||
const filePaths = `${file1} ${file2}`;
|
||||
act(() => result.current.insert(filePaths, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(
|
||||
'@/path/to/file1.txt @/path/to/file2.txt ',
|
||||
);
|
||||
expect(getBufferState(result).text).toBe(`@${file1} @${file2} `);
|
||||
});
|
||||
|
||||
it('should handle multiple paths with escaped spaces', () => {
|
||||
// Use Set to model reality: individual paths exist, combined string doesn't
|
||||
const validPaths = new Set(['/path/to/my file.txt', '/other/path.txt']);
|
||||
const file1 = path.join(tempDir, 'my file.txt');
|
||||
const file2 = path.join(tempDir, 'other.txt');
|
||||
fs.writeFileSync(file1, '');
|
||||
fs.writeFileSync(file2, '');
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: (p) => validPaths.has(p) }),
|
||||
useTextBuffer({ viewport, escapePastedPaths: true }),
|
||||
);
|
||||
const filePaths = '/path/to/my\\ file.txt /other/path.txt';
|
||||
// Construct escaped path string: "/path/to/my\ file.txt /path/to/other.txt"
|
||||
const escapedFile1 = file1.replace(/ /g, '\\ ');
|
||||
const filePaths = `${escapedFile1} ${file2}`;
|
||||
|
||||
act(() => result.current.insert(filePaths, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(
|
||||
'@/path/to/my\\ file.txt @/other/path.txt ',
|
||||
);
|
||||
expect(getBufferState(result).text).toBe(`@${escapedFile1} @${file2} `);
|
||||
});
|
||||
|
||||
it('should only prepend @ to valid paths in multi-path paste', () => {
|
||||
const validFile = path.join(tempDir, 'valid.txt');
|
||||
const invalidFile = path.join(tempDir, 'invalid.jpg');
|
||||
fs.writeFileSync(validFile, '');
|
||||
// Do not create invalidFile
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: (p) => p.endsWith('.txt'),
|
||||
escapePastedPaths: true,
|
||||
}),
|
||||
);
|
||||
const filePaths = '/valid/file.txt /invalid/file.jpg';
|
||||
const filePaths = `${validFile} ${invalidFile}`;
|
||||
act(() => result.current.insert(filePaths, { paste: true }));
|
||||
expect(getBufferState(result).text).toBe(
|
||||
'@/valid/file.txt /invalid/file.jpg ',
|
||||
);
|
||||
expect(getBufferState(result).text).toBe(`@${validFile} ${invalidFile} `);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1104,7 +1107,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
shellModeActive: true,
|
||||
}),
|
||||
);
|
||||
@@ -1117,7 +1120,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
shellModeActive: true,
|
||||
}),
|
||||
);
|
||||
@@ -1130,7 +1133,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
shellModeActive: true,
|
||||
}),
|
||||
);
|
||||
@@ -1143,7 +1146,7 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
shellModeActive: true,
|
||||
}),
|
||||
);
|
||||
@@ -1165,7 +1168,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'long line1next line2', // Corrected: was 'long line1next line2'
|
||||
viewport: { width: 5, height: 4 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
// Initial cursor [0,0] logical, visual [0,0] ("l" of "long ")
|
||||
@@ -1192,7 +1194,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: text,
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
expect(result.current.allVisualLines).toEqual(['abcde', 'xy', '12345']);
|
||||
@@ -1234,7 +1235,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText,
|
||||
viewport: { width: 5, height: 5 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
expect(result.current.allVisualLines).toEqual([
|
||||
@@ -1263,7 +1263,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'This is a very long line of text.', // 33 chars
|
||||
viewport: { width: 10, height: 5 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
const state = getBufferState(result);
|
||||
@@ -1284,7 +1283,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'l1\nl2\nl3\nl4\nl5',
|
||||
viewport: { width: 5, height: 3 }, // Can show 3 visual lines
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
// Initial: l1, l2, l3 visible. visualScrollRow = 0. visualCursor = [0,0]
|
||||
@@ -1330,9 +1328,7 @@ describe('useTextBuffer', () => {
|
||||
|
||||
describe('Undo/Redo', () => {
|
||||
it('should undo and redo an insert operation', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => result.current.insert('a'));
|
||||
expect(getBufferState(result).text).toBe('a');
|
||||
|
||||
@@ -1350,7 +1346,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'test',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end'));
|
||||
@@ -1369,9 +1364,7 @@ describe('useTextBuffer', () => {
|
||||
|
||||
describe('Unicode Handling', () => {
|
||||
it('insert: should correctly handle multi-byte unicode characters', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => result.current.insert('你好'));
|
||||
const state = getBufferState(result);
|
||||
expect(state.text).toBe('你好');
|
||||
@@ -1384,7 +1377,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: '你好',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor at [0,2]
|
||||
@@ -1404,7 +1396,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: '🐶🐱',
|
||||
viewport: { width: 5, height: 1 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
// Initial: visualCursor [0,0]
|
||||
@@ -1432,7 +1423,6 @@ describe('useTextBuffer', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport: { width: 10, height: 5 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1484,7 +1474,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: '你好', // 2 chars, width 4
|
||||
viewport: { width: 10, height: 1 },
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1510,9 +1499,7 @@ describe('useTextBuffer', () => {
|
||||
|
||||
describe('handleInput', () => {
|
||||
it('should insert printable characters', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'h',
|
||||
@@ -1539,9 +1526,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should handle "Enter" key as newline', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
@@ -1557,9 +1542,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should handle Ctrl+J as newline', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'j',
|
||||
@@ -1575,9 +1558,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should do nothing for a tab key press', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'tab',
|
||||
@@ -1593,9 +1574,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should do nothing for a shift tab key press', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'tab',
|
||||
@@ -1615,7 +1594,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'hello',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
expect(getBufferState(result).text).toBe('hello');
|
||||
@@ -1636,9 +1614,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should NOT handle CLEAR_INPUT if buffer is empty', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
let handled = true;
|
||||
act(() => {
|
||||
handled = result.current.handleInput({
|
||||
@@ -1659,7 +1635,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'a',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end'));
|
||||
@@ -1682,7 +1657,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'abcde',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor at the end
|
||||
@@ -1726,7 +1700,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'abcde',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor at the end
|
||||
@@ -1744,7 +1717,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'abcde',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor at the end
|
||||
@@ -1762,7 +1734,6 @@ describe('useTextBuffer', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'ab',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.move('end')); // cursor [0,2]
|
||||
@@ -1793,9 +1764,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should strip ANSI escape codes when pasting text', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const textWithAnsi = '\x1B[31mHello\x1B[0m \x1B[32mWorld\x1B[0m';
|
||||
// Simulate pasting by calling handleInput with a string longer than 1 char
|
||||
act(() => {
|
||||
@@ -1813,9 +1782,7 @@ describe('useTextBuffer', () => {
|
||||
});
|
||||
|
||||
it('should handle VSCode terminal Shift+Enter as newline', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
name: 'return',
|
||||
@@ -1839,9 +1806,7 @@ It is a long established fact that a reader will be distracted by the readable c
|
||||
Where does it come from?
|
||||
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lore
|
||||
`;
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
|
||||
// Simulate pasting the long text multiple times
|
||||
act(() => {
|
||||
@@ -1887,7 +1852,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: '@pac',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 1, 0, 4, 'packages'));
|
||||
@@ -1901,7 +1865,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'hello\nworld\nagain',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 2, 1, 3, ' new ')); // replace 'llo\nwor' with ' new '
|
||||
@@ -1915,7 +1878,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'hello world',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 5, 0, 11, '')); // delete ' world'
|
||||
@@ -1929,7 +1891,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'world',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 0, 0, 0, 'hello '));
|
||||
@@ -1943,7 +1904,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'hello',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 5, 0, 5, ' world'));
|
||||
@@ -1957,7 +1917,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'old text',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 0, 0, 8, 'new text'));
|
||||
@@ -1971,7 +1930,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'hello *** world',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 6, 0, 9, '你好'));
|
||||
@@ -1985,7 +1943,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'test',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => {
|
||||
@@ -2005,7 +1962,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'first\nsecond\nthird',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
act(() => result.current.replaceRange(0, 2, 2, 3, 'X')); // Replace 'rst\nsecond\nthi'
|
||||
@@ -2019,7 +1975,6 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'one two three',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
// Replace "two" with "new\nline"
|
||||
@@ -2063,9 +2018,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
desc: 'pasted text with ANSI',
|
||||
},
|
||||
])('should strip $desc from input', ({ input, expected }) => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
act(() => {
|
||||
result.current.handleInput(createInput(input));
|
||||
});
|
||||
@@ -2073,9 +2026,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
});
|
||||
|
||||
it('should not strip standard characters or newlines', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const validText = 'Hello World\nThis is a test.';
|
||||
act(() => {
|
||||
result.current.handleInput(createInput(validText));
|
||||
@@ -2084,9 +2035,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
});
|
||||
|
||||
it('should sanitize large text (>5000 chars) and strip unsafe characters', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const unsafeChars = '\x07\x08\x0B\x0C';
|
||||
const largeTextWithUnsafe =
|
||||
'safe text'.repeat(600) + unsafeChars + 'more safe text';
|
||||
@@ -2115,9 +2064,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
});
|
||||
|
||||
it('should sanitize large ANSI text (>5000 chars) and strip escape codes', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const largeTextWithAnsi =
|
||||
'\x1B[31m' +
|
||||
'red text'.repeat(800) +
|
||||
@@ -2149,9 +2096,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
});
|
||||
|
||||
it('should not strip popular emojis', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath: () => false }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
const emojis = '🐍🐳🦀🦄';
|
||||
act(() => {
|
||||
result.current.handleInput({
|
||||
@@ -2173,7 +2118,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
inputFilter: (text) => text.replace(/[^0-9]/g, ''),
|
||||
}),
|
||||
);
|
||||
@@ -2186,7 +2131,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
inputFilter: (text) => text.replace(/[^0-9]/g, ''),
|
||||
}),
|
||||
);
|
||||
@@ -2199,7 +2144,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
inputFilter: (text) => text.toUpperCase(),
|
||||
}),
|
||||
);
|
||||
@@ -2212,7 +2157,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
inputFilter: (text) => text, // Allow everything including newlines
|
||||
}),
|
||||
);
|
||||
@@ -2227,7 +2172,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
inputFilter: (text) => text.replace(/\n/g, ''), // Filter out newlines
|
||||
}),
|
||||
);
|
||||
@@ -2260,11 +2205,8 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
|
||||
describe('Memoization', () => {
|
||||
it('should keep action references stable across re-renders', () => {
|
||||
// We pass a stable `isValidPath` so that callbacks that depend on it
|
||||
// are not recreated on every render.
|
||||
const isValidPath = () => false;
|
||||
const { result, rerender } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath }),
|
||||
useTextBuffer({ viewport }),
|
||||
);
|
||||
|
||||
const initialInsert = result.current.insert;
|
||||
@@ -2281,10 +2223,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
});
|
||||
|
||||
it('should have memoized actions that operate on the latest state', () => {
|
||||
const isValidPath = () => false;
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({ viewport, isValidPath }),
|
||||
);
|
||||
const { result } = renderHook(() => useTextBuffer({ viewport }));
|
||||
|
||||
// Store a reference to the memoized insert function.
|
||||
const memoizedInsert = result.current.insert;
|
||||
@@ -2310,7 +2249,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
@@ -2325,7 +2264,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
useTextBuffer({
|
||||
initialText: 'ab',
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
@@ -2341,7 +2280,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
@@ -2363,7 +2302,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
@@ -2385,7 +2324,7 @@ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots
|
||||
const { result } = renderHook(() =>
|
||||
useTextBuffer({
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
|
||||
singleLine: true,
|
||||
}),
|
||||
);
|
||||
@@ -2841,7 +2780,6 @@ describe('Unicode helper functions', () => {
|
||||
initialText: '你好世界',
|
||||
initialCursorOffset: 4, // End of string
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2900,7 +2838,6 @@ describe('Unicode helper functions', () => {
|
||||
initialText: 'Hello你好World',
|
||||
initialCursorOffset: 10, // End
|
||||
viewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3154,7 +3091,7 @@ describe('Transformation Utilities', () => {
|
||||
useTextBuffer({
|
||||
initialText: 'original line',
|
||||
viewport,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3177,7 +3114,7 @@ describe('Transformation Utilities', () => {
|
||||
initialText:
|
||||
'a very long line that will wrap when the viewport is small',
|
||||
viewport: vp,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
}),
|
||||
{ initialProps: { vp: viewport } },
|
||||
);
|
||||
@@ -3198,7 +3135,7 @@ describe('Transformation Utilities', () => {
|
||||
useTextBuffer({
|
||||
initialText: text,
|
||||
viewport,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3231,7 +3168,7 @@ describe('Transformation Utilities', () => {
|
||||
useTextBuffer({
|
||||
initialText,
|
||||
viewport,
|
||||
isValidPath: () => true,
|
||||
escapePastedPaths: true,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3265,7 +3202,6 @@ describe('Transformation Utilities', () => {
|
||||
useTextBuffer({
|
||||
initialText: placeholder,
|
||||
viewport: scrollViewport,
|
||||
isValidPath: () => false,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user