From 6e673197d6dfcc0c40265148e182b9e86ba3e54d Mon Sep 17 00:00:00 2001 From: Anton Usmansky Date: Fri, 11 Nov 2022 16:47:17 +0300 Subject: [PATCH 1/3] feat: ability to decorate esm module name before importing it --- lib/mocha.js | 5 +++-- lib/nodejs/esm-utils.js | 26 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/mocha.js b/lib/mocha.js index 1b7101a2a7..24baab21b0 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -437,7 +437,7 @@ Mocha.prototype.loadFiles = function (fn) { * .then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0)) * .catch(() => process.exitCode = 1); */ -Mocha.prototype.loadFilesAsync = function () { +Mocha.prototype.loadFilesAsync = function ({esmDecorator} = {}) { var self = this; var suite = this.suite; this.lazyLoadFiles(true); @@ -450,7 +450,8 @@ Mocha.prototype.loadFilesAsync = function () { function (file, resultModule) { suite.emit(EVENT_FILE_REQUIRE, resultModule, file, self); suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self); - } + }, + esmDecorator ); }; diff --git a/lib/nodejs/esm-utils.js b/lib/nodejs/esm-utils.js index 18abe81ff8..4d6e3bed0c 100644 --- a/lib/nodejs/esm-utils.js +++ b/lib/nodejs/esm-utils.js @@ -1,10 +1,12 @@ const path = require('path'); const url = require('url'); -const formattedImport = async file => { +const forward = x => x; + +const formattedImport = async (file, esmDecorator = forward) => { if (path.isAbsolute(file)) { try { - return await import(url.pathToFileURL(file)); + return await import(esmDecorator(url.pathToFileURL(file))); } catch (err) { // This is a hack created because ESM in Node.js (at least in Node v15.5.1) does not emit // the location of the syntax error in the error thrown. @@ -27,15 +29,15 @@ const formattedImport = async file => { throw err; } } - return import(file); + return import(esmDecorator(file)); }; -exports.requireOrImport = async file => { +exports.requireOrImport = async (file, esmDecorator) => { if (path.extname(file) === '.mjs') { - return formattedImport(file); + return formattedImport(file, esmDecorator); } try { - return dealWithExports(await formattedImport(file)); + return dealWithExports(await formattedImport(file, esmDecorator)); } catch (err) { if ( err.code === 'ERR_MODULE_NOT_FOUND' || @@ -85,10 +87,18 @@ function dealWithExports(module) { } } -exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => { +exports.loadFilesAsync = async ( + files, + preLoadFunc, + postLoadFunc, + esmDecorator +) => { for (const file of files) { preLoadFunc(file); - const result = await exports.requireOrImport(path.resolve(file)); + const result = await exports.requireOrImport( + path.resolve(file), + esmDecorator + ); postLoadFunc(file, result); } }; From d78504557e4f11049bd8eb5020232071ce0bb169 Mon Sep 17 00:00:00 2001 From: Anton Usmansky Date: Tue, 22 Nov 2022 18:17:50 +0300 Subject: [PATCH 2/3] fixup --- lib/mocha.js | 2 ++ lib/nodejs/esm-utils.js | 6 +++-- test/node-unit/esm-utils.spec.js | 45 ++++++++++++++++++++++++++++++++ test/node-unit/mocha.spec.js | 19 ++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/node-unit/esm-utils.spec.js diff --git a/lib/mocha.js b/lib/mocha.js index 24baab21b0..f93865df7e 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -429,6 +429,8 @@ Mocha.prototype.loadFiles = function (fn) { * @see {@link Mocha#addFile} * @see {@link Mocha#run} * @see {@link Mocha#unloadFiles} + * @param {Object} [options] - Settings object. + * @param {Function} [options.esmDecorator] - Function invoked on esm module name right before importing it. By default will passthrough as is. * @returns {Promise} * @example * diff --git a/lib/nodejs/esm-utils.js b/lib/nodejs/esm-utils.js index 4d6e3bed0c..5318099365 100644 --- a/lib/nodejs/esm-utils.js +++ b/lib/nodejs/esm-utils.js @@ -6,7 +6,7 @@ const forward = x => x; const formattedImport = async (file, esmDecorator = forward) => { if (path.isAbsolute(file)) { try { - return await import(esmDecorator(url.pathToFileURL(file))); + return await exports.doImport(esmDecorator(url.pathToFileURL(file))); } catch (err) { // This is a hack created because ESM in Node.js (at least in Node v15.5.1) does not emit // the location of the syntax error in the error thrown. @@ -29,9 +29,11 @@ const formattedImport = async (file, esmDecorator = forward) => { throw err; } } - return import(esmDecorator(file)); + return exports.doImport(esmDecorator(file)); }; +exports.doImport = async file => import(file); + exports.requireOrImport = async (file, esmDecorator) => { if (path.extname(file) === '.mjs') { return formattedImport(file, esmDecorator); diff --git a/test/node-unit/esm-utils.spec.js b/test/node-unit/esm-utils.spec.js new file mode 100644 index 0000000000..296a8ce50b --- /dev/null +++ b/test/node-unit/esm-utils.spec.js @@ -0,0 +1,45 @@ +'use strict'; + +const esmUtils = require('../../lib/nodejs/esm-utils'); +const sinon = require('sinon'); + +describe('esm-utils', function () { + beforeEach(function () { + sinon.stub(esmUtils, 'doImport').resolves({}); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('loadFilesAsync()', function () { + it('should not decorate imported module if no decorator passed', async function () { + await esmUtils.loadFilesAsync( + ['/foo/bar.mjs'], + () => {}, + () => {} + ); + + expect( + esmUtils.doImport.firstCall.args[0].toString(), + 'to be', + 'file:///foo/bar.mjs' + ); + }); + + it('should decorate imported module with passed decorator', async function () { + await esmUtils.loadFilesAsync( + ['/foo/bar.mjs'], + () => {}, + () => {}, + x => `${x}?foo=bar` + ); + + expect( + esmUtils.doImport.firstCall.args[0].toString(), + 'to be', + 'file:///foo/bar.mjs?foo=bar' + ); + }); + }); +}); diff --git a/test/node-unit/mocha.spec.js b/test/node-unit/mocha.spec.js index 8bf48b1b06..78991657e2 100644 --- a/test/node-unit/mocha.spec.js +++ b/test/node-unit/mocha.spec.js @@ -48,6 +48,9 @@ describe('Mocha', function () { stubs.Suite = sinon.stub().returns(stubs.suite); stubs.Suite.constants = {}; stubs.ParallelBufferedRunner = sinon.stub().returns({}); + stubs.esmUtils = { + loadFilesAsync: sinon.stub() + }; const runner = Object.assign(sinon.createStubInstance(EventEmitter), { runAsync: sinon.stub().resolves(0), globals: sinon.stub(), @@ -66,6 +69,7 @@ describe('Mocha', function () { '../../lib/suite.js': stubs.Suite, '../../lib/nodejs/parallel-buffered-runner.js': stubs.ParallelBufferedRunner, + '../../lib/nodejs/esm-utils': stubs.esmUtils, '../../lib/runner.js': stubs.Runner, '../../lib/errors.js': stubs.errors }) @@ -219,6 +223,21 @@ describe('Mocha', function () { }); }); + describe('loadFilesAsync()', function () { + it('shoud pass esmDecorator to actual load function', async function () { + const esmDecorator = x => `${x}?foo=bar`; + + await mocha.loadFilesAsync({esmDecorator}); + + expect(stubs.esmUtils.loadFilesAsync, 'was called once'); + expect( + stubs.esmUtils.loadFilesAsync.firstCall.args[3], + 'to be', + esmDecorator + ); + }); + }); + describe('unloadFiles()', function () { it('should delegate Mocha.unloadFile() for each item in its list of files', function () { mocha.files = [DUMB_FIXTURE_PATH, DUMBER_FIXTURE_PATH]; From 4db97d5656f9443fb08b4a96054214dbc79ff200 Mon Sep 17 00:00:00 2001 From: Anton Usmansky Date: Fri, 25 Nov 2022 20:17:45 +0300 Subject: [PATCH 3/3] fix tests for windows --- test/node-unit/esm-utils.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/node-unit/esm-utils.spec.js b/test/node-unit/esm-utils.spec.js index 296a8ce50b..8880b5bceb 100644 --- a/test/node-unit/esm-utils.spec.js +++ b/test/node-unit/esm-utils.spec.js @@ -2,6 +2,7 @@ const esmUtils = require('../../lib/nodejs/esm-utils'); const sinon = require('sinon'); +const url = require('url'); describe('esm-utils', function () { beforeEach(function () { @@ -23,7 +24,7 @@ describe('esm-utils', function () { expect( esmUtils.doImport.firstCall.args[0].toString(), 'to be', - 'file:///foo/bar.mjs' + url.pathToFileURL('/foo/bar.mjs').toString() ); }); @@ -38,7 +39,7 @@ describe('esm-utils', function () { expect( esmUtils.doImport.firstCall.args[0].toString(), 'to be', - 'file:///foo/bar.mjs?foo=bar' + `${url.pathToFileURL('/foo/bar.mjs').toString()}?foo=bar` ); }); });