mr4's picture
Upload 136 files
fd8cdf5 verified
import { describe, it, expect, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { analyze } from './index';
/**
* Creates a temporary directory with TypeScript files for testing.
*/
function createTempProject(files) {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'analyze-test-'));
for (const [filePath, content] of Object.entries(files)) {
const fullPath = path.join(tmpDir, filePath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content, 'utf-8');
}
return tmpDir;
}
/**
* Recursively removes a directory.
*/
function removeTempDir(dir) {
fs.rmSync(dir, { recursive: true, force: true });
}
describe('analyze', () => {
let tmpDir;
afterEach(() => {
if (tmpDir) {
removeTempDir(tmpDir);
}
});
it('should analyze a directory with TypeScript files and return correct structure', async () => {
tmpDir = createTempProject({
'package.json': JSON.stringify({ name: 'test-project', description: 'A test' }),
'src/index.ts': `
import { helper } from './utils';
export function main() {
return helper();
}
`,
'src/utils.ts': `
export function helper() {
return 'hello';
}
export function anotherHelper() {
return 'world';
}
`,
});
const result = await analyze(tmpDir, { full: true });
// Should have a valid dashboard structure
expect(result.dashboard.version).toBe('1.0.0');
expect(result.dashboard.project.name).toBe('test-project');
expect(result.dashboard.nodes.length).toBeGreaterThan(0);
expect(result.dashboard.edges.length).toBeGreaterThan(0);
expect(result.dashboard.layers).toBeDefined();
expect(result.dashboard.tour).toBeDefined();
// Should have stats — scanner includes package.json as a data file
expect(result.stats.filesAnalyzed).toBeGreaterThanOrEqual(2);
expect(result.stats.edgesCreated).toBeGreaterThan(0);
});
it('should compute correct stats with nodesByType counts', async () => {
tmpDir = createTempProject({
'src/app.ts': `
export class AppService {
run() { return true; }
}
export function bootstrap() {
return new AppService();
}
`,
});
const result = await analyze(tmpDir, { full: true });
// Should have file nodes and function/class nodes
expect(result.stats.nodesByType['file']).toBe(1);
expect(result.stats.filesAnalyzed).toBe(1);
// Should have function and/or class nodes
const totalNodes = Object.values(result.stats.nodesByType).reduce((a, b) => a + b, 0);
expect(totalNodes).toBeGreaterThan(1); // At least file + function/class
});
it('should return empty dashboard for empty directory', async () => {
tmpDir = createTempProject({});
const result = await analyze(tmpDir, { full: true });
expect(result.dashboard.nodes).toEqual([]);
expect(result.dashboard.edges).toEqual([]);
expect(result.stats.filesAnalyzed).toBe(0);
expect(result.stats.nodesByType).toEqual({});
expect(result.stats.edgesCreated).toBe(0);
expect(result.stats.layersIdentified).toBe(0);
});
it('should detect incremental mode when meta.json exists', async () => {
tmpDir = createTempProject({
'src/index.ts': `export function main() { return 1; }`,
'.understand-anything/meta.json': JSON.stringify({
lastAnalyzedAt: '2024-01-01T00:00:00.000Z',
gitCommitHash: 'abc123nonexistent',
version: '1.0.0',
analyzedFiles: 1,
}),
});
// With full=false and a meta.json present, it should attempt incremental
// but fall back to full since git diff will fail (not a git repo)
const result = await analyze(tmpDir, { full: false });
// Should still produce a valid result (falls back to full)
expect(result.dashboard.version).toBe('1.0.0');
expect(result.stats.filesAnalyzed).toBe(1);
});
it('should force full rebuild when --full flag is set', async () => {
tmpDir = createTempProject({
'src/index.ts': `export function main() { return 1; }`,
'.understand-anything/meta.json': JSON.stringify({
lastAnalyzedAt: '2024-01-01T00:00:00.000Z',
gitCommitHash: 'abc123',
version: '1.0.0',
analyzedFiles: 1,
}),
'.understand-anything/knowledge-graph.json': JSON.stringify({
version: '1.0.0',
project: { name: 'old' },
nodes: [],
edges: [],
}),
});
// With full=true, should ignore meta.json and do full rebuild
const result = await analyze(tmpDir, { full: true });
expect(result.dashboard.version).toBe('1.0.0');
expect(result.stats.filesAnalyzed).toBe(1);
// Should have nodes from the actual file, not the empty existing graph
expect(result.dashboard.nodes.length).toBeGreaterThan(0);
});
it('should handle files that fail to parse gracefully', async () => {
tmpDir = createTempProject({
'src/good.ts': `export function hello() { return 'hi'; }`,
// Binary-like content that might cause parse issues — but our parser
// is regex-based so it won't throw. Instead test with a file that
// doesn't exist on disk (scanner won't include it).
'src/another.ts': `export const x = 42;`,
});
const result = await analyze(tmpDir, { full: true });
// Should still produce results even if some files are problematic
expect(result.stats.filesAnalyzed).toBeGreaterThan(0);
expect(result.dashboard.nodes.length).toBeGreaterThan(0);
});
it('should include layers in the output', async () => {
tmpDir = createTempProject({
'src/components/Button.tsx': `
export function Button() {
return '<button>Click</button>';
}
`,
'src/utils/format.ts': `
export function formatDate(d: Date) {
return d.toISOString();
}
`,
});
const result = await analyze(tmpDir, { full: true });
expect(result.dashboard.layers).toBeDefined();
expect(result.dashboard.layers.length).toBeGreaterThan(0);
expect(result.stats.layersIdentified).toBeGreaterThan(0);
});
});