Skip to content

Commit 73bb819

Browse files
authored
feat(esm): ability to decorate ESM module name before importing it (#4945)
1 parent fc4ac58 commit 73bb819

File tree

4 files changed

+90
-10
lines changed

4 files changed

+90
-10
lines changed

lib/mocha.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,8 @@ Mocha.prototype.loadFiles = function (fn) {
429429
* @see {@link Mocha#addFile}
430430
* @see {@link Mocha#run}
431431
* @see {@link Mocha#unloadFiles}
432+
* @param {Object} [options] - Settings object.
433+
* @param {Function} [options.esmDecorator] - Function invoked on esm module name right before importing it. By default will passthrough as is.
432434
* @returns {Promise}
433435
* @example
434436
*
@@ -437,7 +439,7 @@ Mocha.prototype.loadFiles = function (fn) {
437439
* .then(() => mocha.run(failures => process.exitCode = failures ? 1 : 0))
438440
* .catch(() => process.exitCode = 1);
439441
*/
440-
Mocha.prototype.loadFilesAsync = function () {
442+
Mocha.prototype.loadFilesAsync = function ({esmDecorator} = {}) {
441443
var self = this;
442444
var suite = this.suite;
443445
this.lazyLoadFiles(true);
@@ -450,7 +452,8 @@ Mocha.prototype.loadFilesAsync = function () {
450452
function (file, resultModule) {
451453
suite.emit(EVENT_FILE_REQUIRE, resultModule, file, self);
452454
suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self);
453-
}
455+
},
456+
esmDecorator
454457
);
455458
};
456459

lib/nodejs/esm-utils.js

+20-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
const path = require('path');
22
const url = require('url');
33

4-
const formattedImport = async file => {
4+
const forward = x => x;
5+
6+
const formattedImport = async (file, esmDecorator = forward) => {
57
if (path.isAbsolute(file)) {
68
try {
7-
return await import(url.pathToFileURL(file));
9+
return await exports.doImport(esmDecorator(url.pathToFileURL(file)));
810
} catch (err) {
911
// This is a hack created because ESM in Node.js (at least in Node v15.5.1) does not emit
1012
// the location of the syntax error in the error thrown.
@@ -27,15 +29,17 @@ const formattedImport = async file => {
2729
throw err;
2830
}
2931
}
30-
return import(file);
32+
return exports.doImport(esmDecorator(file));
3133
};
3234

33-
exports.requireOrImport = async file => {
35+
exports.doImport = async file => import(file);
36+
37+
exports.requireOrImport = async (file, esmDecorator) => {
3438
if (path.extname(file) === '.mjs') {
35-
return formattedImport(file);
39+
return formattedImport(file, esmDecorator);
3640
}
3741
try {
38-
return dealWithExports(await formattedImport(file));
42+
return dealWithExports(await formattedImport(file, esmDecorator));
3943
} catch (err) {
4044
if (
4145
err.code === 'ERR_MODULE_NOT_FOUND' ||
@@ -85,10 +89,18 @@ function dealWithExports(module) {
8589
}
8690
}
8791

88-
exports.loadFilesAsync = async (files, preLoadFunc, postLoadFunc) => {
92+
exports.loadFilesAsync = async (
93+
files,
94+
preLoadFunc,
95+
postLoadFunc,
96+
esmDecorator
97+
) => {
8998
for (const file of files) {
9099
preLoadFunc(file);
91-
const result = await exports.requireOrImport(path.resolve(file));
100+
const result = await exports.requireOrImport(
101+
path.resolve(file),
102+
esmDecorator
103+
);
92104
postLoadFunc(file, result);
93105
}
94106
};

test/node-unit/esm-utils.spec.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const esmUtils = require('../../lib/nodejs/esm-utils');
4+
const sinon = require('sinon');
5+
const url = require('url');
6+
7+
describe('esm-utils', function () {
8+
beforeEach(function () {
9+
sinon.stub(esmUtils, 'doImport').resolves({});
10+
});
11+
12+
afterEach(function () {
13+
sinon.restore();
14+
});
15+
16+
describe('loadFilesAsync()', function () {
17+
it('should not decorate imported module if no decorator passed', async function () {
18+
await esmUtils.loadFilesAsync(
19+
['/foo/bar.mjs'],
20+
() => {},
21+
() => {}
22+
);
23+
24+
expect(
25+
esmUtils.doImport.firstCall.args[0].toString(),
26+
'to be',
27+
url.pathToFileURL('/foo/bar.mjs').toString()
28+
);
29+
});
30+
31+
it('should decorate imported module with passed decorator', async function () {
32+
await esmUtils.loadFilesAsync(
33+
['/foo/bar.mjs'],
34+
() => {},
35+
() => {},
36+
x => `${x}?foo=bar`
37+
);
38+
39+
expect(
40+
esmUtils.doImport.firstCall.args[0].toString(),
41+
'to be',
42+
`${url.pathToFileURL('/foo/bar.mjs').toString()}?foo=bar`
43+
);
44+
});
45+
});
46+
});

test/node-unit/mocha.spec.js

+19
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ describe('Mocha', function () {
4848
stubs.Suite = sinon.stub().returns(stubs.suite);
4949
stubs.Suite.constants = {};
5050
stubs.ParallelBufferedRunner = sinon.stub().returns({});
51+
stubs.esmUtils = {
52+
loadFilesAsync: sinon.stub()
53+
};
5154
const runner = Object.assign(sinon.createStubInstance(EventEmitter), {
5255
runAsync: sinon.stub().resolves(0),
5356
globals: sinon.stub(),
@@ -66,6 +69,7 @@ describe('Mocha', function () {
6669
'../../lib/suite.js': stubs.Suite,
6770
'../../lib/nodejs/parallel-buffered-runner.js':
6871
stubs.ParallelBufferedRunner,
72+
'../../lib/nodejs/esm-utils': stubs.esmUtils,
6973
'../../lib/runner.js': stubs.Runner,
7074
'../../lib/errors.js': stubs.errors
7175
})
@@ -219,6 +223,21 @@ describe('Mocha', function () {
219223
});
220224
});
221225

226+
describe('loadFilesAsync()', function () {
227+
it('shoud pass esmDecorator to actual load function', async function () {
228+
const esmDecorator = x => `${x}?foo=bar`;
229+
230+
await mocha.loadFilesAsync({esmDecorator});
231+
232+
expect(stubs.esmUtils.loadFilesAsync, 'was called once');
233+
expect(
234+
stubs.esmUtils.loadFilesAsync.firstCall.args[3],
235+
'to be',
236+
esmDecorator
237+
);
238+
});
239+
});
240+
222241
describe('unloadFiles()', function () {
223242
it('should delegate Mocha.unloadFile() for each item in its list of files', function () {
224243
mocha.files = [DUMB_FIXTURE_PATH, DUMBER_FIXTURE_PATH];

0 commit comments

Comments
 (0)