summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks/atCommandProcessor.test.ts')
-rw-r--r--packages/cli/src/ui/hooks/atCommandProcessor.test.ts196
1 files changed, 188 insertions, 8 deletions
diff --git a/packages/cli/src/ui/hooks/atCommandProcessor.test.ts b/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
index b0a4cc13..a9f43062 100644
--- a/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
+++ b/packages/cli/src/ui/hooks/atCommandProcessor.test.ts
@@ -5,8 +5,9 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
+import type { Mocked } from 'vitest';
import { handleAtCommand } from './atCommandProcessor.js';
-import { Config } from '@gemini-code/core';
+import { Config, FileDiscoveryService } from '@gemini-code/core';
import { ToolCallStatus } from '../types.js';
import { UseHistoryManagerReturn } from './useHistoryManager.js';
import * as fsPromises from 'fs/promises';
@@ -18,6 +19,9 @@ const mockConfig = {
getToolRegistry: mockGetToolRegistry,
getTargetDir: mockGetTargetDir,
isSandboxed: vi.fn(() => false),
+ getFileService: vi.fn(),
+ getFileFilteringRespectGitIgnore: vi.fn(() => true),
+ getFileFilteringAllowBuildArtifacts: vi.fn(() => false),
} as unknown as Config;
const mockReadManyFilesExecute = vi.fn();
@@ -48,8 +52,17 @@ vi.mock('fs/promises', async () => {
};
});
+vi.mock('@gemini-code/core', async () => {
+ const actual = await vi.importActual('@gemini-code/core');
+ return {
+ ...actual,
+ FileDiscoveryService: vi.fn(),
+ };
+});
+
describe('handleAtCommand', () => {
let abortController: AbortController;
+ let mockFileDiscoveryService: Mocked<FileDiscoveryService>;
beforeEach(() => {
vi.resetAllMocks();
@@ -73,6 +86,23 @@ describe('handleAtCommand', () => {
llmContent: 'No files found',
returnDisplay: '',
});
+
+ // Mock FileDiscoveryService
+ mockFileDiscoveryService = {
+ initialize: vi.fn(),
+ shouldIgnoreFile: vi.fn(() => false),
+ filterFiles: vi.fn((files) => files),
+ getIgnoreInfo: vi.fn(() => ({ gitIgnored: [], customIgnored: [] })),
+ isGitRepository: vi.fn(() => true),
+ };
+ vi.mocked(FileDiscoveryService).mockImplementation(
+ () => mockFileDiscoveryService,
+ );
+
+ // Mock getFileService to return the mocked FileDiscoveryService
+ mockConfig.getFileService = vi
+ .fn()
+ .mockResolvedValue(mockFileDiscoveryService);
});
afterEach(() => {
@@ -143,7 +173,7 @@ ${fileContent}`,
125,
);
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [filePath] },
+ { paths: [filePath], respectGitIgnore: true },
abortController.signal,
);
expect(mockAddItem).toHaveBeenCalledWith(
@@ -191,7 +221,7 @@ ${fileContent}`,
126,
);
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [resolvedGlob] },
+ { paths: [resolvedGlob], respectGitIgnore: true },
abortController.signal,
);
expect(mockOnDebugMessage).toHaveBeenCalledWith(
@@ -295,7 +325,7 @@ ${fileContent}`,
signal: abortController.signal,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [unescapedPath] },
+ { paths: [unescapedPath], respectGitIgnore: true },
abortController.signal,
);
});
@@ -325,7 +355,7 @@ ${content2}`,
signal: abortController.signal,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [file1, file2] },
+ { paths: [file1, file2], respectGitIgnore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -368,7 +398,7 @@ ${content2}`,
signal: abortController.signal,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [file1, file2] },
+ { paths: [file1, file2], respectGitIgnore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -434,7 +464,7 @@ ${content2}`,
});
expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
- { paths: [file1, resolvedFile2] },
+ { paths: [file1, resolvedFile2], respectGitIgnore: true },
abortController.signal,
);
expect(result.processedQuery).toEqual([
@@ -538,7 +568,7 @@ ${fileContent}`,
// If the mock is simpler, it might use queryPath if stat(queryPath) succeeds.
// The most important part is that *some* version of the path that leads to the content is used.
// Let's assume it uses the path from the query if stat confirms it exists (even if different case on disk)
- { paths: [queryPath] },
+ { paths: [queryPath], respectGitIgnore: true },
abortController.signal,
);
expect(mockAddItem).toHaveBeenCalledWith(
@@ -557,4 +587,154 @@ ${fileContent}`,
]);
expect(result.shouldProceed).toBe(true);
});
+
+ describe('git-aware filtering', () => {
+ it('should skip git-ignored files in @ commands', async () => {
+ const gitIgnoredFile = 'node_modules/package.json';
+ const query = `@${gitIgnoredFile}`;
+
+ // Mock the file discovery service to report this file as git-ignored
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string) => path === gitIgnoredFile,
+ );
+
+ const result = await handleAtCommand({
+ query,
+ config: mockConfig,
+ addItem: mockAddItem,
+ onDebugMessage: mockOnDebugMessage,
+ messageId: 200,
+ signal: abortController.signal,
+ });
+
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
+ gitIgnoredFile,
+ );
+ expect(mockOnDebugMessage).toHaveBeenCalledWith(
+ `Path ${gitIgnoredFile} is git-ignored and will be skipped.`,
+ );
+ expect(mockOnDebugMessage).toHaveBeenCalledWith(
+ 'Ignored 1 git-ignored files: node_modules/package.json',
+ );
+ expect(mockReadManyFilesExecute).not.toHaveBeenCalled();
+ expect(result.processedQuery).toEqual([{ text: query }]);
+ expect(result.shouldProceed).toBe(true);
+ });
+
+ it('should process non-git-ignored files normally', async () => {
+ const validFile = 'src/index.ts';
+ const query = `@${validFile}`;
+ const fileContent = 'console.log("Hello world");';
+
+ mockFileDiscoveryService.shouldIgnoreFile.mockReturnValue(false);
+ mockReadManyFilesExecute.mockResolvedValue({
+ llmContent: `
+--- ${validFile} ---
+${fileContent}`,
+ returnDisplay: 'Read 1 file.',
+ });
+
+ const result = await handleAtCommand({
+ query,
+ config: mockConfig,
+ addItem: mockAddItem,
+ onDebugMessage: mockOnDebugMessage,
+ messageId: 201,
+ signal: abortController.signal,
+ });
+
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
+ validFile,
+ );
+ expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
+ { paths: [validFile], respectGitIgnore: true },
+ abortController.signal,
+ );
+ expect(result.processedQuery).toEqual([
+ { text: `@${validFile}` },
+ { text: '\n--- Content from referenced files ---' },
+ { text: `\nContent from @${validFile}:\n` },
+ { text: fileContent },
+ { text: '\n--- End of content ---' },
+ ]);
+ expect(result.shouldProceed).toBe(true);
+ });
+
+ it('should handle mixed git-ignored and valid files', async () => {
+ const validFile = 'README.md';
+ const gitIgnoredFile = '.env';
+ const query = `@${validFile} @${gitIgnoredFile}`;
+ const fileContent = '# Project README';
+
+ mockFileDiscoveryService.shouldIgnoreFile.mockImplementation(
+ (path: string) => path === gitIgnoredFile,
+ );
+ mockReadManyFilesExecute.mockResolvedValue({
+ llmContent: `
+--- ${validFile} ---
+${fileContent}`,
+ returnDisplay: 'Read 1 file.',
+ });
+
+ const result = await handleAtCommand({
+ query,
+ config: mockConfig,
+ addItem: mockAddItem,
+ onDebugMessage: mockOnDebugMessage,
+ messageId: 202,
+ signal: abortController.signal,
+ });
+
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
+ validFile,
+ );
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
+ gitIgnoredFile,
+ );
+ expect(mockOnDebugMessage).toHaveBeenCalledWith(
+ `Path ${gitIgnoredFile} is git-ignored and will be skipped.`,
+ );
+ expect(mockOnDebugMessage).toHaveBeenCalledWith(
+ 'Ignored 1 git-ignored files: .env',
+ );
+ expect(mockReadManyFilesExecute).toHaveBeenCalledWith(
+ { paths: [validFile], respectGitIgnore: true },
+ abortController.signal,
+ );
+ expect(result.processedQuery).toEqual([
+ { text: `@${validFile} @${gitIgnoredFile}` },
+ { text: '\n--- Content from referenced files ---' },
+ { text: `\nContent from @${validFile}:\n` },
+ { text: fileContent },
+ { text: '\n--- End of content ---' },
+ ]);
+ expect(result.shouldProceed).toBe(true);
+ });
+
+ it('should always ignore .git directory files', async () => {
+ const gitFile = '.git/config';
+ const query = `@${gitFile}`;
+
+ mockFileDiscoveryService.shouldIgnoreFile.mockReturnValue(true);
+
+ const result = await handleAtCommand({
+ query,
+ config: mockConfig,
+ addItem: mockAddItem,
+ onDebugMessage: mockOnDebugMessage,
+ messageId: 203,
+ signal: abortController.signal,
+ });
+
+ expect(mockFileDiscoveryService.shouldIgnoreFile).toHaveBeenCalledWith(
+ gitFile,
+ );
+ expect(mockOnDebugMessage).toHaveBeenCalledWith(
+ `Path ${gitFile} is git-ignored and will be skipped.`,
+ );
+ expect(mockReadManyFilesExecute).not.toHaveBeenCalled();
+ expect(result.processedQuery).toEqual([{ text: query }]);
+ expect(result.shouldProceed).toBe(true);
+ });
+ });
});