Skip to main content
Glama
ignorePatterns.test.ts13.8 kB
import { jest } from '@jest/globals'; import fs from 'fs'; import path from 'path'; // Mock dependencies jest.mock('fs'); jest.mock('fs/promises'); const mockFs = fs as jest.Mocked<typeof fs>; // Import the functions we want to test // Since these are likely internal to projectIdentifier, we'll test them through that module import { loadIgnorePatterns, shouldIgnoreFile, parseIgnoreFile } from '../projectIdentifier'; describe('Ignore Pattern Processing', () => { beforeEach(() => { jest.clearAllMocks(); // Default mock: files don't exist mockFs.existsSync.mockReturnValue(false); mockFs.readFileSync.mockReturnValue(''); }); describe('loadIgnorePatterns', () => { it('should load patterns from .gitignore', async () => { mockFs.existsSync.mockImplementation((filePath: any) => { return filePath.toString().includes('.gitignore'); }); (mockFs.readFileSync as any).mockImplementation((filePath: any, options?: any) => { if (filePath.toString().includes('.gitignore')) { return ` # Comments should be ignored node_modules/ *.log dist/ # Blank lines should be ignored .env .env.local `.trim(); } return ''; }); const patterns = await loadIgnorePatterns('/test/project'); expect(patterns).toContain('node_modules/'); expect(patterns).toContain('*.log'); expect(patterns).toContain('dist/'); expect(patterns).toContain('.env'); expect(patterns).toContain('.env.local'); // Comments and blank lines should not be included expect(patterns).not.toContain('# Comments should be ignored'); expect(patterns).not.toContain(''); }); it('should load patterns from .cursorignore', async () => { mockFs.existsSync.mockImplementation((filePath: any) => { return filePath.toString().includes('.cursorignore'); }); (mockFs.readFileSync as any).mockImplementation((filePath: any, options?: any) => { if (filePath.toString().includes('.cursorignore')) { return ` .cursor/ *.cursor-* cursor-logs/ `.trim(); } return ''; }); const patterns = await loadIgnorePatterns('/test/project'); expect(patterns).toContain('.cursor/'); expect(patterns).toContain('*.cursor-*'); expect(patterns).toContain('cursor-logs/'); }); it('should load patterns from .vscodeignore', async () => { mockFs.existsSync.mockImplementation((filePath: any) => { return filePath.toString().includes('.vscodeignore'); }); (mockFs.readFileSync as any).mockImplementation((filePath: any, options?: any) => { if (filePath.toString().includes('.vscodeignore')) { return ` .vscode/settings.json .vscode/launch.json *.code-workspace `.trim(); } return ''; }); const patterns = await loadIgnorePatterns('/test/project'); expect(patterns).toContain('.vscode/settings.json'); expect(patterns).toContain('.vscode/launch.json'); expect(patterns).toContain('*.code-workspace'); }); it('should load patterns from .ambianceignore', async () => { mockFs.existsSync.mockImplementation((filePath: any) => { return filePath.toString().includes('.ambianceignore'); }); (mockFs.readFileSync as any).mockImplementation((filePath: any, options?: any) => { if (filePath.toString().includes('.ambianceignore')) { return ` # Ambiance-specific ignores coverage/ *.tmp *.swp docs/_build/ `.trim(); } return ''; }); const patterns = await loadIgnorePatterns('/test/project'); expect(patterns).toContain('coverage/'); expect(patterns).toContain('*.tmp'); expect(patterns).toContain('*.swp'); expect(patterns).toContain('docs/_build/'); }); it('should combine patterns from multiple ignore files', async () => { mockFs.existsSync.mockReturnValue(true); (mockFs.readFileSync as any).mockImplementation((filePath: any, options?: any) => { if (filePath.toString().includes('.gitignore')) { return 'node_modules/\n.env'.trim(); } if (filePath.toString().includes('.cursorignore')) { return '.cursor/\n*.cursor-*'.trim(); } if (filePath.toString().includes('.ambianceignore')) { return 'coverage/\n*.tmp'.trim(); } return ''; }); const patterns = await loadIgnorePatterns('/test/project'); // Should contain patterns from all files expect(patterns).toContain('node_modules/'); expect(patterns).toContain('.env'); expect(patterns).toContain('.cursor/'); expect(patterns).toContain('*.cursor-*'); expect(patterns).toContain('coverage/'); expect(patterns).toContain('*.tmp'); }); it('should include default ignore patterns', async () => { // No ignore files exist mockFs.existsSync.mockReturnValue(false); const patterns = await loadIgnorePatterns('/test/project'); // Should include built-in defaults expect(patterns).toContain('node_modules/**'); expect(patterns).toContain('.git/**'); expect(patterns).toContain('dist/**'); expect(patterns).toContain('build/**'); expect(patterns).toContain('*.log'); expect(patterns).toContain('.DS_Store'); expect(patterns).toContain('Thumbs.db'); }); it('should handle file read errors gracefully', async () => { mockFs.existsSync.mockReturnValue(true); mockFs.readFileSync.mockImplementation(() => { throw new Error('Permission denied'); }); const patterns = await loadIgnorePatterns('/test/project'); // Should still return default patterns even if files can't be read expect(patterns).toContain('node_modules/**'); expect(patterns.length).toBeGreaterThan(0); }); }); describe('parseIgnoreFile', () => { it('should parse ignore file content correctly', () => { const content = ` # This is a comment node_modules/ *.log # Another comment dist/ build/ # Empty lines above should be ignored .env .env.local `; const patterns = parseIgnoreFile(content); expect(patterns).toEqual(['node_modules/', '*.log', 'dist/', 'build/', '.env', '.env.local']); }); it('should handle Windows line endings', () => { const content = 'node_modules/\r\n*.log\r\ndist/\r\n'; const patterns = parseIgnoreFile(content); expect(patterns).toEqual(['node_modules/', '*.log', 'dist/']); }); it('should trim whitespace from patterns', () => { const content = ' node_modules/ \n *.log \n dist/ '; const patterns = parseIgnoreFile(content); expect(patterns).toEqual(['node_modules/', '*.log', 'dist/']); }); it('should handle empty content', () => { const patterns = parseIgnoreFile(''); expect(patterns).toEqual([]); }); it('should ignore lines starting with #', () => { const content = ` # This is a comment node_modules/ # Another comment *.log `; const patterns = parseIgnoreFile(content); expect(patterns).toEqual(['node_modules/', '*.log']); }); }); describe('shouldIgnoreFile', () => { it('should match simple patterns', () => { const patterns = ['*.log', 'node_modules/', 'dist/']; expect(shouldIgnoreFile('app.log', patterns)).toBe(true); expect(shouldIgnoreFile('error.log', patterns)).toBe(true); expect(shouldIgnoreFile('node_modules/', patterns)).toBe(true); expect(shouldIgnoreFile('dist/', patterns)).toBe(true); expect(shouldIgnoreFile('app.js', patterns)).toBe(false); expect(shouldIgnoreFile('src/index.ts', patterns)).toBe(false); }); it('should match wildcard patterns', () => { const patterns = ['*.min.js', '*.map', 'test*.tmp']; expect(shouldIgnoreFile('bundle.min.js', patterns)).toBe(true); expect(shouldIgnoreFile('app.js.map', patterns)).toBe(true); expect(shouldIgnoreFile('test123.tmp', patterns)).toBe(true); expect(shouldIgnoreFile('bundle.js', patterns)).toBe(false); expect(shouldIgnoreFile('app.js', patterns)).toBe(false); }); it('should match directory patterns', () => { const patterns = ['node_modules/**', 'dist/**', '.git/**']; expect(shouldIgnoreFile('node_modules/express/index.js', patterns)).toBe(true); expect(shouldIgnoreFile('dist/bundle.js', patterns)).toBe(true); expect(shouldIgnoreFile('.git/config', patterns)).toBe(true); expect(shouldIgnoreFile('src/index.js', patterns)).toBe(false); expect(shouldIgnoreFile('README.md', patterns)).toBe(false); }); it('should handle relative paths correctly', () => { const patterns = ['src/test/**', 'docs/*.md']; expect(shouldIgnoreFile('src/test/unit.test.ts', patterns)).toBe(true); expect(shouldIgnoreFile('docs/readme.md', patterns)).toBe(true); expect(shouldIgnoreFile('src/index.ts', patterns)).toBe(false); expect(shouldIgnoreFile('test/unit.test.ts', patterns)).toBe(false); }); it('should be case-sensitive by default', () => { const patterns = ['*.LOG', 'Node_Modules/']; expect(shouldIgnoreFile('app.LOG', patterns)).toBe(true); expect(shouldIgnoreFile('Node_Modules/', patterns)).toBe(true); expect(shouldIgnoreFile('app.log', patterns)).toBe(false); expect(shouldIgnoreFile('node_modules/', patterns)).toBe(false); }); it('should handle negation patterns', () => { const patterns = ['*.log', '!important.log']; expect(shouldIgnoreFile('app.log', patterns)).toBe(true); expect(shouldIgnoreFile('error.log', patterns)).toBe(true); // Negation pattern should override ignore expect(shouldIgnoreFile('important.log', patterns)).toBe(false); }); it('should handle complex glob patterns', () => { const patterns = ['src/**/*.{test,spec}.{js,ts}', '**/node_modules/**', '**/*.min.{js,css}']; expect(shouldIgnoreFile('src/utils/helper.test.js', patterns)).toBe(true); expect(shouldIgnoreFile('src/components/Button.spec.ts', patterns)).toBe(true); expect(shouldIgnoreFile('packages/core/node_modules/express/index.js', patterns)).toBe(true); expect(shouldIgnoreFile('dist/app.min.js', patterns)).toBe(true); expect(shouldIgnoreFile('dist/styles.min.css', patterns)).toBe(true); expect(shouldIgnoreFile('src/utils/helper.js', patterns)).toBe(false); expect(shouldIgnoreFile('dist/app.js', patterns)).toBe(false); }); it('should handle empty patterns array', () => { const patterns: string[] = []; expect(shouldIgnoreFile('any-file.js', patterns)).toBe(false); expect(shouldIgnoreFile('node_modules/package.json', patterns)).toBe(false); }); }); describe('Pattern Priority and Ordering', () => { it('should respect pattern order for negation', () => { // Later patterns should override earlier ones const patterns1 = ['*.log', '!important.log']; const patterns2 = ['!important.log', '*.log']; // In patterns1, negation comes after ignore, so important.log should NOT be ignored expect(shouldIgnoreFile('important.log', patterns1)).toBe(false); // In patterns2, ignore comes after negation, so important.log SHOULD be ignored expect(shouldIgnoreFile('important.log', patterns2)).toBe(true); }); it('should handle multiple negations', () => { const patterns = ['*.tmp', '!important.tmp', 'very-important.tmp', '!critical.tmp']; expect(shouldIgnoreFile('temp.tmp', patterns)).toBe(true); expect(shouldIgnoreFile('important.tmp', patterns)).toBe(false); expect(shouldIgnoreFile('very-important.tmp', patterns)).toBe(true); // Specific match overrides negation expect(shouldIgnoreFile('critical.tmp', patterns)).toBe(false); }); }); describe('Performance', () => { it('should handle large numbers of patterns efficiently', () => { // Generate many patterns const patterns = []; for (let i = 0; i < 1000; i++) { patterns.push(`pattern${i}/**`); patterns.push(`*.ext${i}`); } const startTime = Date.now(); // Test multiple files for (let i = 0; i < 100; i++) { shouldIgnoreFile(`test${i}.js`, patterns); shouldIgnoreFile(`pattern${i}/file.txt`, patterns); } const endTime = Date.now(); // Should complete in reasonable time (less than 10 seconds for pattern matching) expect(endTime - startTime).toBeLessThan(10000); }); }); describe('Edge Cases', () => { it('should handle files with special characters', () => { // Use exact string matching for files with special characters const patterns = ['file\\[special\\].js', 'test\\(temp\\).txt']; expect(shouldIgnoreFile('file[special].js', patterns)).toBe(true); expect(shouldIgnoreFile('test(temp).txt', patterns)).toBe(true); }); it('should handle unicode file names', () => { const patterns = ['*.测试', 'файл*']; expect(shouldIgnoreFile('test.测试', patterns)).toBe(true); expect(shouldIgnoreFile('файл123', patterns)).toBe(true); }); it('should handle very long file paths', () => { const longPath = 'very/'.repeat(100) + 'deep/file.js'; const patterns = ['**/file.js']; expect(shouldIgnoreFile(longPath, patterns)).toBe(true); }); it('should handle malformed patterns gracefully', () => { const patterns = ['[unclosed', '**/[', '**/*[a-']; // Should not throw errors, even with malformed patterns expect(() => shouldIgnoreFile('test.js', patterns)).not.toThrow(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sbarron/AmbianceMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server