diff --git a/lib/cli/config.js b/lib/cli/config.js index 9f9dfdbb31..66fcc6f987 100644 --- a/lib/cli/config.js +++ b/lib/cli/config.js @@ -24,30 +24,45 @@ const utils = require('../utils'); exports.CONFIG_FILES = [ '.mocharc.cjs', '.mocharc.js', + '.mocharc.mjs', '.mocharc.yaml', '.mocharc.yml', '.mocharc.jsonc', '.mocharc.json' ]; +/** + * Loads a CommonJS or ESM module, resolving its path if the file path is not + * relative + * + * @param {string} filepath - Module file path to load + * @returns {Object} CommonJS or ESM module + */ +const loadModule = filepath => { + let cwdFilepath; + try { + debug('parsers: load cwd-relative path: "%s"', path.resolve(filepath)); + cwdFilepath = require.resolve(path.resolve(filepath)); // evtl. throws + return require(cwdFilepath); + } catch (err) { + if (cwdFilepath) throw err; + + debug('parsers: retry load as module-relative path: "%s"', filepath); + return require(filepath); + } +}; + /** * Parsers for various config filetypes. Each accepts a filepath and * returns an object (but could throw) */ const parsers = (exports.parsers = { yaml: filepath => require('js-yaml').load(fs.readFileSync(filepath, 'utf8')), - js: filepath => { - let cwdFilepath; - try { - debug('parsers: load cwd-relative path: "%s"', path.resolve(filepath)); - cwdFilepath = require.resolve(path.resolve(filepath)); // evtl. throws - return require(cwdFilepath); - } catch (err) { - if (cwdFilepath) throw err; + js: loadModule, + mjs: filepath => { + const module = loadModule(filepath); - debug('parsers: retry load as module-relative path: "%s"', filepath); - return require(filepath); - } + return module.default ?? module; }, json: filepath => JSON.parse( @@ -73,7 +88,9 @@ exports.loadConfig = filepath => { config = parsers.yaml(filepath); } else if (ext === '.js' || ext === '.cjs') { config = parsers.js(filepath); - } else { + } else if (ext === '.mjs') { + config = parsers.mjs(filepath); + }else { config = parsers.json(filepath); } } catch (err) { diff --git a/test/integration/config.spec.js b/test/integration/config.spec.js index 8f259059d5..56f3b14fac 100644 --- a/test/integration/config.spec.js +++ b/test/integration/config.spec.js @@ -12,10 +12,12 @@ describe('config', function () { var configDir = path.join(__dirname, 'fixtures', 'config'); var js = loadConfig(path.join(configDir, 'mocharc.js')); var cjs = loadConfig(path.join(configDir, 'mocharc.cjs')); + var mjs = loadConfig(path.join(configDir, 'mocharc.mjs')); var json = loadConfig(path.join(configDir, 'mocharc.json')); var yaml = loadConfig(path.join(configDir, 'mocharc.yaml')); expect(js, 'to equal', json); expect(js, 'to equal', cjs); + expect(mjs, 'to equal', cjs); expect(json, 'to equal', yaml); }); diff --git a/test/integration/fixtures/config/mocharc.mjs b/test/integration/fixtures/config/mocharc.mjs new file mode 100644 index 0000000000..62d662c8b8 --- /dev/null +++ b/test/integration/fixtures/config/mocharc.mjs @@ -0,0 +1,9 @@ +'use strict'; + +// a comment +export default { + require: ['foo', 'bar'], + bail: true, + reporter: 'dot', + slow: 60 +}; diff --git a/test/node-unit/cli/config.spec.js b/test/node-unit/cli/config.spec.js index 1dba0fe16a..8ebf0a8da8 100644 --- a/test/node-unit/cli/config.spec.js +++ b/test/node-unit/cli/config.spec.js @@ -28,6 +28,7 @@ describe('cli/config', function () { sinon.stub(parsers, 'yaml').returns(phonyConfigObject); sinon.stub(parsers, 'json').returns(phonyConfigObject); sinon.stub(parsers, 'js').returns(phonyConfigObject); + sinon.stub(parsers, 'mjs').returns(phonyConfigObject); }); describe('when supplied a filepath with ".yaml" extension', function () { @@ -74,6 +75,17 @@ describe('cli/config', function () { }); }); + describe('when supplied a filepath with ".mjs" extension', function () { + const filepath = 'foo.mjs'; + + it('should use the MJS parser', function () { + loadConfig(filepath); + expect(parsers.mjs, 'to have calls satisfying', [ + {args: [filepath], returned: phonyConfigObject} + ]).and('was called once'); + }); + }); + describe('when supplied a filepath with ".jsonc" extension', function () { const filepath = 'foo.jsonc';