Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error predicate #27

Merged
merged 5 commits into from
Dec 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -75,6 +80,9 @@ Object.defineProperties(main, {
},
date: {
get: () => new DatePredicate()
},
error: {
get: () => new ErrorPredicate()
}
});

Expand Down
109 changes: 109 additions & 0 deletions source/lib/predicates/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {Predicate, Context} from './predicate';

export class ErrorPredicate extends Predicate<Error> {
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);
}
}
83 changes: 83 additions & 0 deletions source/test/error.ts
Original file line number Diff line number Diff line change
@@ -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`');
});