From f8b31911256765d32f81616521a6e641b598b538 Mon Sep 17 00:00:00 2001 From: Sam Verschueren Date: Sat, 2 Dec 2017 11:45:56 +0100 Subject: [PATCH] Add error predicate (#27) --- source/index.ts | 8 +++ source/lib/predicates/error.ts | 109 +++++++++++++++++++++++++++++++++ source/test/error.ts | 83 +++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 source/lib/predicates/error.ts create mode 100644 source/test/error.ts diff --git a/source/index.ts b/source/index.ts index 8fd6fc1..fe055c6 100644 --- a/source/index.ts +++ b/source/index.ts @@ -5,6 +5,7 @@ import {NumberPredicate} from './lib/predicates/number'; import {BooleanPredicate} from './lib/predicates/boolean'; import {ArrayPredicate} from './lib/predicates/array'; import {DatePredicate} from './lib/predicates/date'; +import {ErrorPredicate} from './lib/predicates/error'; export interface Ow { (value: any, predicate: Predicate): void; @@ -40,6 +41,10 @@ export interface Ow { * Test the value to be a Date. */ date: DatePredicate; + /** + * Test the value to be an Error. + */ + error: ErrorPredicate; } const main = (value: any, predicate: Predicate) => { @@ -75,6 +80,9 @@ Object.defineProperties(main, { }, date: { get: () => new DatePredicate() + }, + error: { + get: () => new ErrorPredicate() } }); diff --git a/source/lib/predicates/error.ts b/source/lib/predicates/error.ts new file mode 100644 index 0000000..dfcee64 --- /dev/null +++ b/source/lib/predicates/error.ts @@ -0,0 +1,109 @@ +import {Predicate, Context} from './predicate'; + +export class ErrorPredicate extends Predicate { + constructor(context?: Context) { + super('error', context); + } + + /** + * Test an error to have a specific name. + * + * @param expected Expected name of the Error. + */ + name(expected: string) { + return this.addValidator({ + message: error => `Expected error to have name \`${expected}\`, got \`${error.name}\``, + validator: error => error.name === expected + }); + } + + /** + * Test an error to have a specific message. + * + * @param expected Expected message of the Error. + */ + message(expected: string) { + return this.addValidator({ + message: error => `Expected error message to be \`${expected}\`, got \`${error.message}\``, + validator: error => error.message === expected + }); + } + + /** + * Test the error message to include a specific message. + * + * @param message Message that should be included in the error. + */ + messageIncludes(message: string) { + return this.addValidator({ + message: error => `Expected error message to include \`${message}\`, got \`${error.message}\``, + validator: error => error.message.includes(message) + }); + } + + /** + * Test the error object to have specific keys. + * + * @param keys One or more keys which should be part of the error object. + */ + hasKeys(...keys: string[]) { + return this.addValidator({ + message: () => `Expected error message to have keys \`${keys.join('`, `')}\``, + validator: error => keys.every(key => error.hasOwnProperty(key)) + }); + } + + /** + * Test an error to be of a specific instance type. + * + * @param instance The expected instance type of the error. + */ + instanceOf(instance: any) { + return this.addValidator({ + message: error => `Expected \`${error.name}\` to be of type \`${instance.name}\``, + validator: error => error instanceof instance + }); + } + + /** + * Test an Error to be a TypeError. + */ + get typeError() { + return this.instanceOf(TypeError); + } + + /** + * Test an Error to be an EvalError. + */ + get evalError() { + return this.instanceOf(EvalError); + } + + /** + * Test an Error to be a RangeError. + */ + get rangeError() { + return this.instanceOf(RangeError); + } + + /** + * Test an Error to be a ReferenceError. + */ + get referenceError() { + return this.instanceOf(ReferenceError); + } + + /** + * Test an Error to be a SyntaxError. + */ + get syntaxError() { + return this.instanceOf(SyntaxError); + } + + /** + * Test an Error to be a URIError. + */ + get uriError() { + return this.instanceOf(URIError); + } +} diff --git a/source/test/error.ts b/source/test/error.ts new file mode 100644 index 0000000..418e3c5 --- /dev/null +++ b/source/test/error.ts @@ -0,0 +1,83 @@ +import test from 'ava'; +import m from '..'; + +class CustomError extends Error { + constructor(message: string) { + super(message); + this.name = 'CustomError'; + } +} + +test('error', t => { + t.notThrows(() => m(new Error('foo'), m.error)); + t.throws(() => m('12', m.error), 'Expected argument to be of type `error` but received type `string`'); +}); + +test('error.name', t => { + t.notThrows(() => m(new Error('foo'), m.error.name('Error'))); + t.notThrows(() => m(new CustomError('foo'), m.error.name('CustomError'))); + t.throws(() => m(new CustomError('foo'), m.error.name('Error')), 'Expected error to have name `Error`, got `CustomError`'); +}); + +test('error.message', t => { + t.notThrows(() => m(new Error('foo'), m.error.message('foo'))); + t.notThrows(() => m(new CustomError('bar'), m.error.message('bar'))); + t.throws(() => m(new CustomError('foo'), m.error.message('bar')), 'Expected error message to be `bar`, got `foo`'); +}); + +test('error.messageIncludes', t => { + t.notThrows(() => m(new Error('foo bar'), m.error.messageIncludes('foo'))); + t.notThrows(() => m(new Error('foo bar'), m.error.messageIncludes('o'))); + t.notThrows(() => m(new CustomError('foo bar'), m.error.messageIncludes('bar'))); + t.throws(() => m(new CustomError('foo bar'), m.error.messageIncludes('unicorn')), 'Expected error message to include `unicorn`, got `foo bar`'); +}); + +test('error.hasKeys', t => { + const err: any = new Error('foo'); + err.unicorn = '🦄'; + err.rainbow = '🌈'; + + t.notThrows(() => m(err, m.error.hasKeys('unicorn'))); + t.notThrows(() => m(err, m.error.hasKeys('unicorn', 'rainbow'))); + t.throws(() => m(err, m.error.hasKeys('foo')), 'Expected error message to have keys `foo`'); + t.throws(() => m(err, m.error.hasKeys('unicorn', 'foo')), 'Expected error message to have keys `unicorn`, `foo`'); +}); + +test('error.instanceOf', t => { + t.notThrows(() => m(new CustomError('foo'), m.error.instanceOf(CustomError))); + t.notThrows(() => m(new CustomError('foo'), m.error.instanceOf(Error))); + t.notThrows(() => m(new TypeError('foo'), m.error.instanceOf(Error))); + t.notThrows(() => m(new Error('foo'), m.error.instanceOf(Error))); + t.throws(() => m(new Error('foo'), m.error.instanceOf(CustomError)), 'Expected `Error` to be of type `CustomError`'); + t.throws(() => m(new TypeError('foo'), m.error.instanceOf(EvalError)), 'Expected `TypeError` to be of type `EvalError`'); +}); + +test('error.typeError', t => { + t.notThrows(() => m(new TypeError('foo'), m.error.typeError)); + t.throws(() => m(new Error('foo'), m.error.typeError), 'Expected `Error` to be of type `TypeError`'); +}); + +test('error.evalError', t => { + t.notThrows(() => m(new EvalError('foo'), m.error.evalError)); + t.throws(() => m(new Error('foo'), m.error.evalError), 'Expected `Error` to be of type `EvalError`'); +}); + +test('error.rangeError', t => { + t.notThrows(() => m(new RangeError('foo'), m.error.rangeError)); + t.throws(() => m(new EvalError('foo'), m.error.rangeError), 'Expected `EvalError` to be of type `RangeError`'); +}); + +test('error.referenceError', t => { + t.notThrows(() => m(new ReferenceError('foo'), m.error.referenceError)); + t.throws(() => m(new Error('foo'), m.error.referenceError), 'Expected `Error` to be of type `ReferenceError`'); +}); + +test('error.syntaxError', t => { + t.notThrows(() => m(new SyntaxError('foo'), m.error.syntaxError)); + t.throws(() => m(new Error('foo'), m.error.syntaxError), 'Expected `Error` to be of type `SyntaxError`'); +}); + +test('error.uriError', t => { + t.notThrows(() => m(new URIError('foo'), m.error.uriError)); + t.throws(() => m(new Error('foo'), m.error.uriError), 'Expected `Error` to be of type `URIError`'); +});