import { BIT_TEMP_ROOT } from '@teambit/defender.fs.global-bit-temp-dir'; import fs from 'fs-extra'; import assert from 'assert'; import path from 'path'; import rewire from 'rewire'; import sinon from 'sinon'; import type { DependencyDetector } from '@teambit/dependency-resolver'; const UNIT_TEST_DIR = path.join(BIT_TEMP_ROOT, 'unit-test'); const cabinetNonDefault = rewire('./'); const cabinet = cabinetNonDefault.default; const fixtures = path.resolve(`${__dirname}/../fixtures/filing-cabinet`); // eslint-disable-next-line import/no-dynamic-require, global-require const mockedFiles = require(`${fixtures}/mockedJSFiles`); // eslint-disable-next-line import/no-dynamic-require, global-require const mockAST = require(`${fixtures}/ast`); function mockfs(obj: any, acc?: string) { Object.entries(obj).forEach(([key, value]) => { if (typeof value === 'string') { const filePath = acc ? path.join(acc, key) : key; const fullFilePath = path.join(UNIT_TEST_DIR, filePath); const content = value; return fs.outputFileSync(fullFilePath, content); } mockfs(value, acc ? path.join(acc, key) : key); return undefined; }); } function cleanUnitDir() { fs.removeSync(UNIT_TEST_DIR); } // try { // // eslint-disable-next-line global-require // require('module-lookup-amd'); // } catch (err: any) { // // eslint-disable-next-line no-console // console.log(`mocha suppresses the error, so console.error is needed to show the error on the screen. // the problem is with module-lookup-amd that calls requirejs package and uses rewire package. // see https://github.com/jhnns/rewire/issues/178 for more details. // the error occurs since node v12.16.0. for the time being, to run the tests, use an earlier version. // `); // // eslint-disable-next-line no-console // console.error(err); // throw err; // } describe('filing-cabinet', () => { describe('JavaScript', () => { beforeEach(() => { mockfs(mockedFiles); }); afterEach(() => { cleanUnitDir(); }); it('uses a generic resolve for unsupported file extensions', () => { const resolvedFile = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/commonjs/foo.baz`, directory: `${UNIT_TEST_DIR}/js/commonjs/`, }); assert.ok(resolvedFile.endsWith('bar.baz')); }); describe('when given an ast for a JS file', () => { it('reuses the ast when trying to determine the module type', () => { const ast = {}; const result = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/es6/foo.js`, directory: `${UNIT_TEST_DIR}/js/es6/`, ast, }); assert.ok(result.endsWith(path.normalize('es6/bar.js'))); }); it('resolves the dependency successfully', () => { const result = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/es6/foo.js`, directory: `${UNIT_TEST_DIR}/js/es6/`, ast: mockAST, }); assert.equal(result, path.join(UNIT_TEST_DIR, 'js/es6/bar.js')); }); }); describe('when not given an ast', () => { it('uses the filename to look for the module type', () => { const options = { dependency: './bar', filename: `${UNIT_TEST_DIR}/js/es6/foo.js`, directory: `${UNIT_TEST_DIR}/js/es6/`, }; const result = cabinet(options); assert.equal(result, path.join(UNIT_TEST_DIR, 'js/es6/bar.js')); }); }); describe('es6', () => { // TODO: commonJSLookup is not able to be stubbed after the revamp, but keep the test case temporarily for reference it.skip('assumes commonjs for es6 modules with no requirejs/webpack config', () => { const stub = sinon.stub(); const revert = cabinetNonDefault.__set__('commonJSLookup', stub); cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/es6/foo.js`, directory: `${UNIT_TEST_DIR}/js/es6/`, }); assert.ok(stub.called); revert(); }); }); describe('jsx', () => { it('resolves files with the .jsx extension', () => { const result = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/es6/foo.jsx`, directory: `${UNIT_TEST_DIR}/js/es6/`, }); assert.equal(result, `${path.join(UNIT_TEST_DIR, 'js/es6/bar.js')}`); }); }); // describe('amd', () => { // it('uses the amd resolver', () => { // const resolvedFile = cabinet({ // dependency: './bar', // filename: `${UNIT_TEST_DIR}/js/amd/foo.js`, // directory: `${UNIT_TEST_DIR}/js/amd/`, // }); // assert.ok(resolvedFile.endsWith('amd/bar.js')); // }); // // skipped as part of lazy loading fix. not seems to be super helpful test // it.skip('passes along arguments', () => { // const stub = sinon.stub(); // const revert = cabinet.__set__('amdLookup', stub); // const config = { baseUrl: 'js' }; // cabinet({ // dependency: 'bar', // config, // configPath: 'config.js', // filename: `${UNIT_TEST_DIR}/js/amd/foo.js`, // directory: `${UNIT_TEST_DIR}/js/amd/`, // }); // const args = stub.getCall(0).args[0]; // assert.equal(args.dependency, 'bar'); // assert.equal(args.config, config); // assert.equal(args.configPath, 'config.js'); // assert.equal(args.filename, 'js/amd/foo.js'); // assert.equal(args.directory, 'js/amd/'); // assert.ok(stub.called); // revert(); // }); // }); describe('commonjs', () => { // TODO: commonJSLookup is not able to be stubbed after the revamp, but keep the test case temporarily for reference it.skip("uses require's resolver", () => { const stub = sinon.stub(); const revert = cabinetNonDefault.__set__('commonJSLookup', stub); cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/commonjs/foo.js`, directory: `${UNIT_TEST_DIR}/js/commonjs/`, }); assert.ok(stub.called); revert(); }); it('returns an empty string for an unresolved module', () => { const result = cabinet({ dependency: 'foobar', filename: `${UNIT_TEST_DIR}/js/commonjs/foo.js`, directory: `${UNIT_TEST_DIR}/js/commonjs/`, }); assert.equal(result, ''); }); it('adds the directory to the require resolution paths', () => { const directory = `${UNIT_TEST_DIR}/js/commonjs/`; cabinet({ dependency: 'foobar', filename: `${UNIT_TEST_DIR}/js/commonjs/foo.js`, directory, }); assert.ok( require.main!.paths.some(function (p) { return p.indexOf(path.normalize(directory)) !== -1; }) ); }); it('resolves a relative dependency about the filename', () => { const directory = `${UNIT_TEST_DIR}/js/commonjs/`; const filename = `${directory}foo.js`; const result = cabinet({ dependency: './bar', filename, directory, }); assert.equal(result, path.join(path.resolve(directory), 'bar.js')); }); it("resolves a .. dependency to its parent directory's index.js file", () => { const directory = `${UNIT_TEST_DIR}/js/commonjs/`; const filename = `${directory}subdir/module.js`; const result = cabinet({ dependency: '../', filename, directory, }); assert.equal(result, path.join(path.resolve(directory), 'index.js')); }); // @todo: fix it.skip('resolves a dependency within a directory outside of the given file', () => { const directory = `${UNIT_TEST_DIR}/js/commonjs/`; const filename = `${directory}test/index.spec.js`; const result = cabinet({ dependency: 'subdir', filename, directory, }); assert.equal(result, path.join(path.resolve(directory), 'subdir/index.js')); }); // @todo: fix it.skip('resolves a node module with module entry in package.json', () => { const directory = `${UNIT_TEST_DIR}/js/commonjs/`; const filename = `${directory}module.entry.js`; const result = cabinet({ dependency: 'module.entry', filename, directory, nodeModulesConfig: { entry: 'module', }, }); assert.equal( result, path.join(path.resolve(directory), '..', 'node_modules', 'module.entry', 'index.module.js') ); }); it('resolves a nested module', () => { const directory = `${UNIT_TEST_DIR}/js/node_modules/nested/`; const filename = `${directory}index.js`; const result = cabinet({ dependency: 'lodash.assign', filename, directory, }); assert.equal(result, path.join(path.resolve(directory), 'node_modules', 'lodash.assign', 'index.js')); }); it('resolves to the index.js file of a directory', () => { const directory = `${UNIT_TEST_DIR}/js/withIndex`; const filename = `${directory}/index.js`; const result = cabinet({ dependency: './subdir', filename, directory, }); assert.equal(result, path.normalize(`${path.resolve(directory)}/subdir/index.js`)); }); it('resolves implicit .jsx requires', () => { const result = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/cjs/foo.js`, directory: `${UNIT_TEST_DIR}/js/cjs/`, }); assert.equal(result, `${path.join(UNIT_TEST_DIR, 'js/cjs/bar.jsx')}`); }); it('resolves implicit .scss requires', () => { const result = cabinet({ dependency: './baz', filename: `${UNIT_TEST_DIR}/js/cjs/foo.js`, directory: `${UNIT_TEST_DIR}/js/cjs/`, }); assert.equal(result, `${path.join(UNIT_TEST_DIR, 'js/cjs/baz.scss')}`); }); it('resolves implicit .json requires', () => { const result = cabinet({ dependency: './pkg', filename: `${UNIT_TEST_DIR}/js/cjs/foo.js`, directory: `${UNIT_TEST_DIR}/js/cjs/`, }); assert.equal(result, `${path.join(UNIT_TEST_DIR, 'js/cjs/pkg.json')}`); }); }); describe('typescript', () => { it('resolves an import', () => { const directory = `${UNIT_TEST_DIR}/js/ts`; const filename = `${directory}/index.ts`; const result = cabinet({ dependency: './foo', filename, directory, }); assert.equal(result, path.join(path.resolve(directory), 'foo.ts')); }); describe('when a dependency does not exist', () => { it('returns an empty result', () => { const directory = `${UNIT_TEST_DIR}/js/ts`; const filename = `${directory}/index.ts`; const result = cabinet({ dependency: './barbar', filename, directory, }); assert.equal(result, ''); }); }); }); }); describe('CSS', () => { beforeEach(() => { mockfs({ stylus: { 'foo.styl': '', 'bar.styl': '', }, sass: { 'foo.scss': '', 'bar.scss': '', 'foo.sass': '', 'bar.sass': '', }, less: { 'foo.less': '', 'bar.less': '', 'bar.css': '', }, }); // mockJSDir = path.resolve(__dirname, '../'); }); afterEach(() => { cleanUnitDir(); }); describe('sass', () => { it('uses the sass resolver for .scss files', () => { const result = cabinet({ dependency: 'bar', filename: `${UNIT_TEST_DIR}/sass/foo.scss`, directory: `${UNIT_TEST_DIR}/sass/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/sass/bar.scss`)); }); it('uses the sass resolver for .sass files', () => { const result = cabinet({ dependency: 'bar', filename: `${UNIT_TEST_DIR}/sass/foo.sass`, directory: `${UNIT_TEST_DIR}/sass/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/sass/bar.sass`)); }); }); describe('stylus', () => { it('uses the stylus resolver', () => { const result = cabinet({ dependency: 'bar', filename: `${UNIT_TEST_DIR}/stylus/foo.styl`, directory: `${UNIT_TEST_DIR}/stylus/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/stylus/bar.styl`)); }); }); describe('less', () => { it('resolves extensionless dependencies', () => { const result = cabinet({ dependency: 'bar', filename: `${UNIT_TEST_DIR}/less/foo.less`, directory: `${UNIT_TEST_DIR}/less/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/less/bar.less`)); }); it('resolves dependencies with a less extension', () => { const result = cabinet({ dependency: 'bar.less', filename: `${UNIT_TEST_DIR}/less/foo.less`, directory: `${UNIT_TEST_DIR}/less/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/less/bar.less`)); }); it('resolves dependencies with a css extension', () => { const result = cabinet({ dependency: 'bar.css', filename: `${UNIT_TEST_DIR}/less/foo.less`, directory: `${UNIT_TEST_DIR}/less/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/less/bar.css`)); }); }); }); describe('unrecognized extension', () => { it('uses a generic resolve for unsupported file extensions', () => { const result = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/barbazim/foo.baz`, directory: `${UNIT_TEST_DIR}/barbazim/`, }); assert.equal(result, path.normalize(`${UNIT_TEST_DIR}/barbazim/bar.baz`)); }); }); describe('custom env lookups', () => { it('supports passing env detectors', () => { const detector: DependencyDetector = { detect: (fileContent: string) => { return fileContent.indexOf('foo') === -1 ? [] : ['foo']; }, isSupported: ({ ext }) => { return ext === '.foo'; }, dependencyLookup: ({ dependency }) => { return `/xyz/${dependency}.baz`; }, type: 'foo', }; const result = cabinet({ directory: `${UNIT_TEST_DIR}/barbazim/`, filename: `${UNIT_TEST_DIR}/barbazim/xxx.foo`, ext: '.foo', dependency: 'bar', envDetectors: [detector], }); assert.equal(result, '/xyz/bar.baz'); }); }); describe('.register', () => { it('registers a custom resolver for a given extension', () => { const stub = sinon.stub().returns('foo.foobar'); cabinetNonDefault.register('.foobar', stub); const pathResult = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/amd/foo.foobar`, directory: `${UNIT_TEST_DIR}/js/amd/`, }); assert.ok(stub.called); assert.equal(pathResult, 'foo.foobar'); }); it('allows does not break default resolvers', () => { mockfs({ stylus: { 'foo.styl': '', 'bar.styl': '', }, }); const stub = sinon.stub().returns('foo'); cabinetNonDefault.register('.foobar', stub); cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/amd/foo.foobar`, directory: `${UNIT_TEST_DIR}/js/amd/`, }); const result = cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/stylus/foo.styl`, directory: `${UNIT_TEST_DIR}/stylus/`, }); assert.ok(stub.called); assert.ok(result); cleanUnitDir(); }); it('can be called multiple times', () => { const stub = sinon.stub().returns('foo'); const stub2 = sinon.stub().returns('foo'); cabinetNonDefault.register('.foobar', stub); cabinetNonDefault.register('.barbar', stub2); cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/amd/foo.foobar`, directory: `${UNIT_TEST_DIR}/js/amd/`, }); cabinet({ dependency: './bar', filename: `${UNIT_TEST_DIR}/js/amd/foo.barbar`, directory: `${UNIT_TEST_DIR}/js/amd/`, }); assert.ok(stub.called); assert.ok(stub2.called); }); it('does not add redundant extensions to supportedFileExtensions', () => { const stub = sinon.stub; const newExt = '.foobar'; cabinetNonDefault.register(newExt, stub); cabinetNonDefault.register(newExt, stub); const { supportedFileExtensions } = cabinetNonDefault; assert.equal(supportedFileExtensions.indexOf(newExt), supportedFileExtensions.lastIndexOf(newExt)); }); }); // @todo: fix. describe.skip('webpack', () => { let directory; beforeEach(() => { directory = path.resolve(__dirname, '../../../'); }); function testResolution(dependency, expected) { const resolved = cabinet({ dependency, filename: `${__dirname}/index.js`, directory, webpackConfig: `${fixtures}/webpack.config.js`, }); assert.equal(resolved, path.normalize(expected)); } it('resolves an aliased path', () => { testResolution('R', `${directory}/node_modules/resolve/index.js`); }); it('resolves a non-aliased path', () => { testResolution('resolve', `${directory}/node_modules/resolve/index.js`); }); it('resolves a relative path', () => { testResolution('./test/ast', `${fixtures}/test/ast.js`); }); it('resolves an absolute path from a file within a subdirectory', () => { const resolved = cabinet({ dependency: 'R', filename: `${fixtures}/test/ast.js`, directory, webpackConfig: `${fixtures}/webpack.config.js`, }); assert.equal(resolved, `${directory}/node_modules/resolve/index.js`); }); it('resolves a path using resolve.root', () => { const resolved = cabinet({ dependency: 'mod1', filename: `${directory}/index.js`, directory, webpackConfig: `${directory}/webpack-root.config.js`, }); assert.equal(resolved, `${directory}/test/root1/mod1.js`); }); it('resolves NPM module when using resolve.root', () => { const resolved = cabinet({ dependency: 'resolve', filename: `${directory}/index.js`, directory, webpackConfig: `${directory}/webpack-root.config.js`, }); assert.equal(resolved, `${directory}/node_modules/resolve/index.js`); }); it('resolves NPM module when using resolve.modulesDirectories', () => { const resolved = cabinet({ dependency: 'resolve', filename: `${directory}/index.js`, directory, webpackConfig: `${directory}/webpack-root.config.js`, }); assert.equal(resolved, `${directory}/node_modules/resolve/index.js`); }); it('resolves a path using resolve.modulesDirectories', () => { const resolved = cabinet({ dependency: 'mod2', filename: `${directory}/index.js`, directory, webpackConfig: `${directory}/webpack-root.config.js`, }); assert.equal(resolved, `${directory}/test/root2/mod2.js`); }); it('resolves a path using webpack config that exports a function', () => { const resolved = cabinet({ dependency: 'R', filename: `${directory}/index.js`, directory, webpackConfig: `${directory}/webpack-env.config.js`, }); assert.equal(resolved, `${directory}/node_modules/resolve/index.js`); }); it('resolves files with a .jsx extension', () => { testResolution('./test/foo.jsx', `${directory}/test/foo.jsx`); }); describe('when the dependency contains a loader', () => { it('still works', () => { testResolution('hgn!resolve', `${directory}/node_modules/resolve/index.js`); }); }); }); });