From fe91be11e8be6f7413019d098c865d12c5b12a50 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 20 Apr 2017 12:23:32 -0400 Subject: [PATCH] refactor(errors): createError and createUserError accept an object (#1322) Currently, both of these functions accept two arguments: the error title, and the error description. This function signature makes is hard to keep adding options to these error creation functions, like an error code, so this commit refactors them to take a single argument: an options object containing `title` and `description` properties. Signed-off-by: Juan Cruz Viotti --- lib/child-writer/writer-proxy.js | 12 +- lib/cli/etcher.js | 21 +-- lib/cli/options.js | 17 ++- lib/cli/writer.js | 8 +- lib/gui/models/store.js | 136 +++++++++++++----- .../pages/main/controllers/image-selection.js | 9 +- .../manifest-bind/directives/manifest-bind.js | 4 +- lib/image-stream/archive-hooks/zip.js | 4 +- lib/image-stream/archive.js | 16 +-- lib/image-stream/index.js | 10 +- lib/shared/errors.js | 39 +++-- lib/shared/robot/README.md | 6 +- lib/shared/robot/index.js | 14 +- lib/shared/utils.js | 4 +- tests/shared/errors.spec.js | 134 +++++++++++++---- 15 files changed, 312 insertions(+), 122 deletions(-) diff --git a/lib/child-writer/writer-proxy.js b/lib/child-writer/writer-proxy.js index 6f65be21..804e2235 100644 --- a/lib/child-writer/writer-proxy.js +++ b/lib/child-writer/writer-proxy.js @@ -152,7 +152,9 @@ return permissions.isElevated().then((elevated) => { name: packageJSON.displayName }).then((stdout, stderr) => { if (!_.isEmpty(stderr)) { - throw errors.createError(stderr); + throw errors.createError({ + title: stderr + }); } // We're hardcoding internal error messages declared by `sudo-prompt`. @@ -167,10 +169,10 @@ return permissions.isElevated().then((elevated) => { }).catch({ message: 'No polkit authentication agent found.' }, () => { - throw errors.createUserError( - 'No polkit authentication agent found', - 'Please install a polkit authentication agent for your desktop environment of choice to continue' - ); + throw errors.createUserError({ + title: 'No polkit authentication agent found', + description: 'Please install a polkit authentication agent for your desktop environment of choice to continue' + }); }); } diff --git a/lib/cli/etcher.js b/lib/cli/etcher.js index 0923ffa8..da561ed0 100644 --- a/lib/cli/etcher.js +++ b/lib/cli/etcher.js @@ -35,10 +35,10 @@ const imagePath = options._[ARGV_IMAGE_PATH_INDEX]; permissions.isElevated().then((elevated) => { if (!elevated) { - throw errors.createUserError( - messages.error.elevationRequired(), - 'This tool requires special permissions to write to external drives' - ); + throw errors.createUserError({ + title: messages.error.elevationRequired(), + description: 'This tool requires special permissions to write to external drives' + }); } return form.run([ @@ -66,7 +66,10 @@ permissions.isElevated().then((elevated) => { }); }).then((answers) => { if (!answers.yes) { - throw errors.createUserError('Aborted', 'We can\'t proceed without confirmation'); + throw errors.createUserError({ + title: 'Aborted', + description: 'We can\'t proceed without confirmation' + }); } const progressBars = { @@ -80,10 +83,10 @@ permissions.isElevated().then((elevated) => { }); if (!selectedDrive) { - throw errors.createUserError( - 'The selected drive was not found', - `We can't find ${answers.drive} in your system. Did you unplug the drive?` - ); + throw errors.createUserError({ + title: 'The selected drive was not found', + description: `We can't find ${answers.drive} in your system. Did you unplug the drive?` + }); } return writer.writeImage(imagePath, selectedDrive, { diff --git a/lib/cli/options.js b/lib/cli/options.js index e09e6939..bd9ba17f 100644 --- a/lib/cli/options.js +++ b/lib/cli/options.js @@ -93,7 +93,9 @@ module.exports = yargs // Error reporting .fail((message, error) => { - const errorObject = error || errors.createUserError(message); + const errorObject = error || errors.createUserError({ + title: message + }); if (robot.isEnabled(process.env)) { robot.printError(errorObject); @@ -112,7 +114,10 @@ module.exports = yargs try { fs.accessSync(imagePath); } catch (error) { - throw errors.createUserError('Unable to access file', `The image ${imagePath} is not accessible`); + throw errors.createUserError({ + title: 'Unable to access file', + description: `The image ${imagePath} is not accessible` + }); } return true; @@ -120,10 +125,10 @@ module.exports = yargs .check((argv) => { if (robot.isEnabled(process.env) && !argv.drive) { - throw errors.createUserError( - 'Missing drive', - 'You need to explicitly pass a drive when enabling robot mode' - ); + throw errors.createUserError({ + title: 'Missing drive', + description: 'You need to explicitly pass a drive when enabling robot mode' + }); } return true; diff --git a/lib/cli/writer.js b/lib/cli/writer.js index b5d9bd5b..164a0a41 100644 --- a/lib/cli/writer.js +++ b/lib/cli/writer.js @@ -70,10 +70,10 @@ exports.writeImage = (imagePath, drive, options, onProgress) => { }).then((driveFileDescriptor) => { return imageStream.getFromFilePath(imagePath).then((image) => { if (!constraints.isDriveLargeEnough(drive, image)) { - throw errors.createUserError( - 'The image you selected is too big for this drive', - 'Please connect a bigger drive and try again' - ); + throw errors.createUserError({ + title: 'The image you selected is too big for this drive', + description: 'Please connect a bigger drive and try again' + }); } return imageWrite.write({ diff --git a/lib/gui/models/store.js b/lib/gui/models/store.js index 5b6c0028..d50ba02f 100644 --- a/lib/gui/models/store.js +++ b/lib/gui/models/store.js @@ -119,11 +119,15 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SET_AVAILABLE_DRIVES: { if (!action.data) { - throw errors.createError('Missing drives'); + throw errors.createError({ + title: 'Missing drives' + }); } if (!_.isArray(action.data) || !_.every(action.data, _.isPlainObject)) { - throw errors.createError(`Invalid drives: ${action.data}`); + throw errors.createError({ + title: `Invalid drives: ${action.data}` + }); } const newState = state.set('availableDrives', Immutable.fromJS(action.data)); @@ -170,35 +174,51 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SET_FLASH_STATE: { if (!state.get('isFlashing')) { - throw errors.createError('Can\'t set the flashing state when not flashing'); + throw errors.createError({ + title: 'Can\'t set the flashing state when not flashing' + }); } if (!action.data.type) { - throw errors.createError('Missing state type'); + throw errors.createError({ + title: 'Missing state type' + }); } if (!_.isString(action.data.type)) { - throw errors.createError(`Invalid state type: ${action.data.type}`); + throw errors.createError({ + title: `Invalid state type: ${action.data.type}` + }); } if (_.isNil(action.data.percentage)) { - throw errors.createError('Missing state percentage'); + throw errors.createError({ + title: 'Missing state percentage' + }); } if (!_.isNumber(action.data.percentage)) { - throw errors.createError(`Invalid state percentage: ${action.data.percentage}`); + throw errors.createError({ + title: `Invalid state percentage: ${action.data.percentage}` + }); } if (_.isNil(action.data.eta)) { - throw errors.createError('Missing state eta'); + throw errors.createError({ + title: 'Missing state eta' + }); } if (!_.isNumber(action.data.eta)) { - throw errors.createError(`Invalid state eta: ${action.data.eta}`); + throw errors.createError({ + title: `Invalid state eta: ${action.data.eta}` + }); } if (_.isNil(action.data.speed)) { - throw errors.createError('Missing state speed'); + throw errors.createError({ + title: 'Missing state speed' + }); } return state.set('flashState', Immutable.fromJS(action.data)); @@ -218,7 +238,9 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.UNSET_FLASHING_FLAG: { if (!action.data) { - throw errors.createError('Missing results'); + throw errors.createError({ + title: 'Missing results' + }); } _.defaults(action.data, { @@ -226,19 +248,27 @@ const storeReducer = (state = DEFAULT_STATE, action) => { }); if (!_.isBoolean(action.data.cancelled)) { - throw errors.createError(`Invalid results cancelled: ${action.data.cancelled}`); + throw errors.createError({ + title: `Invalid results cancelled: ${action.data.cancelled}` + }); } if (action.data.cancelled && action.data.sourceChecksum) { - throw errors.createError('The sourceChecksum value can\'t exist if the flashing was cancelled'); + throw errors.createError({ + title: 'The sourceChecksum value can\'t exist if the flashing was cancelled' + }); } if (action.data.sourceChecksum && !_.isString(action.data.sourceChecksum)) { - throw errors.createError(`Invalid results sourceChecksum: ${action.data.sourceChecksum}`); + throw errors.createError({ + title: `Invalid results sourceChecksum: ${action.data.sourceChecksum}` + }); } if (action.data.errorCode && !_.isString(action.data.errorCode) && !_.isNumber(action.data.errorCode)) { - throw errors.createError(`Invalid results errorCode: ${action.data.errorCode}`); + throw errors.createError({ + title: `Invalid results errorCode: ${action.data.errorCode}` + }); } return state @@ -249,26 +279,36 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SELECT_DRIVE: { if (!action.data) { - throw errors.createError('Missing drive'); + throw errors.createError({ + title: 'Missing drive' + }); } if (!_.isString(action.data)) { - throw errors.createError(`Invalid drive: ${action.data}`); + throw errors.createError({ + title: `Invalid drive: ${action.data}` + }); } const selectedDrive = findDrive(state, action.data); if (!selectedDrive) { - throw errors.createError(`The drive is not available: ${action.data}`); + throw errors.createError({ + title: `The drive is not available: ${action.data}` + }); } if (selectedDrive.get('protected')) { - throw errors.createError('The drive is write-protected'); + throw errors.createError({ + title: 'The drive is write-protected' + }); } const image = state.getIn([ 'selection', 'image' ]); if (image && !constraints.isDriveLargeEnough(selectedDrive.toJS(), image.toJS())) { - throw errors.createError('The drive is not large enough'); + throw errors.createError({ + title: 'The drive is not large enough' + }); } return state.setIn([ 'selection', 'drive' ], Immutable.fromJS(action.data)); @@ -276,45 +316,65 @@ const storeReducer = (state = DEFAULT_STATE, action) => { case ACTIONS.SELECT_IMAGE: { if (!action.data.path) { - throw errors.createError('Missing image path'); + throw errors.createError({ + title: 'Missing image path' + }); } if (!_.isString(action.data.path)) { - throw errors.createError(`Invalid image path: ${action.data.path}`); + throw errors.createError({ + title: `Invalid image path: ${action.data.path}` + }); } if (!action.data.size) { - throw errors.createError('Missing image size'); + throw errors.createError({ + title: 'Missing image size' + }); } if (!_.isPlainObject(action.data.size)) { - throw errors.createError(`Invalid image size: ${action.data.size}`); + throw errors.createError({ + title: `Invalid image size: ${action.data.size}` + }); } const MINIMUM_IMAGE_SIZE = 0; if (!_.isInteger(action.data.size.original) || action.data.size.original < MINIMUM_IMAGE_SIZE) { - throw errors.createError(`Invalid original image size: ${action.data.size.original}`); + throw errors.createError({ + title: `Invalid original image size: ${action.data.size.original}` + }); } if (!_.isInteger(action.data.size.final.value) || action.data.size.final.value < MINIMUM_IMAGE_SIZE) { - throw errors.createError(`Invalid final image size: ${action.data.size.final.value}`); + throw errors.createError({ + title: `Invalid final image size: ${action.data.size.final.value}` + }); } if (!_.isBoolean(action.data.size.final.estimation)) { - throw errors.createError(`Invalid final image size estimation flag: ${action.data.size.final.estimation}`); + throw errors.createError({ + title: `Invalid final image size estimation flag: ${action.data.size.final.estimation}` + }); } if (action.data.url && !_.isString(action.data.url)) { - throw errors.createError(`Invalid image url: ${action.data.url}`); + throw errors.createError({ + title: `Invalid image url: ${action.data.url}` + }); } if (action.data.name && !_.isString(action.data.name)) { - throw errors.createError(`Invalid image name: ${action.data.name}`); + throw errors.createError({ + title: `Invalid image name: ${action.data.name}` + }); } if (action.data.logo && !_.isString(action.data.logo)) { - throw errors.createError(`Invalid image logo: ${action.data.logo}`); + throw errors.createError({ + title: `Invalid image logo: ${action.data.logo}` + }); } const selectedDrive = findDrive(state, state.getIn([ 'selection', 'drive' ])); @@ -346,19 +406,27 @@ const storeReducer = (state = DEFAULT_STATE, action) => { const value = action.data.value; if (!key) { - throw errors.createError('Missing setting key'); + throw errors.createError({ + title: 'Missing setting key' + }); } if (!_.isString(key)) { - throw errors.createError(`Invalid setting key: ${key}`); + throw errors.createError({ + title: `Invalid setting key: ${key}` + }); } if (!DEFAULT_STATE.get('settings').has(key)) { - throw errors.createError(`Unsupported setting: ${key}`); + throw errors.createError({ + title: `Unsupported setting: ${key}` + }); } if (_.isObject(value)) { - throw errors.createError(`Invalid setting value: ${value}`); + throw errors.createError({ + title: `Invalid setting value: ${value}` + }); } return state.setIn([ 'settings', key ], value); diff --git a/lib/gui/pages/main/controllers/image-selection.js b/lib/gui/pages/main/controllers/image-selection.js index ce34cb27..3984f6b3 100644 --- a/lib/gui/pages/main/controllers/image-selection.js +++ b/lib/gui/pages/main/controllers/image-selection.js @@ -68,9 +68,12 @@ module.exports = function( */ this.selectImage = (image) => { if (!supportedFormats.isSupportedImage(image.path)) { - const invalidImageError = errors.createUserError('Invalid image', messages.error.invalidImage({ - image - })); + const invalidImageError = errors.createUserError({ + title: 'Invalid image', + description: messages.error.invalidImage({ + image + }) + }); OSDialogService.showError(invalidImageError); AnalyticsService.logEvent('Invalid image', image); diff --git a/lib/gui/utils/manifest-bind/directives/manifest-bind.js b/lib/gui/utils/manifest-bind/directives/manifest-bind.js index 5281b5f4..36fb264a 100644 --- a/lib/gui/utils/manifest-bind/directives/manifest-bind.js +++ b/lib/gui/utils/manifest-bind/directives/manifest-bind.js @@ -41,7 +41,9 @@ module.exports = (ManifestBindService) => { const value = ManifestBindService.get(attributes.manifestBind); if (!value) { - throw errors.createError(`ManifestBind: Unknown property \`${attributes.manifestBind}\``); + throw errors.createError({ + title: `ManifestBind: Unknown property \`${attributes.manifestBind}\`` + }); } element.html(value); diff --git a/lib/image-stream/archive-hooks/zip.js b/lib/image-stream/archive-hooks/zip.js index 6fac610d..590514d2 100644 --- a/lib/image-stream/archive-hooks/zip.js +++ b/lib/image-stream/archive-hooks/zip.js @@ -89,7 +89,9 @@ exports.extractFile = (archive, entries, file) => { if (!_.find(entries, { name: file })) { - throw errors.createError(`Invalid entry: ${file}`); + throw errors.createError({ + title: `Invalid entry: ${file}` + }); } yauzl.openAsync(archive, { diff --git a/lib/image-stream/archive.js b/lib/image-stream/archive.js index 1d5c556c..207e3568 100644 --- a/lib/image-stream/archive.js +++ b/lib/image-stream/archive.js @@ -119,10 +119,10 @@ const extractArchiveMetadata = (archive, basePath, options) => { try { return JSON.parse(manifest); } catch (parseError) { - throw errors.createUserError( - 'Invalid archive manifest.json', - 'The archive manifest.json file is not valid JSON' - ); + throw errors.createUserError({ + title: 'Invalid archive manifest.json', + description: 'The archive manifest.json file is not valid JSON' + }); } }); }) @@ -178,10 +178,10 @@ exports.extractImage = (archive, hooks) => { const VALID_NUMBER_OF_IMAGE_ENTRIES = 1; if (imageEntries.length !== VALID_NUMBER_OF_IMAGE_ENTRIES) { - throw errors.createUserError( - 'Invalid archive image', - 'The archive image should contain one and only one top image file' - ); + throw errors.createUserError({ + title: 'Invalid archive image', + description: 'The archive image should contain one and only one top image file' + }); } const imageEntry = _.first(imageEntries); diff --git a/lib/image-stream/index.js b/lib/image-stream/index.js index 2003da3e..ea526063 100644 --- a/lib/image-stream/index.js +++ b/lib/image-stream/index.js @@ -69,12 +69,18 @@ const errors = require('../shared/errors'); exports.getFromFilePath = (file) => { return fs.statAsync(file).then((fileStats) => { if (!fileStats.isFile()) { - throw errors.createUserError('Invalid image', 'The image must be a file'); + throw errors.createUserError({ + title: 'Invalid image', + description: 'The image must be a file' + }); } return utils.getArchiveMimeType(file).then((type) => { if (!_.has(handlers, type)) { - throw errors.createUserError('Invalid image', `The ${type} format is not supported`); + throw errors.createUserError({ + title: 'Invalid image', + description: `The ${type} format is not supported` + }); } return _.invoke(handlers, type, file, { diff --git a/lib/shared/errors.js b/lib/shared/errors.js index 24c39414..4ae1d0c5 100644 --- a/lib/shared/errors.js +++ b/lib/shared/errors.js @@ -231,23 +231,27 @@ exports.getDescription = (error, options = {}) => { * @function * @public * - * @param {String} title - error title - * @param {String} [description] - error description - * @param {Object} [options] - options + * @param {Object} options - options + * @param {String} options.title - error title + * @param {String} [options.description] - error description * @param {Boolean} [options.report] - report error * @returns {Error} error * * @example - * const error = errors.createError('Foo', 'Bar'); + * const error = errors.createError({ + * title: 'Foo' + * description: 'Bar' + * }); + * * throw error; */ -exports.createError = (title, description, options = {}) => { - if (isBlank(title)) { - throw new Error(`Invalid error title: ${title}`); +exports.createError = (options) => { + if (isBlank(options.title)) { + throw new Error(`Invalid error title: ${options.title}`); } - const error = new Error(title); - error.description = description; + const error = new Error(options.title); + error.description = options.description; if (!_.isNil(options.report) && !options.report) { error.report = false; @@ -267,16 +271,23 @@ exports.createError = (title, description, options = {}) => { * Therefore, user errors don't get reported to analytics * and error reporting services. * - * @param {String} title - error title - * @param {String} [description] - error description + * @param {Object} options - options + * @param {String} options.title - error title + * @param {String} [options.description] - error description * @returns {Error} user error * * @example - * const error = errors.createUserError('Foo', 'Bar'); + * const error = errors.createUserError({ + * title: 'Foo', + * description: 'Bar' + * }); + * * throw error; */ -exports.createUserError = (title, description) => { - return exports.createError(title, description, { +exports.createUserError = (options) => { + return exports.createError({ + title: options.title, + description: options.description, report: false }); }; diff --git a/lib/shared/robot/README.md b/lib/shared/robot/README.md index 8ca8e0b4..2925b27e 100644 --- a/lib/shared/robot/README.md +++ b/lib/shared/robot/README.md @@ -120,7 +120,11 @@ functions: `.printError()` and `.recomposeErrorMessage()`. Here's an example of these functions in action: ```javascript -const error = errors.createError('This is an error', 'My description'); +const error = errors.createError({ + title: 'This is an error', + description: 'My description' +}); + robot.printError(error); ``` diff --git a/lib/shared/robot/index.js b/lib/shared/robot/index.js index aa387dbe..ee130383 100644 --- a/lib/shared/robot/index.js +++ b/lib/shared/robot/index.js @@ -56,7 +56,9 @@ exports.isEnabled = (environment) => { */ exports.buildMessage = (title, data = {}) => { if (!_.isPlainObject(data)) { - throw errors.createError(`Invalid data: ${data}`); + throw errors.createError({ + title: `Invalid data: ${data}` + }); } return JSON.stringify({ @@ -117,11 +119,17 @@ exports.parseMessage = (string) => { try { output = JSON.parse(string); } catch (error) { - throw errors.createError('Invalid message', `${string}, ${error.message}`); + throw errors.createError({ + title: 'Invalid message', + description: `${string}, ${error.message}` + }); } if (!output.command || !output.data) { - throw errors.createError('Invalid message', `No command or data: ${string}`); + throw errors.createError({ + title: 'Invalid message', + description: `No command or data: ${string}` + }); } return output; diff --git a/lib/shared/utils.js b/lib/shared/utils.js index c9e98e3a..8a407099 100644 --- a/lib/shared/utils.js +++ b/lib/shared/utils.js @@ -71,7 +71,9 @@ exports.isValidPercentage = (percentage) => { */ exports.percentageToFloat = (percentage) => { if (!exports.isValidPercentage(percentage)) { - throw errors.createError(`Invalid percentage: ${percentage}`); + throw errors.createError({ + title: `Invalid percentage: ${percentage}` + }); } return percentage / PERCENTAGE_MAXIMUM; diff --git a/tests/shared/errors.spec.js b/tests/shared/errors.spec.js index cc79d8a8..b3d3e3ed 100644 --- a/tests/shared/errors.spec.js +++ b/tests/shared/errors.spec.js @@ -390,12 +390,18 @@ describe('Shared: Errors', function() { describe('.createError()', function() { it('should not set `error.report` by default', function() { - const error = errors.createError('Foo', 'Something happened'); + const error = errors.createError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error.report).to.be.undefined; }); it('should set `error.report` to false if `options.report` is false', function() { - const error = errors.createError('Foo', 'Something happened', { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened', report: false }); @@ -403,7 +409,9 @@ describe('Shared: Errors', function() { }); it('should set `error.report` to false if `options.report` evaluates to false', function() { - const error = errors.createError('Foo', 'Something happened', { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened', report: 0 }); @@ -411,47 +419,78 @@ describe('Shared: Errors', function() { }); it('should be an instance of Error', function() { - const error = errors.createError('Foo', 'Something happened'); + const error = errors.createError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error).to.be.an.instanceof(Error); }); - it('should correctly add both a message and a description', function() { - const error = errors.createError('Foo', 'Something happened'); + it('should correctly add both a title and a description', function() { + const error = errors.createError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal('Something happened'); }); - it('should correctly add only a message', function() { - const error = errors.createError('Foo'); + it('should correctly add only a title', function() { + const error = errors.createError({ + title: 'Foo' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore an empty description', function() { - const error = errors.createError('Foo', ''); + const error = errors.createError({ + title: 'Foo', + description: '' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore a blank description', function() { - const error = errors.createError('Foo', ' '); + const error = errors.createError({ + title: 'Foo', + description: ' ' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); - it('should throw if no message', function() { + it('should throw if no title', function() { m.chai.expect(() => { - errors.createError(); + errors.createError({}); }).to.throw('Invalid error title: undefined'); }); - it('should throw if message is empty', function() { + it('should throw if there is a description but no title', function() { m.chai.expect(() => { - errors.createError(''); + errors.createError({ + description: 'foo' + }); + }).to.throw('Invalid error title: undefined'); + }); + + it('should throw if title is empty', function() { + m.chai.expect(() => { + errors.createError({ + title: '' + }); }).to.throw('Invalid error title: '); }); - it('should throw if message is blank', function() { + it('should throw if title is blank', function() { m.chai.expect(() => { - errors.createError(' '); + errors.createError({ + title: ' ' + }); }).to.throw('Invalid error title: '); }); @@ -460,52 +499,87 @@ describe('Shared: Errors', function() { describe('.createUserError()', function() { it('should set the `report` flag to `false`', function() { - const error = errors.createUserError('Foo', 'Something happened'); + const error = errors.createUserError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error.report).to.be.false; }); it('should be an instance of Error', function() { - const error = errors.createUserError('Foo', 'Something happened'); + const error = errors.createUserError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(error).to.be.an.instanceof(Error); }); - it('should correctly add both a message and a description', function() { - const error = errors.createUserError('Foo', 'Something happened'); + it('should correctly add both a title and a description', function() { + const error = errors.createUserError({ + title: 'Foo', + description: 'Something happened' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal('Something happened'); }); - it('should correctly add only a message', function() { - const error = errors.createUserError('Foo'); + it('should correctly add only a title', function() { + const error = errors.createUserError({ + title: 'Foo' + }); + m.chai.expect(errors.getTitle(error)).to.equal('Foo'); m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore an empty description', function() { - const error = errors.createUserError('Foo', ''); + const error = errors.createUserError({ + title: 'Foo', + description: '' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); it('should ignore a blank description', function() { - const error = errors.createUserError('Foo', ' '); + const error = errors.createUserError({ + title: 'Foo', + description: ' ' + }); + m.chai.expect(errors.getDescription(error)).to.equal(error.stack); }); - it('should throw if no message', function() { + it('should throw if no title', function() { m.chai.expect(() => { - errors.createUserError(); + errors.createUserError({}); }).to.throw('Invalid error title: undefined'); }); - it('should throw if message is empty', function() { + it('should throw if title is empty', function() { m.chai.expect(() => { - errors.createUserError(''); + errors.createUserError({ + title: '' + }); }).to.throw('Invalid error title: '); }); - it('should throw if message is blank', function() { + it('should throw if there is a description but no title', function() { m.chai.expect(() => { - errors.createUserError(' '); + errors.createUserError({ + description: 'foo' + }); + }).to.throw('Invalid error title: undefined'); + }); + + it('should throw if title is blank', function() { + m.chai.expect(() => { + errors.createUserError({ + title: ' ' + }); }).to.throw('Invalid error title: '); });