Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions __tests__/extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2921,6 +2921,55 @@ export function multiply(a: number, b: number): number {
cg.close();
});

it('should index multiple files with ioBatchSize 1', async () => {
const srcDir = path.join(tempDir, 'src');
fs.mkdirSync(srcDir);

fs.writeFileSync(
path.join(srcDir, 'math.ts'),
`export function add(a: number, b: number) { return a + b; }`
);

fs.writeFileSync(
path.join(srcDir, 'string.ts'),
`export function capitalize(s: string) { return s.toUpperCase(); }`
);

const cg = CodeGraph.initSync(tempDir);
const result = await cg.indexAll({ ioBatchSize: 1 });

expect(result.success).toBe(true);
expect(result.filesIndexed).toBe(2);
expect(cg.getFiles().map((f) => f.path).sort()).toEqual(['src/math.ts', 'src/string.ts']);

cg.close();
});

it('should preserve indexing results with a large ioBatchSize', async () => {
const srcDir = path.join(tempDir, 'src');
fs.mkdirSync(srcDir);

fs.writeFileSync(
path.join(srcDir, 'math.ts'),
`export function add(a: number, b: number) { return a + b; }`
);

fs.writeFileSync(
path.join(srcDir, 'string.ts'),
`export function capitalize(s: string) { return s.toUpperCase(); }`
);

const cg = CodeGraph.initSync(tempDir);
const result = await cg.indexAll({ ioBatchSize: 100 });

expect(result.success).toBe(true);
expect(result.filesIndexed).toBe(2);
expect(cg.getNodesInFile('src/math.ts').some((n) => n.name === 'add')).toBe(true);
expect(cg.getNodesInFile('src/string.ts').some((n) => n.name === 'capitalize')).toBe(true);

cg.close();
});

it('should track file hashes for incremental updates', async () => {
// Create initial file
const srcDir = path.join(tempDir, 'src');
Expand Down Expand Up @@ -2979,6 +3028,142 @@ export function multiply(a: number, b: number): number {

cg.close();
});

it('should exclude paths from full indexing', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'JSTests'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'JSTests', 'fixture.ts'), 'export const fixture = 1;');

const cg = CodeGraph.initSync(tempDir);
const result = await cg.indexAll({ exclude: ['JSTests'] });

expect(result.success).toBe(true);
expect(cg.getFile('src/app.ts')).toBeDefined();
expect(cg.getFile('JSTests/fixture.ts')).toBeNull();

cg.close();
});

it('should stack multiple exclude patterns', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'vendor'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'vendor', 'pkg.ts'), 'export const pkg = 1;');
fs.writeFileSync(path.join(tempDir, 'src', 'api.generated.ts'), 'export const generated = 1;');

const cg = CodeGraph.initSync(tempDir);
await cg.indexAll({ exclude: ['vendor', '**/*.generated.ts'] });

expect(cg.getFile('src/app.ts')).toBeDefined();
expect(cg.getFile('vendor/pkg.ts')).toBeNull();
expect(cg.getFile('src/api.generated.ts')).toBeNull();

cg.close();
});

it('should use include patterns as a whitelist', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'scripts'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'scripts', 'build.ts'), 'export const build = 1;');

const cg = CodeGraph.initSync(tempDir);
await cg.indexAll({ include: ['src/**'] });

expect(cg.getFile('src/app.ts')).toBeDefined();
expect(cg.getFile('scripts/build.ts')).toBeNull();

cg.close();
});

it('should let exclude patterns win over include patterns', async () => {
fs.mkdirSync(path.join(tempDir, 'src', 'legacy'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'src', 'legacy', 'old.ts'), 'export const old = 1;');

const cg = CodeGraph.initSync(tempDir);
await cg.indexAll({ include: ['src/**'], exclude: ['src/legacy/**'] });

expect(cg.getFile('src/app.ts')).toBeDefined();
expect(cg.getFile('src/legacy/old.ts')).toBeNull();

cg.close();
});

it('should keep gitignored paths excluded even when include matches them', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'ignored'), { recursive: true });
fs.writeFileSync(path.join(tempDir, '.gitignore'), 'ignored/\n');
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'ignored', 'hidden.ts'), 'export const hidden = 1;');

const cg = CodeGraph.initSync(tempDir);
await cg.indexAll({ include: ['src/**', 'ignored/**'] });

expect(cg.getFile('src/app.ts')).toBeDefined();
expect(cg.getFile('ignored/hidden.ts')).toBeNull();

cg.close();
});

it('should leave indexing unchanged when no path filters are supplied', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'tests'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'tests', 'app.test.ts'), 'export const test = 1;');

const cg = CodeGraph.initSync(tempDir);
const result = await cg.indexAll();

expect(result.success).toBe(true);
expect(result.filesIndexed).toBe(2);
expect(cg.getFiles().map((f) => f.path).sort()).toEqual(['src/app.ts', 'tests/app.test.ts']);

cg.close();
});

it('should reject invalid path filters before indexing', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');

const cg = CodeGraph.initSync(tempDir);
await expect(cg.indexAll({ exclude: [''] })).rejects.toThrow(/Invalid exclude pattern/);
expect(cg.getFiles()).toHaveLength(0);

cg.close();
});

it('should reject invalid ioBatchSize values before indexing', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');

for (const ioBatchSize of [0, -5, 'abc', 1025]) {
const cg = CodeGraph.initSync(tempDir);
await expect(cg.indexAll({ ioBatchSize } as any)).rejects.toThrow(/Invalid ioBatchSize/);
expect(cg.getFiles()).toHaveLength(0);
cg.close();
cleanupTempDir(path.join(tempDir, '.codegraph'));
}
});

it('should apply path filters during sync', async () => {
fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'vendor'), { recursive: true });
fs.writeFileSync(path.join(tempDir, 'src', 'app.ts'), 'export const app = 1;');
fs.writeFileSync(path.join(tempDir, 'vendor', 'pkg.ts'), 'export const pkg = 1;');

const cg = CodeGraph.initSync(tempDir);
await cg.indexAll();

const result = await cg.sync({ exclude: ['vendor'] });

expect(result.filesRemoved).toBe(1);
expect(cg.getFile('src/app.ts')).toBeDefined();
expect(cg.getFile('vendor/pkg.ts')).toBeNull();

cg.close();
});
});

describe('Path Normalization', () => {
Expand Down
Loading