UNPKG

apisurf

Version:

Analyze API surface changes between npm package versions to catch breaking changes

149 lines (147 loc) 7.02 kB
import { describe, expect, it } from '@jest/globals'; import { parseCommonJSStatically } from './parseCommonJSStatically.js'; describe('parseCommonJSStatically', () => { it('should parse direct exports (exports.foo = bar)', () => { const sourceContent = ` exports.myFunction = function() {}; exports.MY_CONSTANT = 42; exports.defaultConfig = { enabled: true }; `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.namedExports.has('myFunction')).toBe(true); expect(result.namedExports.has('MY_CONSTANT')).toBe(true); expect(result.namedExports.has('defaultConfig')).toBe(true); expect(result.defaultExport).toBe(false); expect(result.packageName).toBe('test-package'); expect(result.version).toBe('1.0.0'); }); it('should parse computed exports (exports["foo"] = bar)', () => { const sourceContent = ` exports["computed-name"] = function() {}; exports['another-export'] = 123; exports[\`template-string\`] = "value"; `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.namedExports.has('computed-name')).toBe(true); expect(result.namedExports.has('another-export')).toBe(true); expect(result.namedExports.has('template-string')).toBe(false); // Template strings not supported by regex expect(result.namedExports.size).toBe(2); }); it('should parse Object.defineProperty exports', () => { const sourceContent = ` Object.defineProperty(exports, 'propertyExport', { value: function() {}, enumerable: true }); Object.defineProperty(exports, "anotherProperty", { value: 42 }); `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.namedExports.has('propertyExport')).toBe(true); expect(result.namedExports.has('anotherProperty')).toBe(true); }); it('should detect module.exports default export', () => { const sourceContent = ` function MyClass() {} module.exports = MyClass; `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.defaultExport).toBe(true); expect(result.namedExports.size).toBe(0); }); it('should parse __exportStar calls (bundler generated)', () => { const sourceContent = ` __exportStar(require("./internal-module"), exports); __exportStar(require('./another-module'), exports); `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.starExports).toContain('./internal-module'); expect(result.starExports).toContain('./another-module'); expect(result.starExports.length).toBe(2); }); it('should skip comments and empty lines', () => { const sourceContent = ` // This is a comment /* Multi-line comment */ exports.realExport = function() {}; // exports.commentedExport = 'should be ignored'; /* exports.multiLineCommentExport = 'also ignored'; */ `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.namedExports.has('realExport')).toBe(true); expect(result.namedExports.has('commentedExport')).toBe(false); expect(result.namedExports.has('multiLineCommentExport')).toBe(false); expect(result.namedExports.size).toBe(1); }); it('should handle mixed export patterns', () => { const sourceContent = ` exports.directExport = 'value'; exports["computed"] = 42; Object.defineProperty(exports, 'defined', { value: true }); __exportStar(require('./utils'), exports); module.exports = { mixed: true }; `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.namedExports.has('directExport')).toBe(true); expect(result.namedExports.has('computed')).toBe(true); expect(result.namedExports.has('defined')).toBe(true); expect(result.starExports).toContain('./utils'); expect(result.defaultExport).toBe(true); }); it('should return correct package metadata', () => { const sourceContent = 'exports.test = true;'; const result = parseCommonJSStatically(sourceContent, 'my-package', '2.1.0'); expect(result.packageName).toBe('my-package'); expect(result.version).toBe('2.1.0'); expect(result.typeDefinitions?.size).toBe(0); }); it('should handle exports with special characters in names', () => { const sourceContent = ` exports._privateExport = 'value'; exports.$jquery = function() {}; exports.name123 = true; `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); expect(result.namedExports.has('_privateExport')).toBe(true); expect(result.namedExports.has('$jquery')).toBe(true); expect(result.namedExports.has('name123')).toBe(true); }); it('should not parse invalid export patterns', () => { const sourceContent = ` // These should not be parsed as exports var exports = {}; someObject.exports.notAnExport = true; exports['with spaces'] = 'invalid'; exports[123] = 'numeric key'; exports. = 'invalid syntax'; `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); // Actually, exports['with spaces'] is a valid CommonJS export expect(result.namedExports.size).toBe(1); expect(result.namedExports.has('with spaces')).toBe(true); }); it('should handle module.exports with various patterns', () => { const sourceContent1 = 'module.exports = function() {};'; const result1 = parseCommonJSStatically(sourceContent1, 'test', '1.0.0'); expect(result1.defaultExport).toBe(true); const sourceContent2 = 'module.exports = {};'; const result2 = parseCommonJSStatically(sourceContent2, 'test', '1.0.0'); expect(result2.defaultExport).toBe(true); const sourceContent3 = 'module.exports.notDefault = true;'; const result3 = parseCommonJSStatically(sourceContent3, 'test', '1.0.0'); expect(result3.defaultExport).toBe(false); }); it('should handle exports inside conditional blocks (but still parse them)', () => { const sourceContent = ` if (process.env.NODE_ENV === 'production') { exports.prodOnly = true; } else { exports.devOnly = true; } `; const result = parseCommonJSStatically(sourceContent, 'test-package', '1.0.0'); // Static analysis will find both even though only one would be exported at runtime expect(result.namedExports.has('prodOnly')).toBe(true); expect(result.namedExports.has('devOnly')).toBe(true); }); });