Skip to content
This repository has been archived by the owner on Dec 4, 2023. It is now read-only.

Commit

Permalink
Added custom error objects including code (mochajs#3467)
Browse files Browse the repository at this point in the history
* add MochaError and use

add missing E

add codes to docs

how to add new error

E_ to ERR_

typo fix

fix failing test

typo fix

* single custom object to multiple custom objects

remove generic MochaError

Use instance over code

lint fix

lint fix

update references to suffix Error

mocfix rebase

* Remove custom errors, use factories

Signed-off-by: Craig Taub <craigtaub@gmail.com>

module name Errors

remove superfluous error type.

* add another reporter error

* errors unit test

* cleanup documentation

* camelCase factories. Use props on errors. Use TypeError

* test case use code

* use TypeError for type issues

* add full documentation. update error names

* use code and message for reporter check

* add error handling unit test for mocha

* CLI to throw invalidArgumentValue error

* add data type for MissingArgument error

* updated suite test

* use const for cli's errors

* follow jsdoc for optional params
  • Loading branch information
craigtaub authored and boneskull committed Jan 1, 2019
1 parent abf943c commit daec7c1
Show file tree
Hide file tree
Showing 16 changed files with 282 additions and 43 deletions.
15 changes: 15 additions & 0 deletions docs/index.md
Expand Up @@ -74,6 +74,7 @@ Mocha is a feature-rich JavaScript test framework running on [Node.js](https://n
- [Configuring Mocha (Node.js)](#configuring-mocha-nodejs)
- [`mocha.opts`](#mochaopts)
- [The `test/` Directory](#the-test-directory)
- [Error Codes](#error-codes)
- [Editor Plugins](#editor-plugins)
- [Examples](#examples)
- [Testing Mocha](#testing-mocha)
Expand Down Expand Up @@ -1724,6 +1725,20 @@ $ mocha "./spec/**/*.js"

*Note*: Double quotes around the glob are recommended for portability.

## Error Codes

List of codes associated with Errors thrown inside Mocha. Following NodeJS practices.

| Code | Meaning |
| ------------- | ------------- |
| ERR_MOCHA_INVALID_ARG_TYPE | argument of the wrong type was passed to Mocha's API |
| ERR_MOCHA_INVALID_ARG_VALUE | invalid or unsupported value was passed for a given argument |
| ERR_MOCHA_INVALID_INTERFACE | interface specified in options not found |
| ERR_MOCHA_INVALID_REPORTER | reporter specified in options not found |
| ERR_MOCHA_NO_FILES_MATCH_PATTERN | file/s of test could not be found |
| ERR_MOCHA_NOT_SUPPORTED | type of output specified was not supported |
| ERR_MOCHA_UNDEFINED_ERROR | an error was thrown but no details were specified |

## Editor Plugins

The following editor-related packages are available:
Expand Down
6 changes: 2 additions & 4 deletions lib/cli/run-helpers.js
Expand Up @@ -143,10 +143,8 @@ exports.handleFiles = ({
try {
newFiles = utils.lookupFiles(arg, extension, recursive);
} catch (err) {
if (err.message.indexOf('cannot resolve path') === 0) {
console.error(
`Warning: Could not find any test files matching pattern: ${arg}`
);
if (err.code === 'ERR_MOCHA_NO_FILES_MATCH_PATTERN') {
console.warn('Warning: %s: %O', err.message, err.pattern);
return;
}

Expand Down
9 changes: 8 additions & 1 deletion lib/cli/run.js
Expand Up @@ -9,6 +9,8 @@

const Mocha = require('../mocha');
const ansi = require('ansi-colors');
const errors = require('../errors');
const createInvalidArgumentValueError = errors.createInvalidArgumentValueError;

const {
list,
Expand Down Expand Up @@ -190,7 +192,12 @@ exports.builder = yargs =>
const pair = opt.split('=');

if (pair.length > 2 || !pair.length) {
throw new Error(`invalid reporter option '${opt}'`);
throw createInvalidArgumentValueError(
`invalid reporter option '${opt}'`,
'--reporter-option',
opt,
'expected "key=value" format'
);
}

acc[pair[0]] = pair.length === 2 ? pair[1] : true;
Expand Down
139 changes: 139 additions & 0 deletions lib/errors.js
@@ -0,0 +1,139 @@
'use strict';
/**
* @module Errors
*/
/**
* Factory functions to create throwable error objects
*/

/**
* Creates an error object used when no files to be tested could be found using specified pattern.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} pattern - User-specified argument value.
* @returns {Error} instance detailing the error condition
*/
function createNoFilesMatchPatternError(message, pattern) {
var err = new Error(message);
err.code = 'ERR_MOCHA_NO_FILES_MATCH_PATTERN';
err.pattern = pattern;
return err;
}

/**
* Creates an error object used when the reporter specified in the options was not found.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} reporter - User-specified reporter value.
* @returns {Error} instance detailing the error condition
*/
function createInvalidReporterError(message, reporter) {
var err = new TypeError(message);
err.code = 'ERR_MOCHA_INVALID_REPORTER';
err.reporter = reporter;
return err;
}

/**
* Creates an error object used when the interface specified in the options was not found.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} ui - User-specified interface value.
* @returns {Error} instance detailing the error condition
*/
function createInvalidInterfaceError(message, ui) {
var err = new Error(message);
err.code = 'ERR_MOCHA_INVALID_INTERFACE';
err.interface = ui;
return err;
}

/**
* Creates an error object used when the type of output specified was not supported.
*
* @public
* @param {string} message - Error message to be displayed.
* @returns {Error} instance detailing the error condition
*/
function createNotSupportedError(message) {
var err = new Error(message);
err.code = 'ERR_MOCHA_NOT_SUPPORTED';
return err;
}

/**
* Creates an error object used when an argument is missing.
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} argument - Argument name.
* @param {string} expected - Expected argument datatype.
* @returns {Error} instance detailing the error condition
*/
function createMissingArgumentError(message, argument, expected) {
return createInvalidArgumentTypeError(message, argument, expected);
}

/**
* Creates an error object used when an argument did not use the supported type
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} argument - Argument name.
* @param {string} expected - Expected argument datatype.
* @returns {Error} instance detailing the error condition
*/
function createInvalidArgumentTypeError(message, argument, expected) {
var err = new TypeError(message);
err.code = 'ERR_MOCHA_INVALID_ARG_TYPE';
err.argument = argument;
err.expected = expected;
err.actual = typeof argument;
return err;
}

/**
* Creates an error object used when an argument did not use the supported value
*
* @public
* @param {string} message - Error message to be displayed.
* @param {string} argument - Argument name.
* @param {string} value - Argument value.
* @param {string} [reason] - Why value is invalid.
* @returns {Error} instance detailing the error condition
*/
function createInvalidArgumentValueError(message, argument, value, reason) {
var err = new TypeError(message);
err.code = 'ERR_MOCHA_INVALID_ARG_VALUE';
err.argument = argument;
err.value = value;
err.reason = typeof reason !== 'undefined' ? reason : 'is invalid';
return err;
}

/**
* Creates an error object used when an error was thrown but no details were specified.
*
* @public
* @param {string} message - Error message to be displayed.
* @returns {Error} instance detailing the error condition
*/
function createUndefinedError(message) {
var err = new Error(message);
err.code = 'ERR_MOCHA_UNDEFINED_ERROR';
return err;
}

module.exports = {
createInvalidArgumentTypeError: createInvalidArgumentTypeError,
createInvalidArgumentValueError: createInvalidArgumentValueError,
createInvalidInterfaceError: createInvalidInterfaceError,
createInvalidReporterError: createInvalidReporterError,
createMissingArgumentError: createMissingArgumentError,
createNoFilesMatchPatternError: createNoFilesMatchPatternError,
createNotSupportedError: createNotSupportedError,
createUndefinedError: createUndefinedError
};
8 changes: 6 additions & 2 deletions lib/interfaces/common.js
Expand Up @@ -2,6 +2,8 @@

var Suite = require('../suite');
var utils = require('../utils');
var errors = require('../errors');
var createMissingArgumentError = errors.createMissingArgumentError;

/**
* Functions common to more than one interface.
Expand Down Expand Up @@ -147,11 +149,13 @@ module.exports = function(suites, context, mocha) {
}
suites.shift();
} else if (typeof opts.fn === 'undefined' && !suite.pending) {
throw new Error(
throw createMissingArgumentError(
'Suite "' +
suite.fullTitle() +
'" was defined but no callback was supplied. ' +
'Supply a callback or explicitly skip the suite.'
'Supply a callback or explicitly skip the suite.',
'callback',
'function'
);
} else if (!opts.fn && suite.pending) {
suites.shift();
Expand Down
21 changes: 17 additions & 4 deletions lib/mocha.js
Expand Up @@ -13,6 +13,9 @@ var growl = require('./growl');
var utils = require('./utils');
var mocharc = require('./mocharc.json');
var assign = require('object.assign').getPolyfill();
var errors = require('./errors');
var createInvalidReporterError = errors.createInvalidReporterError;
var createInvalidInterfaceError = errors.createInvalidInterfaceError;

exports = module.exports = Mocha;

Expand Down Expand Up @@ -210,12 +213,16 @@ Mocha.prototype.reporter = function(reporter, reporterOptions) {
try {
_reporter = require(reporter);
} catch (err) {
if (err.message.indexOf('Cannot find module') !== -1) {
if (
err.code !== 'MODULE_NOT_FOUND' ||
err.message.includes('Cannot find module')
) {
// Try to load reporters from a path (absolute or relative)
try {
_reporter = require(path.resolve(process.cwd(), reporter));
} catch (_err) {
err.message.indexOf('Cannot find module') !== -1
_err.code !== 'MODULE_NOT_FOUND' ||
_err.message.includes('Cannot find module')
? console.warn('"' + reporter + '" reporter not found')
: console.warn(
'"' +
Expand All @@ -239,7 +246,10 @@ Mocha.prototype.reporter = function(reporter, reporterOptions) {
);
}
if (!_reporter) {
throw new Error('invalid reporter "' + reporter + '"');
throw createInvalidReporterError(
'invalid reporter "' + reporter + '"',
reporter
);
}
this._reporter = _reporter;
}
Expand All @@ -265,7 +275,10 @@ Mocha.prototype.ui = function(name) {
try {
this._ui = require(name);
} catch (err) {
throw new Error('invalid interface "' + name + '"');
throw createInvalidInterfaceError(
'invalid interface "' + name + '"',
name
);
}
}
this._ui = this._ui(this.suite);
Expand Down
5 changes: 3 additions & 2 deletions lib/reporters/xunit.js
Expand Up @@ -13,7 +13,8 @@ var fs = require('fs');
var escape = utils.escape;
var mkdirp = require('mkdirp');
var path = require('path');

var errors = require('../errors');
var createNotSupportedError = errors.createNotSupportedError;
/**
* Save timer references to avoid Sinon interfering (see GH-237).
*/
Expand Down Expand Up @@ -50,7 +51,7 @@ function XUnit(runner, options) {
if (options && options.reporterOptions) {
if (options.reporterOptions.output) {
if (!fs.createWriteStream) {
throw new Error('file output not supported in browser');
throw createNotSupportedError('file output not supported in browser');
}

mkdirp.sync(path.dirname(options.reporterOptions.output));
Expand Down
10 changes: 7 additions & 3 deletions lib/suite.js
Expand Up @@ -12,6 +12,8 @@ var utils = require('./utils');
var inherits = utils.inherits;
var debug = require('debug')('mocha:suite');
var milliseconds = require('ms');
var errors = require('./errors');
var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;

/**
* Expose `Suite`.
Expand Down Expand Up @@ -49,10 +51,12 @@ exports.create = function(parent, title) {
*/
function Suite(title, parentContext) {
if (!utils.isString(title)) {
throw new Error(
'Suite `title` should be a "string" but "' +
throw createInvalidArgumentTypeError(
'Suite argument "title" must be a string. Received type "' +
typeof title +
'" was given instead.'
'"',
'title',
'string'
);
}
this.title = title;
Expand Down
10 changes: 7 additions & 3 deletions lib/test.js
@@ -1,6 +1,8 @@
'use strict';
var Runnable = require('./runnable');
var utils = require('./utils');
var errors = require('./errors');
var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError;
var isString = utils.isString;

module.exports = Test;
Expand All @@ -15,10 +17,12 @@ module.exports = Test;
*/
function Test(title, fn) {
if (!isString(title)) {
throw new Error(
'Test `title` should be a "string" but "' +
throw createInvalidArgumentTypeError(
'Test argument "title" should be a string. Received type "' +
typeof title +
'" was given instead.'
'"',
'title',
'string'
);
}
Runnable.call(this, title, fn);
Expand Down
17 changes: 13 additions & 4 deletions lib/utils.js
Expand Up @@ -14,6 +14,10 @@ var glob = require('glob');
var path = require('path');
var join = path.join;
var he = require('he');
var errors = require('./errors');
var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
var createMissingArgumentError = errors.createMissingArgumentError;
var createUndefinedError = errors.createUndefinedError;

/**
* Ignored directories.
Expand Down Expand Up @@ -515,7 +519,10 @@ exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) {
} else {
files = glob.sync(filepath);
if (!files.length) {
throw new Error("cannot resolve path (or pattern) '" + filepath + "'");
throw createNoFilesMatchPatternError(
'cannot find any files matching pattern "' + filepath + '"',
filepath
);
}
return files;
}
Expand Down Expand Up @@ -546,8 +553,10 @@ exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) {
return;
}
if (!extensions) {
throw new Error(
'extensions parameter required when filepath is a directory'
throw createMissingArgumentError(
'Argument "extensions" required when argument "filepath" is a directory',
'extensions',
'array'
);
}
var re = new RegExp('\\.(?:' + extensions.join('|') + ')$');
Expand All @@ -567,7 +576,7 @@ exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) {
*/

exports.undefinedError = function() {
return new Error(
return createUndefinedError(
'Caught undefined error, did you throw without specifying what?'
);
};
Expand Down

0 comments on commit daec7c1

Please sign in to comment.