Spaces:
Running
Running
| 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); | |
| }); | |
| }); | |