const { expect } = require('chai'); const { providence } = require('../../../src/program/providence.js'); const { QueryService } = require('../../../src/program/core/QueryService.js'); const { setupAnalyzerTest } = require('../../../test-helpers/setup-analyzer-test.js'); const { mockProject, getEntry, getEntries, } = require('../../../test-helpers/mock-project-helpers.js'); const findExportsQueryConfig = QueryService.getQueryConfigFromAnalyzer('find-exports'); describe('Analyzer "find-exports"', () => { const queryResults = setupAnalyzerTest(); const _providenceCfg = { targetProjectPaths: ['/fictional/project'], // defined in mockProject }; describe('Export notations', () => { it(`supports [export const x = 0] (named specifier)`, async () => { mockProject([`export const x = 0`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].source).to.be.undefined; }); it(`supports [export default class X {}] (default export)`, async () => { mockProject([`export default class X {}`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].source).to.equal(undefined); }); it(`supports [export default fn(){}] (default export)`, async () => { mockProject([`export default x => x * 3`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].source).to.equal(undefined); }); it(`supports [export {default as x} from 'y'] (default re-export)`, async () => { mockProject({ './file-with-default-export.js': 'export default 1;', './file-with-default-re-export.js': "export { default as namedExport } from './file-with-default-export.js';", }); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0]).to.eql({ exportSpecifiers: ['[default]'], source: undefined, rootFileMap: [ { currentFileSpecifier: '[default]', rootFile: { file: '[current]', specifier: '[default]' }, }, ], }); const secondEntry = getEntry(queryResult, 1); expect(secondEntry.result[0].exportSpecifiers.length).to.equal(1); expect(secondEntry.result[0].exportSpecifiers[0]).to.equal('namedExport'); expect(secondEntry.result[0].source).to.equal('./file-with-default-export.js'); expect(secondEntry.result[0]).to.eql({ exportSpecifiers: ['namedExport'], source: './file-with-default-export.js', localMap: [{ exported: 'namedExport', local: '[default]' }], normalizedSource: './file-with-default-export.js', rootFileMap: [ { currentFileSpecifier: 'namedExport', rootFile: { file: './file-with-default-export.js', specifier: '[default]' }, }, ], }); }); it(`supports [import {x} from 'y'; export default x] (named re-export as default)`, async () => { mockProject([`import {x} from 'y'; export default x;`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].source).to.equal('y'); }); it(`supports [import x from 'y'; export default x] (default re-export as default)`, async () => { mockProject([`import x from 'y'; export default x;`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].source).to.equal('y'); }); it(`supports [export { x } from 'my/source'] (re-export named specifier)`, async () => { mockProject([`export { x } from 'my/source'`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('x'); expect(firstEntry.result[0].source).to.equal('my/source'); }); it(`supports [export { x as y } from 'my/source'] (re-export renamed specifier)`, async () => { mockProject([`export { x as y } from 'my/source'`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); expect(firstEntry.result[0].source).to.equal('my/source'); }); it(`supports [export styles from './styles.css' assert { type: "css" }] (import assertions)`, async () => { mockProject({ './styles.css': '.block { display:block; };', './x.js': `export styles from './styles.css' assert { type: "css" };`, }); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('styles'); expect(firstEntry.result[0].source).to.equal('./styles.css'); expect(firstEntry.result[0].rootFileMap[0]).to.eql({ currentFileSpecifier: 'styles', rootFile: { file: './styles.css', specifier: '[default]', }, }); }); it(`supports [import styles from './styles.css' assert { type: "css" }; export default styles;] (import assertions)`, async () => { mockProject({ './styles.css': '.block { display:block; };', './x.js': `import styles from './styles.css' assert { type: "css" }; export default styles;`, }); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('[default]'); expect(firstEntry.result[0].source).to.equal('./styles.css'); expect(firstEntry.result[0].rootFileMap[0]).to.eql({ currentFileSpecifier: '[default]', rootFile: { file: './styles.css', specifier: '[default]', }, }); }); it(`stores meta info(local name) of renamed specifiers`, async () => { mockProject([`export { x as y } from 'my/source'`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); // This info will be relevant later to identify 'transitive' relations expect(firstEntry.result[0].localMap).to.eql([ { local: 'x', exported: 'y', }, ]); }); it(`supports [export { x, y } from 'my/source'] (multiple re-exported named specifiers)`, async () => { mockProject([`export { x, y } from 'my/source'`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(2); expect(firstEntry.result[0].exportSpecifiers).to.eql(['x', 'y']); expect(firstEntry.result[0].source).to.equal('my/source'); }); it(`stores rootFileMap of an exported Identifier`, async () => { mockProject({ './src/OriginalComp.js': `export class OriginalComp {}`, './src/inbetween.js': `export { OriginalComp as InBetweenComp } from './OriginalComp.js'`, './index.js': `export { InBetweenComp as MyComp } from './src/inbetween.js'`, }); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); const secondEntry = getEntry(queryResult, 1); const thirdEntry = getEntry(queryResult, 2); expect(firstEntry.result[0].rootFileMap).to.eql([ { currentFileSpecifier: 'MyComp', // this is the local name in the file we track from rootFile: { file: './src/OriginalComp.js', // the file containing declaration specifier: 'OriginalComp', // the specifier that was exported in file }, }, ]); expect(secondEntry.result[0].rootFileMap).to.eql([ { currentFileSpecifier: 'InBetweenComp', rootFile: { file: './src/OriginalComp.js', specifier: 'OriginalComp', }, }, ]); expect(thirdEntry.result[0].rootFileMap).to.eql([ { currentFileSpecifier: 'OriginalComp', rootFile: { file: '[current]', specifier: 'OriginalComp', }, }, ]); }); it(`stores rootFileMap of an exported Identifier`, async () => { mockProject({ './src/reexport.js': ` // a direct default import import RefDefault from 'exporting-ref-project'; export default RefDefault; `, './index.js': ` import ExtendRefDefault from './src/reexport.js'; export default ExtendRefDefault; `, }); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].rootFileMap).to.eql([ { currentFileSpecifier: '[default]', rootFile: { file: 'exporting-ref-project', specifier: '[default]', }, }, ]); }); it(`correctly handles empty files`, async () => { // These can be encountered while scanning repos.. They should not break the code... mockProject([`// some comment here...`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers).to.eql(['[file]']); expect(firstEntry.result[0].source).to.equal(undefined); }); }); describe('Export variable types', () => { it(`classes`, async () => { mockProject([`export class X {}`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('X'); expect(firstEntry.result[0].source).to.be.undefined; }); it(`functions`, async () => { mockProject([`export function y() {}`]); await providence(findExportsQueryConfig, _providenceCfg); const queryResult = queryResults[0]; const firstEntry = getEntry(queryResult); expect(firstEntry.result[0].exportSpecifiers.length).to.equal(1); expect(firstEntry.result[0].exportSpecifiers[0]).to.equal('y'); expect(firstEntry.result[0].source).to.be.undefined; }); // ...etc? // ...TODO: create custom hooks to store meta info about types etc. }); describe('Default post processing', () => { // onlyInternalSources: false, // keepOriginalSourcePaths: false, // filterSpecifier: null, }); describe('Options', () => { // TODO: Move to dashboard it.skip(`"metaConfig.categoryConfig"`, async () => { mockProject( [ `export const foo = null`, // firstEntry `export const bar = null`, // secondEntry `export const baz = null`, // thirdEntry ], { projectName: 'my-project', filePaths: ['./foo.js', './packages/bar/test/bar.test.js', './temp/baz.js'], }, ); const findExportsCategoryQueryObj = QueryService.getQueryConfigFromAnalyzer('find-exports', { metaConfig: { categoryConfig: [ { project: 'my-project', categories: { fooCategory: localFilePath => localFilePath.startsWith('./foo'), barCategory: localFilePath => localFilePath.startsWith('./packages/bar'), testCategory: localFilePath => localFilePath.includes('/test/'), }, }, ], }, }); await providence(findExportsCategoryQueryObj, _providenceCfg); const queryResult = queryResults[0]; const [firstEntry, secondEntry, thirdEntry] = getEntries(queryResult); expect(firstEntry.meta.categories).to.eql(['fooCategory']); // not mutually exclusive... expect(secondEntry.meta.categories).to.eql(['barCategory', 'testCategory']); expect(thirdEntry.meta.categories).to.eql([]); }); }); });