mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 15:10:59 -07:00
309 lines
10 KiB
TypeScript
309 lines
10 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import {
|
|
FileExclusions,
|
|
BINARY_EXTENSIONS,
|
|
extractExtensionsFromPatterns,
|
|
} from './ignorePatterns.js';
|
|
import type { Config } from '../config/config.js';
|
|
|
|
// Mock the memoryTool module
|
|
vi.mock('../tools/memoryTool.js', () => ({
|
|
getCurrentGeminiMdFilename: vi.fn(() => 'GEMINI.md'),
|
|
}));
|
|
|
|
describe('FileExclusions', () => {
|
|
describe('getCoreIgnorePatterns', () => {
|
|
it('should return basic ignore patterns', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getCoreIgnorePatterns();
|
|
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns).toContain('**/.git/**');
|
|
expect(patterns).toContain('**/bower_components/**');
|
|
expect(patterns).toContain('**/.svn/**');
|
|
expect(patterns).toContain('**/.hg/**');
|
|
expect(patterns).toHaveLength(5);
|
|
});
|
|
});
|
|
|
|
describe('getDefaultExcludePatterns', () => {
|
|
it('should return comprehensive patterns by default', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getDefaultExcludePatterns();
|
|
|
|
// Should include core patterns
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns).toContain('**/.git/**');
|
|
|
|
// Should include directory excludes
|
|
expect(patterns).toContain('**/.vscode/**');
|
|
expect(patterns).toContain('**/dist/**');
|
|
expect(patterns).toContain('**/build/**');
|
|
|
|
// Should include binary patterns
|
|
expect(patterns).toContain('**/*.exe');
|
|
expect(patterns).toContain('**/*.jar');
|
|
|
|
// Should include system files
|
|
expect(patterns).toContain('**/.DS_Store');
|
|
expect(patterns).toContain('**/.env');
|
|
|
|
// Should include dynamic patterns
|
|
expect(patterns).toContain('**/GEMINI.md');
|
|
});
|
|
|
|
it('should respect includeDefaults option', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getDefaultExcludePatterns({
|
|
includeDefaults: false,
|
|
includeDynamicPatterns: false,
|
|
});
|
|
|
|
expect(patterns).not.toContain('**/node_modules/**');
|
|
expect(patterns).not.toContain('**/.git/**');
|
|
expect(patterns).not.toContain('**/GEMINI.md');
|
|
expect(patterns).toHaveLength(0);
|
|
});
|
|
|
|
it('should include custom patterns', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getDefaultExcludePatterns({
|
|
customPatterns: ['**/custom/**', '**/*.custom'],
|
|
});
|
|
|
|
expect(patterns).toContain('**/custom/**');
|
|
expect(patterns).toContain('**/*.custom');
|
|
});
|
|
|
|
it('should include runtime patterns', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getDefaultExcludePatterns({
|
|
runtimePatterns: ['**/temp/**', '**/*.tmp'],
|
|
});
|
|
|
|
expect(patterns).toContain('**/temp/**');
|
|
expect(patterns).toContain('**/*.tmp');
|
|
});
|
|
|
|
it('should respect includeDynamicPatterns option', () => {
|
|
const excluder = new FileExclusions();
|
|
const patternsWithDynamic = excluder.getDefaultExcludePatterns({
|
|
includeDynamicPatterns: true,
|
|
});
|
|
const patternsWithoutDynamic = excluder.getDefaultExcludePatterns({
|
|
includeDynamicPatterns: false,
|
|
});
|
|
|
|
expect(patternsWithDynamic).toContain('**/GEMINI.md');
|
|
expect(patternsWithoutDynamic).not.toContain('**/GEMINI.md');
|
|
});
|
|
});
|
|
|
|
describe('getReadManyFilesExcludes', () => {
|
|
it('should provide legacy compatibility', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getReadManyFilesExcludes(['**/*.log']);
|
|
|
|
// Should include all default patterns
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns).toContain('**/.git/**');
|
|
expect(patterns).toContain('**/GEMINI.md');
|
|
|
|
// Should include additional excludes
|
|
expect(patterns).toContain('**/*.log');
|
|
});
|
|
});
|
|
|
|
describe('getGlobExcludes', () => {
|
|
it('should return core patterns for glob operations', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getGlobExcludes();
|
|
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns).toContain('**/.git/**');
|
|
expect(patterns).toContain('**/bower_components/**');
|
|
expect(patterns).toContain('**/.svn/**');
|
|
expect(patterns).toContain('**/.hg/**');
|
|
|
|
// Should not include comprehensive patterns by default
|
|
expect(patterns).toHaveLength(5);
|
|
});
|
|
|
|
it('should include additional excludes', () => {
|
|
const excluder = new FileExclusions();
|
|
const patterns = excluder.getGlobExcludes(['**/temp/**']);
|
|
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns).toContain('**/.git/**');
|
|
expect(patterns).toContain('**/temp/**');
|
|
});
|
|
});
|
|
|
|
describe('with Config', () => {
|
|
it('should use config custom excludes when available', () => {
|
|
const mockConfig = {
|
|
getCustomExcludes: vi.fn(() => ['**/config-exclude/**']),
|
|
} as unknown as Config;
|
|
|
|
const excluder = new FileExclusions(mockConfig);
|
|
const patterns = excluder.getDefaultExcludePatterns();
|
|
|
|
expect(patterns).toContain('**/config-exclude/**');
|
|
expect(mockConfig.getCustomExcludes).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should handle config without getCustomExcludes method', () => {
|
|
const mockConfig = {} as Config;
|
|
|
|
const excluder = new FileExclusions(mockConfig);
|
|
const patterns = excluder.getDefaultExcludePatterns();
|
|
|
|
// Should not throw and should include default patterns
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should include config custom excludes in glob patterns', () => {
|
|
const mockConfig = {
|
|
getCustomExcludes: vi.fn(() => ['**/config-glob/**']),
|
|
} as unknown as Config;
|
|
|
|
const excluder = new FileExclusions(mockConfig);
|
|
const patterns = excluder.getGlobExcludes();
|
|
|
|
expect(patterns).toContain('**/node_modules/**');
|
|
expect(patterns).toContain('**/.git/**');
|
|
expect(patterns).toContain('**/config-glob/**');
|
|
});
|
|
});
|
|
|
|
describe('buildExcludePatterns', () => {
|
|
it('should be an alias for getDefaultExcludePatterns', () => {
|
|
const excluder = new FileExclusions();
|
|
const options = {
|
|
includeDefaults: true,
|
|
customPatterns: ['**/test/**'],
|
|
runtimePatterns: ['**/runtime/**'],
|
|
};
|
|
|
|
const defaultPatterns = excluder.getDefaultExcludePatterns(options);
|
|
const buildPatterns = excluder.buildExcludePatterns(options);
|
|
|
|
expect(buildPatterns).toEqual(defaultPatterns);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('BINARY_EXTENSIONS', () => {
|
|
it.each([
|
|
['common binary file extensions', ['.exe', '.dll', '.jar', '.zip']],
|
|
['additional binary extensions', ['.dat', '.obj', '.wasm']],
|
|
['media file extensions', ['.pdf', '.png', '.jpg']],
|
|
])('should include %s', (_, extensions) => {
|
|
extensions.forEach((ext) => {
|
|
expect(BINARY_EXTENSIONS).toContain(ext);
|
|
});
|
|
});
|
|
|
|
it('should be sorted', () => {
|
|
const sortedExtensions = [...BINARY_EXTENSIONS].sort();
|
|
expect(BINARY_EXTENSIONS).toEqual(sortedExtensions);
|
|
});
|
|
|
|
it('should not contain invalid extensions from brace patterns', () => {
|
|
// If brace expansion was not handled correctly, we would see invalid extensions like '.{jpg,png}'
|
|
const invalidExtensions = BINARY_EXTENSIONS.filter(
|
|
(ext) => ext.includes('{') || ext.includes('}'),
|
|
);
|
|
expect(invalidExtensions).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('extractExtensionsFromPatterns', () => {
|
|
it.each([
|
|
[
|
|
'simple extensions',
|
|
['**/*.exe', '**/*.jar', '**/*.zip'],
|
|
['.exe', '.jar', '.zip'],
|
|
],
|
|
[
|
|
'compound extensions',
|
|
['**/*.tar.gz', '**/*.min.js', '**/*.d.ts'],
|
|
['.gz', '.js', '.ts'],
|
|
],
|
|
[
|
|
'dotfiles',
|
|
['**/*.gitignore', '**/*.profile', '**/*.bashrc'],
|
|
['.bashrc', '.gitignore', '.profile'],
|
|
],
|
|
])('should extract %s', (_, patterns, expected) => {
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
it('should handle brace expansion patterns', () => {
|
|
const patterns = ['**/*.{js,ts}', '**/*.{jpg,png}'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
expect(result).toContain('.js');
|
|
expect(result).toContain('.ts');
|
|
expect(result).toContain('.jpg');
|
|
expect(result).toContain('.png');
|
|
expect(result).not.toContain('.{js,ts}');
|
|
expect(result).not.toContain('.{jpg,png}');
|
|
});
|
|
|
|
it('should combine simple and brace expansion patterns', () => {
|
|
const patterns = ['**/*.exe', '**/*.{js,ts}', '**/*.pdf'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
expect(result).toContain('.exe');
|
|
expect(result).toContain('.js');
|
|
expect(result).toContain('.ts');
|
|
expect(result).toContain('.pdf');
|
|
});
|
|
|
|
it('should handle empty brace expansion', () => {
|
|
const patterns = ['**/*.{}', '**/*.{,}'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
// Empty extensions should be filtered out
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
|
|
it('should ignore invalid patterns', () => {
|
|
const patterns = ['no-asterisk.exe', '**/*no-dot', '**/*.{unclosed'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
|
|
it('should remove duplicates and sort results', () => {
|
|
const patterns = ['**/*.js', '**/*.{js,ts}', '**/*.ts'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
expect(result).toEqual(['.js', '.ts']);
|
|
});
|
|
|
|
it('should handle complex brace patterns with multiple extensions', () => {
|
|
const patterns = ['**/*.{html,css,js,jsx,ts,tsx}'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
expect(result).toEqual(['.css', '.html', '.js', '.jsx', '.ts', '.tsx']);
|
|
});
|
|
|
|
it('should handle edge cases with path.extname', () => {
|
|
const patterns = ['**/*.hidden.', '**/*.config.json'];
|
|
const result = extractExtensionsFromPatterns(patterns);
|
|
|
|
// Should handle edge cases properly (trailing dots are filtered out)
|
|
expect(result).toEqual(['.json']);
|
|
});
|
|
});
|