GitNexus/gitnexus/test/unit/symbol-table.test.ts
abhigyanpatwari 8a100a76d3 test: add test suite with vitest (unit + integration + fixtures)
- 59 test files covering unit and integration tests
- vitest config with coverage thresholds and fork pooling
- Test fixtures (mini-repo + multi-language sample code)
- Add vitest + coverage-v8 to devDependencies
- Add test scripts (test, test:integration, test:all, test:watch, test:coverage)
- Move typescript to devDependencies where it belongs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:07:02 +05:30

121 lines
4.6 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { createSymbolTable, type SymbolTable } from '../../src/core/ingestion/symbol-table.js';
describe('SymbolTable', () => {
let table: SymbolTable;
beforeEach(() => {
table = createSymbolTable();
});
describe('add', () => {
it('registers a symbol in the table', () => {
table.add('src/index.ts', 'main', 'func:main', 'Function');
expect(table.getStats().globalSymbolCount).toBe(1);
expect(table.getStats().fileCount).toBe(1);
});
it('handles multiple symbols in the same file', () => {
table.add('src/index.ts', 'main', 'func:main', 'Function');
table.add('src/index.ts', 'helper', 'func:helper', 'Function');
expect(table.getStats().fileCount).toBe(1);
expect(table.getStats().globalSymbolCount).toBe(2);
});
it('handles same name in different files', () => {
table.add('src/a.ts', 'init', 'func:a:init', 'Function');
table.add('src/b.ts', 'init', 'func:b:init', 'Function');
expect(table.getStats().fileCount).toBe(2);
// Global index groups by name, so 'init' has one entry with two definitions
expect(table.getStats().globalSymbolCount).toBe(1);
});
it('allows duplicate adds for same file and name', () => {
table.add('src/a.ts', 'foo', 'func:foo:1', 'Function');
table.add('src/a.ts', 'foo', 'func:foo:2', 'Function');
// File index overwrites: last wins
expect(table.lookupExact('src/a.ts', 'foo')).toBe('func:foo:2');
// Global index appends
expect(table.lookupFuzzy('foo')).toHaveLength(2);
});
});
describe('lookupExact', () => {
it('finds a symbol by file path and name', () => {
table.add('src/index.ts', 'main', 'func:main', 'Function');
expect(table.lookupExact('src/index.ts', 'main')).toBe('func:main');
});
it('returns undefined for unknown file', () => {
table.add('src/index.ts', 'main', 'func:main', 'Function');
expect(table.lookupExact('src/other.ts', 'main')).toBeUndefined();
});
it('returns undefined for unknown symbol name', () => {
table.add('src/index.ts', 'main', 'func:main', 'Function');
expect(table.lookupExact('src/index.ts', 'notExist')).toBeUndefined();
});
it('returns undefined for empty table', () => {
expect(table.lookupExact('src/index.ts', 'main')).toBeUndefined();
});
});
describe('lookupFuzzy', () => {
it('finds all definitions of a symbol across files', () => {
table.add('src/a.ts', 'render', 'func:a:render', 'Function');
table.add('src/b.ts', 'render', 'func:b:render', 'Method');
const results = table.lookupFuzzy('render');
expect(results).toHaveLength(2);
expect(results[0]).toEqual({ nodeId: 'func:a:render', filePath: 'src/a.ts', type: 'Function' });
expect(results[1]).toEqual({ nodeId: 'func:b:render', filePath: 'src/b.ts', type: 'Method' });
});
it('returns empty array for unknown symbol', () => {
expect(table.lookupFuzzy('nonexistent')).toEqual([]);
});
it('returns empty array for empty table', () => {
expect(table.lookupFuzzy('anything')).toEqual([]);
});
});
describe('getStats', () => {
it('returns zero counts for empty table', () => {
expect(table.getStats()).toEqual({ fileCount: 0, globalSymbolCount: 0 });
});
it('tracks unique file count correctly', () => {
table.add('src/a.ts', 'foo', 'func:foo', 'Function');
table.add('src/a.ts', 'bar', 'func:bar', 'Function');
table.add('src/b.ts', 'baz', 'func:baz', 'Function');
expect(table.getStats().fileCount).toBe(2);
});
it('tracks unique global symbol names', () => {
table.add('src/a.ts', 'foo', 'func:a:foo', 'Function');
table.add('src/b.ts', 'foo', 'func:b:foo', 'Function');
table.add('src/a.ts', 'bar', 'func:a:bar', 'Function');
// 'foo' and 'bar' are 2 unique global names
expect(table.getStats().globalSymbolCount).toBe(2);
});
});
describe('clear', () => {
it('resets all state', () => {
table.add('src/a.ts', 'foo', 'func:foo', 'Function');
table.add('src/b.ts', 'bar', 'func:bar', 'Function');
table.clear();
expect(table.getStats()).toEqual({ fileCount: 0, globalSymbolCount: 0 });
expect(table.lookupExact('src/a.ts', 'foo')).toBeUndefined();
expect(table.lookupFuzzy('foo')).toEqual([]);
});
it('allows re-adding after clear', () => {
table.add('src/a.ts', 'foo', 'func:foo', 'Function');
table.clear();
table.add('src/b.ts', 'bar', 'func:bar', 'Function');
expect(table.getStats()).toEqual({ fileCount: 1, globalSymbolCount: 1 });
});
});
});