UNPKG

archunit

Version:

ArchUnit TypeScript is an architecture testing library, to specify and assert architecture rules in your TypeScript app

459 lines 17.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const distance_1 = require("./distance"); const util_1 = require("../../common/util"); /* eslint-disable @typescript-eslint/no-explicit-any */ jest.mock('../../common/util'); describe('Distance Metrics', () => { const mockCountDeclarations = util_1.countDeclarations; beforeEach(() => { jest.clearAllMocks(); }); describe('Abstractness', () => { const abstractness = new distance_1.Abstractness(); it('should have correct name and description', () => { expect(abstractness.name).toBe('Abstractness'); expect(abstractness.description).toContain('ratio of abstract elements'); }); it('should return 0 when sourceFile has no declarations', () => { const analysisResult = { filePath: 'test.ts', sourceFile: {}, classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 0, totalTypes: 0, dependencies: { efferentCoupling: 0, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; mockCountDeclarations.mockReturnValue({ total: 0, interfaces: 0, abstractClasses: 0, abstractMethods: 0, }); expect(abstractness.calculateForFile(analysisResult)).toBe(0); }); it('should calculate abstractness correctly with sourceFile', () => { const analysisResult = { filePath: 'test.ts', sourceFile: {}, classes: [], interfaces: 2, abstractClasses: 1, concreteClasses: 7, totalTypes: 10, dependencies: { efferentCoupling: 0, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; mockCountDeclarations.mockReturnValue({ total: 20, interfaces: 2, abstractClasses: 1, abstractMethods: 2, }); expect(abstractness.calculateForFile(analysisResult)).toBe(5 / 20); }); it('should fall back to legacy calculation without sourceFile', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 2, abstractClasses: 1, concreteClasses: 7, totalTypes: 10, dependencies: { efferentCoupling: 0, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; expect(abstractness.calculateForFile(analysisResult)).toBe(3 / 10); }); it('should return 0 when totalTypes is 0 in legacy mode', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 0, totalTypes: 0, dependencies: { efferentCoupling: 0, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; expect(abstractness.calculateForFile(analysisResult)).toBe(0); }); }); describe('Instability', () => { const instability = new distance_1.Instability(); it('should have correct name and description', () => { expect(instability.name).toBe('Instability'); expect(instability.description).toContain('instability of a file'); }); it('should return 0 when there is no coupling', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 0, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; expect(instability.calculateForFile(analysisResult)).toBe(0); }); it('should calculate instability correctly', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 3, afferentCoupling: 2, outgoingDependencies: [], incomingDependencies: [], }, }; expect(instability.calculateForFile(analysisResult)).toBe(3 / 5); }); it('should return 1 when only efferent coupling exists', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 5, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; expect(instability.calculateForFile(analysisResult)).toBe(1); }); it('should return 0 when only afferent coupling exists', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 0, afferentCoupling: 5, outgoingDependencies: [], incomingDependencies: [], }, }; expect(instability.calculateForFile(analysisResult)).toBe(0); }); }); describe('DistanceFromMainSequence', () => { const distance = new distance_1.DistanceFromMainSequence(); it('should have correct name and description', () => { expect(distance.name).toBe('DistanceFromMainSequence'); expect(distance.description).toContain('Robert Martin'); }); it('should return 0 when on main sequence (A + I = 1)', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 3, abstractClasses: 0, concreteClasses: 7, totalTypes: 10, dependencies: { efferentCoupling: 7, afferentCoupling: 3, outgoingDependencies: [], incomingDependencies: [], }, }; expect(distance.calculateForFile(analysisResult)).toBeCloseTo(0, 5); }); it('should calculate distance correctly for concrete stable file', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 10, totalTypes: 10, dependencies: { efferentCoupling: 0, afferentCoupling: 10, outgoingDependencies: [], incomingDependencies: [], }, }; expect(distance.calculateForFile(analysisResult)).toBe(1); }); it('should calculate distance correctly for abstract unstable file', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 10, abstractClasses: 0, concreteClasses: 0, totalTypes: 10, dependencies: { efferentCoupling: 10, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; expect(distance.calculateForFile(analysisResult)).toBe(1); }); }); describe('CouplingFactor', () => { const coupling = new distance_1.CouplingFactor(); it('should have correct name and description', () => { expect(coupling.name).toBe('CouplingFactor'); expect(coupling.description).toContain('tightly coupled'); }); it('should return 0 when no coupling exists', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 0, afferentCoupling: 0, outgoingDependencies: [], incomingDependencies: [], }, }; expect(coupling.calculateForFile(analysisResult)).toBe(0); }); it('should calculate coupling factor with totalFiles', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 3, afferentCoupling: 2, outgoingDependencies: [], incomingDependencies: [], }, }; expect(coupling.calculateForFile(analysisResult, 11)).toBe(0.5); }); it('should use default normalization without totalFiles', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 3, afferentCoupling: 2, outgoingDependencies: [], incomingDependencies: [], }, }; expect(coupling.calculateForFile(analysisResult)).toBe(0.5); }); it('should cap at 1 for excessive coupling', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 10, afferentCoupling: 10, outgoingDependencies: [], incomingDependencies: [], }, }; expect(coupling.calculateForFile(analysisResult, 5)).toBe(1); }); it('should return 0 when totalFiles is 1', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 3, afferentCoupling: 2, outgoingDependencies: [], incomingDependencies: [], }, }; expect(coupling.calculateForFile(analysisResult, 1)).toBe(0); }); }); describe('NormalizedDistance', () => { const normalizedDistance = new distance_1.NormalizedDistance(); it('should have correct name and description', () => { expect(normalizedDistance.name).toBe('NormalizedDistance'); expect(normalizedDistance.description).toContain('size-adjusted'); }); it('should reduce distance for larger files with sourceFile', () => { const analysisResult = { filePath: 'test.ts', sourceFile: {}, classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 10, totalTypes: 10, dependencies: { efferentCoupling: 0, afferentCoupling: 10, outgoingDependencies: [], incomingDependencies: [], }, }; mockCountDeclarations.mockReturnValue({ total: 50, interfaces: 0, abstractClasses: 0, abstractMethods: 0, }); const result = normalizedDistance.calculateForFile(analysisResult); expect(result).toBeLessThan(1); expect(result).toBeCloseTo(0.75, 2); }); it('should use totalTypes when sourceFile is not available', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 50, totalTypes: 50, dependencies: { efferentCoupling: 0, afferentCoupling: 10, outgoingDependencies: [], incomingDependencies: [], }, }; const result = normalizedDistance.calculateForFile(analysisResult); expect(result).toBeLessThan(1); expect(result).toBeCloseTo(0.75, 2); }); it('should cap size factor at 1 for very large files', () => { const analysisResult = { filePath: 'test.ts', sourceFile: {}, classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 200, totalTypes: 200, dependencies: { efferentCoupling: 0, afferentCoupling: 10, outgoingDependencies: [], incomingDependencies: [], }, }; mockCountDeclarations.mockReturnValue({ total: 200, interfaces: 0, abstractClasses: 0, abstractMethods: 0, }); const result = normalizedDistance.calculateForFile(analysisResult); expect(result).toBe(0.5); }); }); describe('calculateFileDistanceMetrics', () => { it('should calculate all metrics for a file', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 3, abstractClasses: 0, concreteClasses: 7, totalTypes: 10, dependencies: { efferentCoupling: 7, afferentCoupling: 3, outgoingDependencies: [], incomingDependencies: [], }, }; const result = (0, distance_1.calculateFileDistanceMetrics)(analysisResult, 20); expect(result).toMatchObject({ filePath: 'test.ts', abstractness: 0.3, instability: 0.7, distanceFromMainSequence: expect.any(Number), couplingFactor: expect.any(Number), normalizedDistance: expect.any(Number), analysisResult, }); expect(result.distanceFromMainSequence).toBeCloseTo(0, 5); }); it('should work without totalFiles parameter', () => { const analysisResult = { filePath: 'test.ts', classes: [], interfaces: 0, abstractClasses: 0, concreteClasses: 5, totalTypes: 5, dependencies: { efferentCoupling: 2, afferentCoupling: 3, outgoingDependencies: [], incomingDependencies: [], }, }; const result = (0, distance_1.calculateFileDistanceMetrics)(analysisResult); expect(result).toMatchObject({ filePath: 'test.ts', abstractness: 0, instability: 0.4, distanceFromMainSequence: 0.6, couplingFactor: 0.5, normalizedDistance: expect.any(Number), analysisResult, }); }); }); }); //# sourceMappingURL=distance.spec.js.map