diff --git a/test/integration.test.ts b/test/integration.test.ts new file mode 100644 index 0000000..2dc1657 --- /dev/null +++ b/test/integration.test.ts @@ -0,0 +1,205 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { TypeParser, createTypeParser } from '../src/parsers/typeParser'; +import { DependencyGraphBuilder, createDependencyGraphBuilder } from '../src/analyzers/dependencyGraph'; +import { detectCircularDependencies } from '../src/analyzers/circularDetector'; + +describe('Integration Tests', () => { + const fixturesDir = path.join(__dirname, 'fixtures'); + + beforeAll(() => { + if (!fs.existsSync(fixturesDir)) { + fs.mkdirSync(fixturesDir, { recursive: true }); + } + }); + + describe('Real-world TypeScript parsing', () => { + it('should parse complex interfaces with generics', () => { + const source = ` + interface ApiResponse { + data: T; + status: number; + message: string; + timestamp: Date; + } + + interface User { + id: number; + name: string; + email: string; + } + + type UserApiResponse = ApiResponse; + `; + + const parser = createTypeParser(); + const result = parser.parse(source, 'test.ts'); + + expect(result.types.length).toBeGreaterThanOrEqual(2); + }); + + it('should parse conditional types', () => { + const source = ` + type IfEquals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? A : B; + + type DeepPartial = { + [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; + }; + `; + + const parser = createTypeParser(); + const result = parser.parse(source, 'test.ts'); + + expect(result.types.length).toBe(2); + }); + + it('should parse mapped types', () => { + const source = ` + type Readonly = { + readonly [P in keyof T]: T[P]; + }; + + type Partial = { + [P in keyof T]?: T[P]; + }; + `; + + const parser = createTypeParser(); + const result = parser.parse(source, 'test.ts'); + + expect(result.types.length).toBe(2); + }); + + it('should parse template literal types', () => { + const source = ` + type EventName = \`on\${string}\`; + type Color = 'red' | 'green' | 'blue'; + `; + + const parser = createTypeParser(); + const result = parser.parse(source, 'test.ts'); + + expect(result.types.length).toBe(2); + }); + }); + + describe('Circular Dependency Detection Integration', () => { + it('should detect circular dependencies in a chain', () => { + const files = [ + { + filePath: '/test/a.ts', + types: [ + { name: 'A', filePath: '/test/a.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['B'], rawNode: undefined } + ], + imports: [], + errors: [] + }, + { + filePath: '/test/b.ts', + types: [ + { name: 'B', filePath: '/test/b.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['C'], rawNode: undefined } + ], + imports: [], + errors: [] + }, + { + filePath: '/test/c.ts', + types: [ + { name: 'C', filePath: '/test/c.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['A'], rawNode: undefined } + ], + imports: [], + errors: [] + } + ]; + + const builder = createDependencyGraphBuilder(); + const graph = builder.build(files, '/test'); + const cycles = detectCircularDependencies(graph); + + expect(cycles.length).toBeGreaterThan(0); + expect(cycles[0].nodes).toContain('/test/a.ts#A'); + expect(cycles[0].nodes).toContain('/test/b.ts#B'); + expect(cycles[0].nodes).toContain('/test/c.ts#C'); + }); + + it('should handle multiple independent circular dependencies', () => { + const files = [ + { + filePath: '/test/a.ts', + types: [ + { name: 'A', filePath: '/test/a.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['B'], rawNode: undefined } + ], + imports: [], + errors: [] + }, + { + filePath: '/test/b.ts', + types: [ + { name: 'B', filePath: '/test/b.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['A'], rawNode: undefined } + ], + imports: [], + errors: [] + }, + { + filePath: '/test/c.ts', + types: [ + { name: 'C', filePath: '/test/c.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['D'], rawNode: undefined } + ], + imports: [], + errors: [] + }, + { + filePath: '/test/d.ts', + types: [ + { name: 'D', filePath: '/test/d.ts', startLine: 1, endLine: 5, kind: 'interface' as const, dependencies: ['C'], rawNode: undefined } + ], + imports: [], + errors: [] + } + ]; + + const builder = createDependencyGraphBuilder(); + const graph = builder.build(files, '/test'); + const cycles = detectCircularDependencies(graph); + + expect(cycles.length).toBe(2); + }); + }); + + describe('Full Analysis Pipeline', () => { + it('should parse and build graph for complex type hierarchy', () => { + const source = ` + interface Animal { + name: string; + } + + interface Dog extends Animal { + breed: string; + } + + interface Cat extends Animal { + color: string; + } + + type Pet = Dog | Cat; + `; + + const parser = createTypeParser(); + const parsedFiles = parser.parse(source, 'test.ts'); + + const builder = createDependencyGraphBuilder(); + const graph = builder.build([parsedFiles], '/test'); + + expect(graph.nodes.size).toBe(4); + expect(graph.edges.length).toBeGreaterThanOrEqual(2); + + const dogNode = graph.nodes.get('test.ts#Dog'); + expect(dogNode).toBeDefined(); + + const petNode = graph.nodes.get('test.ts#Pet'); + expect(petNode).toBeDefined(); + }); + }); +});