summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks/useSlashCompletion.test.ts')
-rw-r--r--packages/cli/src/ui/hooks/useSlashCompletion.test.ts258
1 files changed, 258 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
index 13f8c240..206c4dc9 100644
--- a/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
+++ b/packages/cli/src/ui/hooks/useSlashCompletion.test.ts
@@ -1350,4 +1350,262 @@ describe('useSlashCompletion', () => {
expect(result.current.textBuffer.text).toBe('@file1.txt @src/file2.txt');
});
});
+
+ describe('File Path Escaping', () => {
+ it('should escape special characters in file names', async () => {
+ await createTestFile('', 'my file.txt');
+ await createTestFile('', 'file(1).txt');
+ await createTestFile('', 'backup[old].txt');
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@my'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestion = result.current.suggestions.find(
+ (s) => s.label === 'my file.txt',
+ );
+ expect(suggestion).toBeDefined();
+ expect(suggestion!.value).toBe('my\\ file.txt');
+ });
+
+ it('should escape parentheses in file names', async () => {
+ await createTestFile('', 'document(final).docx');
+ await createTestFile('', 'script(v2).sh');
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@doc'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestion = result.current.suggestions.find(
+ (s) => s.label === 'document(final).docx',
+ );
+ expect(suggestion).toBeDefined();
+ expect(suggestion!.value).toBe('document\\(final\\).docx');
+ });
+
+ it('should escape square brackets in file names', async () => {
+ await createTestFile('', 'backup[2024-01-01].zip');
+ await createTestFile('', 'config[dev].json');
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@backup'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestion = result.current.suggestions.find(
+ (s) => s.label === 'backup[2024-01-01].zip',
+ );
+ expect(suggestion).toBeDefined();
+ expect(suggestion!.value).toBe('backup\\[2024-01-01\\].zip');
+ });
+
+ it('should escape multiple special characters in file names', async () => {
+ await createTestFile('', 'my file (backup) [v1.2].txt');
+ await createTestFile('', 'data & config {prod}.json');
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@my'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestion = result.current.suggestions.find(
+ (s) => s.label === 'my file (backup) [v1.2].txt',
+ );
+ expect(suggestion).toBeDefined();
+ expect(suggestion!.value).toBe(
+ 'my\\ file\\ \\(backup\\)\\ \\[v1.2\\].txt',
+ );
+ });
+
+ it('should preserve path separators while escaping special characters', async () => {
+ await createTestFile(
+ '',
+ 'projects',
+ 'my project (2024)',
+ 'file with spaces.txt',
+ );
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@projects/my'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestion = result.current.suggestions.find((s) =>
+ s.label.includes('my project'),
+ );
+ expect(suggestion).toBeDefined();
+ // Should escape spaces and parentheses but preserve forward slashes
+ expect(suggestion!.value).toMatch(/my\\ project\\ \\\(2024\\\)/);
+ expect(suggestion!.value).toContain('/'); // Should contain forward slash for path separator
+ });
+
+ it('should normalize Windows path separators to forward slashes while preserving escaping', async () => {
+ // Create test with complex nested structure
+ await createTestFile(
+ '',
+ 'deep',
+ 'nested',
+ 'special folder',
+ 'file with (parentheses).txt',
+ );
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@deep/nested/special'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestion = result.current.suggestions.find((s) =>
+ s.label.includes('special folder'),
+ );
+ expect(suggestion).toBeDefined();
+ // Should use forward slashes for path separators and escape spaces
+ expect(suggestion!.value).toContain('special\\ folder/');
+ expect(suggestion!.value).not.toContain('\\\\'); // Should not contain double backslashes for path separators
+ });
+
+ it('should handle directory names with special characters', async () => {
+ await createEmptyDir('my documents (personal)');
+ await createEmptyDir('config [production]');
+ await createEmptyDir('data & logs');
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestions = result.current.suggestions;
+
+ const docSuggestion = suggestions.find(
+ (s) => s.label === 'my documents (personal)/',
+ );
+ expect(docSuggestion).toBeDefined();
+ expect(docSuggestion!.value).toBe('my\\ documents\\ \\(personal\\)/');
+
+ const configSuggestion = suggestions.find(
+ (s) => s.label === 'config [production]/',
+ );
+ expect(configSuggestion).toBeDefined();
+ expect(configSuggestion!.value).toBe('config\\ \\[production\\]/');
+
+ const dataSuggestion = suggestions.find(
+ (s) => s.label === 'data & logs/',
+ );
+ expect(dataSuggestion).toBeDefined();
+ expect(dataSuggestion!.value).toBe('data\\ \\&\\ logs/');
+ });
+
+ it('should handle files with various shell metacharacters', async () => {
+ await createTestFile('', 'file$var.txt');
+ await createTestFile('', 'important!.md');
+
+ const { result } = renderHook(() =>
+ useSlashCompletion(
+ useTextBufferForTest('@'),
+ testDirs,
+ testRootDir,
+ [],
+ mockCommandContext,
+ false,
+ mockConfig,
+ ),
+ );
+
+ await act(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 150));
+ });
+
+ const suggestions = result.current.suggestions;
+
+ const dollarSuggestion = suggestions.find(
+ (s) => s.label === 'file$var.txt',
+ );
+ expect(dollarSuggestion).toBeDefined();
+ expect(dollarSuggestion!.value).toBe('file\\$var.txt');
+
+ const importantSuggestion = suggestions.find(
+ (s) => s.label === 'important!.md',
+ );
+ expect(importantSuggestion).toBeDefined();
+ expect(importantSuggestion!.value).toBe('important\\!.md');
+ });
+ });
});