From 253100dc0172199d30ca7231e8c79a7155af5fd1 Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Wed, 13 Mar 2019 17:07:09 -0700 Subject: [PATCH] fix extension handling; closes #3808 --- lib/cli/options.js | 29 +++++++++- lib/utils.js | 12 +++- test/node-unit/cli/options.spec.js | 90 +++++++++++++++++++++++++++--- 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/lib/cli/options.js b/lib/cli/options.js index 5ec8ff0ebf..6ad6bed1df 100644 --- a/lib/cli/options.js +++ b/lib/cli/options.js @@ -10,7 +10,8 @@ const fs = require('fs'); const yargsParser = require('yargs-parser'); const {types, aliases} = require('./run-option-metadata'); const {ONE_AND_DONE_ARGS} = require('./one-and-dones'); -const mocharc = require('../mocharc.json'); +// paranoia +const mocharc = Object.freeze(require('../mocharc.json')); const {list} = require('./run-helpers'); const {loadConfig, findConfig} = require('./config'); const findup = require('findup-sync'); @@ -321,6 +322,30 @@ const loadOptions = (argv = []) => { args.opts = false; args._ = args._.concat(optsConfig._ || []); } + // special case: "extension" option should not combine with default value. + // normally we want to combine "array"-type options, and we _do_ with "extension", but only + // within user-defined configuration (args or anything else). + // we must also search through any aliases of "extension" because while the arguments are /// + // normalized by this point, the config file values are not. + // only the "canonical" option name is used in `mocharc`, so we needn't worry about clearing + // multiple options. + // NOTE: as of this writing, "extension" is the only default value which is of an "array" type; + // it's unknown whether the the below strategy should be generalized to any other future + // "array"-type default option. + const processedMocharc = Object.assign({}, mocharc); + if ( + args.extension || + ['extension'] + .concat(aliases.extension) + .some( + opt => + Object.hasOwnProperty(rcConfig, opt) || + Object.hasOwnProperty(pkgConfig, opt) || + Object.hasOwnProperty(optsConfig, opt) + ) + ) { + delete processedMocharc.extension; + } args = parse( args._, @@ -328,7 +353,7 @@ const loadOptions = (argv = []) => { rcConfig || {}, pkgConfig || {}, optsConfig || {}, - mocharc + processedMocharc ); // recombine positional arguments and "spec" diff --git a/lib/utils.js b/lib/utils.js index 86ba8f0376..14c5ce126d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -575,9 +575,15 @@ exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) { var stat; if (!fs.existsSync(filepath)) { - if (fs.existsSync(filepath + '.js')) { - filepath += '.js'; - } else { + // check all extensions + if ( + !extensions.some(function(ext) { + if (fs.existsSync(filepath + '.' + ext)) { + filepath += '.' + ext; + return true; + } + }) + ) { // Handle glob files = glob.sync(filepath); if (!files.length) { diff --git a/test/node-unit/cli/options.spec.js b/test/node-unit/cli/options.spec.js index b849db5ab3..76f98efdab 100644 --- a/test/node-unit/cli/options.spec.js +++ b/test/node-unit/cli/options.spec.js @@ -25,7 +25,9 @@ const defaults = { timeout: 1000, timeouts: 1000, t: 1000, - opts: '/default/path/to/mocha.opts' + opts: '/default/path/to/mocha.opts', + extension: ['js'], + 'watch-extensions': ['js'] }; describe('options', function() { @@ -57,6 +59,7 @@ describe('options', function() { describe('loadOptions()', function() { describe('when no parameter provided', function() { beforeEach(function() { + this.timeout(500); readFileSync = sandbox.stub(); readFileSync.onFirstCall().returns('{}'); readFileSync.onSecondCall().returns('--retries 3'); @@ -495,8 +498,8 @@ describe('options', function() { beforeEach(function() { readFileSync = sandbox.stub(); config = '/some/.mocharc.json'; - readFileSync.onFirstCall().returns('--retries 3'); - readFileSync.onSecondCall().returns('{}'); + readFileSync.onFirstCall().returns('{}'); + readFileSync.onSecondCall().returns('--retries 3'); findConfig = sandbox.stub(); loadConfig = sandbox.stub().throws('Error', 'failed to parse'); findupSync = sandbox.stub().returns('/some/package.json'); @@ -540,8 +543,8 @@ describe('options', function() { beforeEach(function() { readFileSync = sandbox.stub(); - readFileSync.onFirstCall().returns('--retries 3'); - readFileSync.onSecondCall().returns('{}'); + readFileSync.onFirstCall().returns('{}'); + readFileSync.onSecondCall().throws(); findConfig = sandbox.stub().returns('/some/.mocharc.json'); loadConfig = sandbox.stub().returns({}); findupSync = sandbox.stub().returns('/some/package.json'); @@ -576,8 +579,8 @@ describe('options', function() { beforeEach(function() { readFileSync = sandbox.stub(); - readFileSync.onFirstCall().returns('--retries 3'); - readFileSync.onSecondCall().returns('{}'); + readFileSync.onFirstCall().returns('{}'); + readFileSync.onSecondCall().throws(); findConfig = sandbox.stub().returns(null); loadConfig = sandbox.stub().returns({}); findupSync = sandbox.stub().returns('/some/package.json'); @@ -714,5 +717,78 @@ describe('options', function() { }); }); }); + + describe('"extension" handling', function() { + describe('when user supplies "extension" option', function() { + let result; + + beforeEach(function() { + readFileSync = sandbox.stub(); + readFileSync.onFirstCall().throws(); + findConfig = sandbox.stub().returns('/some/.mocharc.json'); + loadConfig = sandbox.stub().returns({extension: ['tsx']}); + findupSync = sandbox.stub(); + loadOptions = proxyLoadOptions({ + readFileSync, + findConfig, + loadConfig, + findupSync + }); + result = loadOptions(['--extension', 'ts']); + }); + + it('should not concatenate the default value', function() { + expect(result, 'to have property', 'extension', ['ts', 'tsx']); + }); + }); + + describe('when user does not supply "extension" option', function() { + let result; + + beforeEach(function() { + readFileSync = sandbox.stub(); + readFileSync.onFirstCall().throws(); + findConfig = sandbox.stub().returns('/some/.mocharc.json'); + loadConfig = sandbox.stub().returns({}); + findupSync = sandbox.stub(); + loadOptions = proxyLoadOptions({ + readFileSync, + findConfig, + loadConfig, + findupSync + }); + result = loadOptions(); + }); + + it('should retain the default', function() { + expect(result, 'to have property', 'extension', ['js']); + }); + }); + }); + + describe('"spec" handling', function() { + describe('when user supplies "spec" in config and positional arguments', function() { + let result; + + beforeEach(function() { + readFileSync = sandbox.stub(); + readFileSync.onFirstCall().throws(); + findConfig = sandbox.stub().returns('/some/.mocharc.json'); + loadConfig = sandbox.stub().returns({spec: '*.spec.js'}); + findupSync = sandbox.stub(); + loadOptions = proxyLoadOptions({ + readFileSync, + findConfig, + loadConfig, + findupSync + }); + result = loadOptions(['*.test.js']); + }); + + it('should place both into the positional arguments array', function() { + expect(result, 'to have property', '_', ['*.test.js', '*.spec.js']); + }); + }); + }); }); });