import * as fs from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; import { globby } from 'globby'; import { minimatch } from 'minimatch'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { getIgnoreFilePatterns, getIgnorePatterns, parseIgnoreContent, searchFiles, } from '../../../src/core/file/fileSearch.js'; import { createMockConfig, isWindows } from '../../testing/testUtils.js'; vi.mock('fs/promises'); vi.mock('globby'); describe('fileSearch', () => { beforeEach(() => { vi.resetAllMocks(); }); describe('getIgnoreFilePaths', () => { test('should return correct paths when .gitignore and .repomixignore exist', async () => { vi.mocked(fs.access).mockResolvedValue(undefined); const mockConfig = createMockConfig({ ignore: { useGitignore: true, useDefaultPatterns: true, customPatterns: [], }, }); const filePatterns = await getIgnoreFilePatterns(mockConfig); expect(filePatterns).toEqual(['**/.gitignore', '**/.repomixignore']); }); test('should not include .gitignore when useGitignore is false', async () => { vi.mocked(fs.access).mockResolvedValue(undefined); const mockConfig = createMockConfig({ ignore: { useGitignore: false, useDefaultPatterns: true, customPatterns: [], }, }); const filePatterns = await getIgnoreFilePatterns(mockConfig); expect(filePatterns).toEqual(['**/.repomixignore']); }); }); describe('getIgnorePatterns', () => { test('should return default patterns when useDefaultPatterns is true', async () => { const mockConfig = createMockConfig({ ignore: { useGitignore: true, useDefaultPatterns: true, customPatterns: [], }, }); const patterns = await getIgnorePatterns(process.cwd(), mockConfig); expect(patterns.length).toBeGreaterThan(0); expect(patterns).toContain('**/node_modules/**'); }); test('should include custom patterns', async () => { const mockConfig = createMockConfig({ ignore: { useGitignore: true, useDefaultPatterns: false, customPatterns: ['*.custom', 'temp/'], }, }); const patterns = await getIgnorePatterns(process.cwd(), mockConfig); expect(patterns).toEqual(['repomix-output.txt', '*.custom', 'temp/']); }); test('should combine default and custom patterns', async () => { const mockConfig = createMockConfig({ ignore: { useGitignore: true, useDefaultPatterns: true, customPatterns: ['*.custom', 'temp/'], }, }); const patterns = await getIgnorePatterns(process.cwd(), mockConfig); expect(patterns).toContain('**/node_modules/**'); expect(patterns).toContain('*.custom'); expect(patterns).toContain('temp/'); }); }); describe('parseIgnoreContent', () => { test('should correctly parse ignore content', () => { const content = ` # Comment node_modules *.log .DS_Store `; const patterns = parseIgnoreContent(content); expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); }); test('should handle mixed line endings', () => { const content = 'node_modules\n*.log\r\n.DS_Store\r'; const patterns = parseIgnoreContent(content); expect(patterns).toEqual(['node_modules', '*.log', '.DS_Store']); }); }); describe('filterFiles', () => { beforeEach(() => { vi.resetAllMocks(); }); test('should call globby with correct parameters', async () => { const mockConfig = createMockConfig({ include: ['**/*.js'], ignore: { useGitignore: true, useDefaultPatterns: false, customPatterns: ['*.custom'], }, }); vi.mocked(globby).mockResolvedValue(['file1.js', 'file2.js']); vi.mocked(fs.access).mockResolvedValue(undefined); await searchFiles('/mock/root', mockConfig); expect(globby).toHaveBeenCalledWith( ['**/*.js'], expect.objectContaining({ cwd: '/mock/root', ignore: expect.arrayContaining(['*.custom']), ignoreFiles: expect.arrayContaining(['**/.gitignore', '**/.repomixignore']), onlyFiles: true, absolute: false, dot: true, followSymbolicLinks: false, }), ); }); test.runIf(!isWindows)('Honor .gitignore files in subdirectories', async () => { const mockConfig = createMockConfig({ include: ['**/*.js'], ignore: { useGitignore: true, useDefaultPatterns: false, customPatterns: [], }, }); const mockFileStructure = [ 'root/file1.js', 'root/subdir/file2.js', 'root/subdir/ignored.js', 'root/another/file3.js', ]; const mockGitignoreContent = { '/mock/root/.gitignore': '*.log', '/mock/root/subdir/.gitignore': 'ignored.js', }; vi.mocked(globby).mockImplementation(async () => { // Simulate filtering files based on .gitignore return mockFileStructure.filter((file) => { const relativePath = file.replace('root/', ''); const dir = path.dirname(relativePath); const gitignorePath = path.join('/mock/root', dir, '.gitignore'); const gitignoreContent = mockGitignoreContent[gitignorePath as keyof typeof mockGitignoreContent]; if (gitignoreContent && minimatch(path.basename(file), gitignoreContent)) { return false; } return true; }); }); vi.mocked(fs.readFile).mockImplementation(async (filePath) => { return mockGitignoreContent[filePath as keyof typeof mockGitignoreContent] || ''; }); const result = await searchFiles('/mock/root', mockConfig); expect(result).toEqual(['root/another/file3.js', 'root/subdir/file2.js', 'root/file1.js']); expect(result).not.toContain('root/subdir/ignored.js'); }); test('should not apply .gitignore when useGitignore is false', async () => { const mockConfig = createMockConfig({ include: ['**/*.js'], ignore: { useGitignore: false, useDefaultPatterns: false, customPatterns: [], }, }); const mockFileStructure = [ 'root/file1.js', 'root/another/file3.js', 'root/subdir/file2.js', 'root/subdir/ignored.js', ]; vi.mocked(globby).mockResolvedValue(mockFileStructure); const result = await searchFiles('/mock/root', mockConfig); expect(result).toEqual(mockFileStructure); expect(result).toContain('root/subdir/ignored.js'); }); }); });