Skip to content

Commit

Permalink
refactor validatePlugins to throw coded errors
Browse files Browse the repository at this point in the history
- add `createInvalidPluginError` for reporters, UIs, and future plugins

Ref: #4198
  • Loading branch information
boneskull committed Apr 21, 2020
1 parent 38d579a commit f08d590
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 36 deletions.
28 changes: 19 additions & 9 deletions lib/cli/run-helpers.js
Expand Up @@ -14,6 +14,7 @@ const watchRun = require('./watch-run');
const collectFiles = require('./collect-files');

const cwd = (exports.cwd = process.cwd());
const {createInvalidPluginError} = require('../errors');

/**
* Exits Mocha when tests + code under test has finished execution (default)
Expand Down Expand Up @@ -146,22 +147,31 @@ exports.runMocha = async (mocha, options) => {
};

/**
* Used for `--reporter` and `--ui`. Ensures there's only one, and asserts
* that it actually exists.
* @todo XXX This must get run after requires are processed, as it'll prevent
* interfaces from loading.
* Used for `--reporter` and `--ui`. Ensures there's only one, and asserts that
* it actually exists. This must be run _after_ requires are processed (see
* {@link handleRequires}), as it'll prevent interfaces from loading otherwise.
* @param {Object} opts - Options object
* @param {string} key - Resolvable module name or path
* @param {Object} [map] - An object perhaps having key `key`
* @param {Object} [map] - An object perhaps having key `key`. Used as a cache
* of sorts; `Mocha.reporters` is one, where each key corresponds to a reporter
* name
* @private
*/
exports.validatePlugin = (opts, key, map = {}) => {
if (Array.isArray(opts[key])) {
throw new TypeError(`"--${key} <${key}>" can only be specified once`);
throw createInvalidPluginError(
`"--${key} <${key}>" can only be specified once`,
key
);
}

const unknownError = () => new Error(`Unknown "${key}": ${opts[key]}`);
const unknownError = err =>
createInvalidPluginError(
`Could not load ${key} "${opts[key]}": ${err}`,
key
);

// if this exists, then it's already loaded, so nothing more to do.
if (!map[opts[key]]) {
try {
opts[key] = require(opts[key]);
Expand All @@ -171,10 +181,10 @@ exports.validatePlugin = (opts, key, map = {}) => {
try {
opts[key] = require(path.resolve(process.cwd(), opts[key]));
} catch (err) {
throw unknownError();
throw unknownError(err);
}
} else {
throw unknownError();
throw unknownError(err);
}
}
}
Expand Down
23 changes: 22 additions & 1 deletion lib/errors.js
Expand Up @@ -129,6 +129,26 @@ function createInvalidExceptionError(message, value) {
return err;
}

/**
* Dynamically creates a plugin-type-specific error based on plugin type
* @param {string} message - Error message
* @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed
* @param {string} pluginName - Name of plugin
* @throws When `pluginType` is not known
* @public
* @returns {Error}
*/
function createInvalidPluginError(message, pluginType, pluginName) {
switch (pluginType) {
case 'reporter':
return createInvalidReporterError(message, pluginName);
case 'interface':
return createInvalidInterfaceError(message, pluginName);
default:
throw new Error('unknown pluginType "' + pluginType + '"');
}
}

module.exports = {
createInvalidArgumentTypeError: createInvalidArgumentTypeError,
createInvalidArgumentValueError: createInvalidArgumentValueError,
Expand All @@ -137,5 +157,6 @@ module.exports = {
createInvalidReporterError: createInvalidReporterError,
createMissingArgumentError: createMissingArgumentError,
createNoFilesMatchPatternError: createNoFilesMatchPatternError,
createUnsupportedError: createUnsupportedError
createUnsupportedError: createUnsupportedError,
createInvalidPluginError: createInvalidPluginError
};
94 changes: 68 additions & 26 deletions test/node-unit/cli/run-helpers.spec.js
Expand Up @@ -3,7 +3,7 @@
const {validatePlugin, list} = require('../../../lib/cli/run-helpers');
const {createSandbox} = require('sinon');

describe('cli "run" command', function() {
describe('run helper functions', function() {
let sandbox;

beforeEach(function() {
Expand All @@ -14,36 +14,78 @@ describe('cli "run" command', function() {
sandbox.restore();
});

describe('helpers', function() {
describe('validatePlugin()', function() {
it('should disallow an array of module names', function() {
describe('validatePlugin()', function() {
describe('when used with "reporter" key', function() {
it('should disallow an array of names', function() {
expect(
() => validatePlugin({foo: ['bar']}, 'foo'),
'to throw a',
TypeError
() => validatePlugin({reporter: ['bar']}, 'reporter'),
'to throw',
{
code: 'ERR_MOCHA_INVALID_REPORTER',
message: /can only be specified once/i
}
);
});

it('should fail to recognize an unknown reporter', function() {
expect(
() => validatePlugin({reporter: 'bar'}, 'reporter'),
'to throw',
{code: 'ERR_MOCHA_INVALID_REPORTER', message: /cannot find module/i}
);
});
});

describe('list()', function() {
describe('when provided a flat array', function() {
it('should return a flat array', function() {
expect(list(['foo', 'bar']), 'to equal', ['foo', 'bar']);
});
});
describe('when provided a nested array', function() {
it('should return a flat array', function() {
expect(list([['foo', 'bar'], 'baz']), 'to equal', [
'foo',
'bar',
'baz'
]);
});
});
describe('when given a comma-delimited string', function() {
it('should return a flat array', function() {
expect(list('foo,bar'), 'to equal', ['foo', 'bar']);
});
describe('when used with an "interfaces" key', function() {
it('should disallow an array of names', function() {
expect(
() => validatePlugin({interface: ['bar']}, 'interface'),
'to throw',
{
code: 'ERR_MOCHA_INVALID_INTERFACE',
message: /can only be specified once/i
}
);
});

it('should fail to recognize an unknown interface', function() {
expect(
() => validatePlugin({interface: 'bar'}, 'interface'),
'to throw',
{code: 'ERR_MOCHA_INVALID_INTERFACE', message: /cannot find module/i}
);
});
});

describe('when used with an unknown plugin type', function() {
it('should fail', function() {
expect(
() => validatePlugin({frog: ['bar']}, 'frog'),
'to throw',
/unknown plugin/i
);
});
});
});

describe('list()', function() {
describe('when provided a flat array', function() {
it('should return a flat array', function() {
expect(list(['foo', 'bar']), 'to equal', ['foo', 'bar']);
});
});
describe('when provided a nested array', function() {
it('should return a flat array', function() {
expect(list([['foo', 'bar'], 'baz']), 'to equal', [
'foo',
'bar',
'baz'
]);
});
});
describe('when given a comma-delimited string', function() {
it('should return a flat array', function() {
expect(list('foo,bar'), 'to equal', ['foo', 'bar']);
});
});
});
Expand Down

0 comments on commit f08d590

Please sign in to comment.