From 1b8b91ddf2b9b1e32cef25c439696137ab699637 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Tue, 1 Jul 2025 10:34:49 -0400 Subject: [PATCH 1/7] support loading ESM configuration files --- lib/cli/config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/cli/config.js b/lib/cli/config.js index 9f9dfdbb31..5181b5040d 100644 --- a/lib/cli/config.js +++ b/lib/cli/config.js @@ -24,6 +24,7 @@ const utils = require('../utils'); exports.CONFIG_FILES = [ '.mocharc.cjs', '.mocharc.js', + '.mocharc.mjs', '.mocharc.yaml', '.mocharc.yml', '.mocharc.jsonc', @@ -64,6 +65,7 @@ const parsers = (exports.parsers = { * @returns {Object} Parsed config object */ exports.loadConfig = filepath => { + const packageJson = require(path.resolve(utils.cwd(), 'package.json')); let config = {}; debug('loadConfig: trying to parse config at %s', filepath); @@ -71,6 +73,8 @@ exports.loadConfig = filepath => { try { if (ext === '.yml' || ext === '.yaml') { config = parsers.yaml(filepath); + } else if ((ext === '.js' && packageJson.type === "module") || ext === '.mjs') { + config = parsers.js(filepath).default; } else if (ext === '.js' || ext === '.cjs') { config = parsers.js(filepath); } else { From 7613e583f6df91655263b3a8b927a408f76cb79a Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Wed, 24 Sep 2025 10:17:22 -0400 Subject: [PATCH 2/7] refactor to leverage require(esm) --- lib/cli/config.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/cli/config.js b/lib/cli/config.js index 5181b5040d..19ae085563 100644 --- a/lib/cli/config.js +++ b/lib/cli/config.js @@ -65,7 +65,6 @@ const parsers = (exports.parsers = { * @returns {Object} Parsed config object */ exports.loadConfig = filepath => { - const packageJson = require(path.resolve(utils.cwd(), 'package.json')); let config = {}; debug('loadConfig: trying to parse config at %s', filepath); @@ -73,10 +72,9 @@ exports.loadConfig = filepath => { try { if (ext === '.yml' || ext === '.yaml') { config = parsers.yaml(filepath); - } else if ((ext === '.js' && packageJson.type === "module") || ext === '.mjs') { - config = parsers.js(filepath).default; - } else if (ext === '.js' || ext === '.cjs') { - config = parsers.js(filepath); + } else if (ext === '.js' || ext === '.cjs' || ext === '.mjs') { + const parsedConfig = parsers.js(filepath); + config = parsedConfig.default ?? parsedConfig; } else { config = parsers.json(filepath); } From f9b77df08102f49fbb2ec7615bfb5bdc5064c17d Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Tue, 14 Oct 2025 10:08:06 -0400 Subject: [PATCH 3/7] add test cases --- test/integration/config.spec.js | 2 ++ test/integration/fixtures/config/mocharc.mjs | 7 +++++++ test/node-unit/cli/config.spec.js | 22 ++++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 test/integration/fixtures/config/mocharc.mjs diff --git a/test/integration/config.spec.js b/test/integration/config.spec.js index 8f259059d5..9740f09773 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', js); 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..7cbe079d2c --- /dev/null +++ b/test/integration/fixtures/config/mocharc.mjs @@ -0,0 +1,7 @@ +// 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..0dec0528f5 100644 --- a/test/node-unit/cli/config.spec.js +++ b/test/node-unit/cli/config.spec.js @@ -74,6 +74,28 @@ describe('cli/config', function () { }); }); + describe('when supplied a filepath with ".mjs" extension', function () { + const filepath = 'foo.mjs'; + + it('should use the JS parser', function () { + loadConfig(filepath); + expect(parsers.js, 'to have calls satisfying', [ + {args: [filepath], returned: phonyConfigObject} + ]).and('was called once'); + }); + }); + + describe('when supplied a filepath with an ESM ".js" extension', function () { + const filepath = 'foo.js'; + + it('should use the JS parser', function () { + loadConfig(filepath); + expect(parsers.js, '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'; From 766d6ca17b2aca8b693efcd64406d54867d50949 Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Sat, 22 Nov 2025 06:17:11 -0800 Subject: [PATCH 4/7] Cleanup "config shoud return the same values..." test --- test/integration/config.spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/integration/config.spec.js b/test/integration/config.spec.js index 76fb7e939b..a5d820941e 100644 --- a/test/integration/config.spec.js +++ b/test/integration/config.spec.js @@ -10,15 +10,15 @@ var loadConfig = require("../../lib/cli/config").loadConfig; describe("config", function () { it("should return the same values for all supported config types", function () { var configDir = path.join(__dirname, "fixtures", "config"); - var js = loadConfig(path.join(configDir, "mocharc.js")); + var js = loadConfig(path.join(configDir, "mocharc.js")); // canonical form 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 mjs = loadConfig(path.join(configDir, "mocharc.mjs")); var yaml = loadConfig(path.join(configDir, "mocharc.yaml")); - expect(js, "to equal", json); - expect(js, "to equal", cjs); + expect(cjs, "to equal", js); + expect(json, "to equal", js); expect(mjs, "to equal", js); - expect(json, "to equal", yaml); + expect(yaml, "to equal", js); }); describe('when configuring Mocha via a ".js" file', function () { From ab07f1a07fd18bdb7f8fc90b6999a01d5846ad57 Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Sat, 22 Nov 2025 06:19:15 -0800 Subject: [PATCH 5/7] Remove dupe test --- test/node-unit/cli/config.spec.js | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/test/node-unit/cli/config.spec.js b/test/node-unit/cli/config.spec.js index d1b16edd27..4d561aa428 100644 --- a/test/node-unit/cli/config.spec.js +++ b/test/node-unit/cli/config.spec.js @@ -75,24 +75,13 @@ describe("cli/config", function () { }); describe('when supplied a filepath with ".mjs" extension', function () { - const filepath = 'foo.mjs'; + const filepath = "foo.mjs"; - it('should use the JS parser', function () { - loadConfig(filepath); - expect(parsers.js, 'to have calls satisfying', [ - {args: [filepath], returned: phonyConfigObject} - ]).and('was called once'); - }); - }); - - describe('when supplied a filepath with an ESM ".js" extension', function () { - const filepath = 'foo.js'; - - it('should use the JS parser', function () { + it("should use the JS parser", function () { loadConfig(filepath); - expect(parsers.js, 'to have calls satisfying', [ - {args: [filepath], returned: phonyConfigObject} - ]).and('was called once'); + expect(parsers.js, "to have calls satisfying", [ + { args: [filepath], returned: phonyConfigObject }, + ]).and("was called once"); }); }); From 0823a91553c39db013d3267cbc1d7e4382a0bb9e Mon Sep 17 00:00:00 2001 From: Mark Wiemer Date: Sat, 22 Nov 2025 06:20:33 -0800 Subject: [PATCH 6/7] Alphabetize loadConfig when parsing succeeds tests --- test/node-unit/cli/config.spec.js | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/test/node-unit/cli/config.spec.js b/test/node-unit/cli/config.spec.js index 4d561aa428..314fbdf2fd 100644 --- a/test/node-unit/cli/config.spec.js +++ b/test/node-unit/cli/config.spec.js @@ -30,45 +30,45 @@ describe("cli/config", function () { sinon.stub(parsers, "js").returns(phonyConfigObject); }); - describe('when supplied a filepath with ".yaml" extension', function () { - const filepath = "foo.yaml"; + describe('when supplied a filepath with ".cjs" extension', function () { + const filepath = "foo.cjs"; - it("should use the YAML parser", function () { + it("should use the JS parser", function () { loadConfig(filepath); - expect(parsers.yaml, "to have calls satisfying", [ + expect(parsers.js, "to have calls satisfying", [ { args: [filepath], returned: phonyConfigObject }, ]).and("was called once"); }); }); - describe('when supplied a filepath with ".yml" extension', function () { - const filepath = "foo.yml"; + describe('when supplied a filepath with ".js" extension', function () { + const filepath = "foo.js"; - it("should use the YAML parser", function () { + it("should use the JS parser", function () { loadConfig(filepath); - expect(parsers.yaml, "to have calls satisfying", [ + expect(parsers.js, "to have calls satisfying", [ { args: [filepath], returned: phonyConfigObject }, ]).and("was called once"); }); }); - describe('when supplied a filepath with ".js" extension', function () { - const filepath = "foo.js"; + describe('when supplied a filepath with ".json" extension', function () { + const filepath = "foo.json"; - it("should use the JS parser", function () { - loadConfig(filepath); - expect(parsers.js, "to have calls satisfying", [ + it("should use the JSON parser", function () { + loadConfig("foo.json"); + expect(parsers.json, "to have calls satisfying", [ { args: [filepath], returned: phonyConfigObject }, ]).and("was called once"); }); }); - describe('when supplied a filepath with ".cjs" extension', function () { - const filepath = "foo.cjs"; + describe('when supplied a filepath with ".jsonc" extension', function () { + const filepath = "foo.jsonc"; - it("should use the JS parser", function () { - loadConfig(filepath); - expect(parsers.js, "to have calls satisfying", [ + it("should use the JSON parser", function () { + loadConfig("foo.jsonc"); + expect(parsers.json, "to have calls satisfying", [ { args: [filepath], returned: phonyConfigObject }, ]).and("was called once"); }); @@ -85,23 +85,23 @@ describe("cli/config", function () { }); }); - describe('when supplied a filepath with ".jsonc" extension', function () { - const filepath = "foo.jsonc"; + describe('when supplied a filepath with ".yaml" extension', function () { + const filepath = "foo.yaml"; - it("should use the JSON parser", function () { - loadConfig("foo.jsonc"); - expect(parsers.json, "to have calls satisfying", [ + it("should use the YAML parser", function () { + loadConfig(filepath); + expect(parsers.yaml, "to have calls satisfying", [ { args: [filepath], returned: phonyConfigObject }, ]).and("was called once"); }); }); - describe('when supplied a filepath with ".json" extension', function () { - const filepath = "foo.json"; + describe('when supplied a filepath with ".yml" extension', function () { + const filepath = "foo.yml"; - it("should use the JSON parser", function () { - loadConfig("foo.json"); - expect(parsers.json, "to have calls satisfying", [ + it("should use the YAML parser", function () { + loadConfig(filepath); + expect(parsers.yaml, "to have calls satisfying", [ { args: [filepath], returned: phonyConfigObject }, ]).and("was called once"); }); From 90f9daef230cffebbc12a63f8fbd8b39896b591c Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 22 Nov 2025 12:04:20 -0500 Subject: [PATCH 7/7] doc: document ESM configuration --- docs-next/src/content/docs/running/configuring.mdx | 3 ++- docs/index.md | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs-next/src/content/docs/running/configuring.mdx b/docs-next/src/content/docs/running/configuring.mdx index 64f8052575..e4cdb758d9 100644 --- a/docs-next/src/content/docs/running/configuring.mdx +++ b/docs-next/src/content/docs/running/configuring.mdx @@ -9,7 +9,8 @@ title: Configuring Mocha (Node.js) Mocha supports configuration files, typical of modern command-line tools, in several formats: - **JavaScript**: Create a `.mocharc.js` (or `.mocharc.cjs` when using [`"type"="module"`](/explainers/nodejs-native-esm-support) in your `package.json`) - in your project's root directory, and export an object (`module.exports = {/* ... */}`) containing your configuration. + in your project's root directory, and export an object (`module.exports = {/* ... */}`) containing your configuration. For native ESM and using `type="module"` + or using `.mjs`, use a default export (`default export {/* ... */}`). - **YAML**: Create a `.mocharc.yaml` (or `.mocharc.yml`) in your project's root directory. - **JSON**: Create a `.mocharc.json` (or `.mocharc.jsonc`) in your project's root directory. Comments--while not valid JSON--are allowed in this file, and will be ignored by Mocha. diff --git a/docs/index.md b/docs/index.md index 0e0f8c4ed0..495fce5658 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2254,7 +2254,9 @@ The HTML reporter is the default reporter when running Mocha in the browser. It Mocha supports configuration files, typical of modern command-line tools, in several formats: -- **JavaScript**: Create a `.mocharc.js` (or `.mocharc.cjs` when using [`"type"="module"`](#nodejs-native-esm-support) in your `package.json`) +- **JavaScript**: Create a `.mocharc.js` (or `.mocharc.cjs` when using [`"type"="module"`](/explainers/nodejs-native-esm-support) in your `package.json`) + in your project's root directory, and export an object (`module.exports = {/* ... */}`) containing your configuration. For native ESM and using `type="module"` + or using `.mjs`, use a default export (`default export {/* ... */}`). in your project's root directory, and export an object (`module.exports = {/* ... */}`) containing your configuration. - **YAML**: Create a `.mocharc.yaml` (or `.mocharc.yml`) in your project's root directory. - **JSON**: Create a `.mocharc.json` (or `.mocharc.jsonc`) in your project's root directory. Comments — while not valid JSON — are allowed in this file, and will be ignored by Mocha.