Skip to content

Commit

Permalink
Add object predicate
Browse files Browse the repository at this point in the history
  • Loading branch information
SamVerschueren committed Jan 31, 2018
1 parent 184a1eb commit 477f053
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
8 changes: 8 additions & 0 deletions source/index.ts
Expand Up @@ -4,6 +4,7 @@ import {StringPredicate} from './lib/predicates/string';
import {NumberPredicate} from './lib/predicates/number';
import {BooleanPredicate} from './lib/predicates/boolean';
import {ArrayPredicate} from './lib/predicates/array';
import {ObjectPredicate} from './lib/predicates/object';
import {DatePredicate} from './lib/predicates/date';
import {ErrorPredicate} from './lib/predicates/error';
import {MapPredicate} from './lib/predicates/map';
Expand Down Expand Up @@ -60,6 +61,10 @@ export interface Ow {
* Test the value to be an array.
*/
readonly array: ArrayPredicate;
/**
* Test the value to be an object.
*/
readonly object: ObjectPredicate;
/**
* Test the value to be a Date.
*/
Expand Down Expand Up @@ -196,6 +201,9 @@ Object.defineProperties(main, {
array: {
get: () => new ArrayPredicate()
},
object: {
get: () => new ObjectPredicate()
},
date: {
get: () => new DatePredicate()
},
Expand Down
76 changes: 76 additions & 0 deletions source/lib/predicates/object.ts
@@ -0,0 +1,76 @@
import is from '@sindresorhus/is';
import * as isEqual from 'lodash.isequal';
import {Predicate, Context} from './predicate';
import hasItems from '../utils/has-items';

export class ObjectPredicate extends Predicate<object> {
constructor(context?: Context) {
super('object', context);
}

/**
* Test if an Object is a plain object.
*/
get plain() {
return this.addValidator({
message: () => 'Expected object to be a plain object',
validator: object => is.plainObject(object)
});
}

/**
* Test an object to be empty.
*/
get empty() {
return this.addValidator({
message: object => `Expected object to be empty, got \`${JSON.stringify(object)}\``,
validator: object => Object.keys(object).length === 0
});
}

/**
* Test an object to be not empty.
*/
get nonEmpty() {
return this.addValidator({
message: () => 'Expected object to not be empty',
validator: object => Object.keys(object).length > 0
});
}

/**
* Test an object to be deeply equal to the provided object.
*
* @param expected Expected object to match.
*/
deepEqual(expected: {}) {
return this.addValidator({
message: object => `Expected object to be deeply equal to \`${JSON.stringify(expected)}\`, got \`${JSON.stringify(object)}\``,
validator: object => isEqual(object, expected)
});
}

/**
* Test an object to include all the provided keys.
*
* @param keys The keys that should be present in the object.
*/
hasKeys(...keys: string[]) {
return this.addValidator({
message: (_, missingKeys) => `Expected object to have keys \`${JSON.stringify(missingKeys)}\``,
validator: object => hasItems(new Set(Object.keys(object)), keys)
});
}

/**
* Test an object to include any of the provided keys.
*
* @param keys The keys that could be a key in the object.
*/
hasAnyKeys(...keys: string[]) {
return this.addValidator({
message: () => `Expected object to have any key of \`${JSON.stringify(keys)}\``,
validator: object => keys.some(key => object[key] !== undefined)
});
}
}
42 changes: 42 additions & 0 deletions source/test/object.ts
@@ -0,0 +1,42 @@
import test from 'ava';
import m from '..';

test('object', t => {
t.notThrows(() => m({}, m.object));
t.notThrows(() => m(new Error('foo'), m.object));
t.throws(() => m('foo' as any, m.object), 'Expected argument to be of type `object` but received type `string`');
t.throws(() => m(1 as any, m.object), 'Expected argument to be of type `object` but received type `number`');
});

test('object.plain', t => {
t.notThrows(() => m({}, m.object.plain));
t.throws(() => m(new Error('foo'), m.object.plain), 'Expected object to be a plain object');
});

test('object.empty', t => {
t.notThrows(() => m({}, m.object.empty));
t.throws(() => m({unicorn: 'πŸ¦„'}, m.object.empty), 'Expected object to be empty, got `{"unicorn":"πŸ¦„"}`');
});

test('object.nonEmpty', t => {
t.notThrows(() => m({unicorn: 'πŸ¦„'}, m.object.nonEmpty));
t.throws(() => m({}, m.object.nonEmpty), 'Expected object to not be empty');
});

test('object.deepEqual', t => {
t.notThrows(() => m({unicorn: 'πŸ¦„'}, m.object.deepEqual({unicorn: 'πŸ¦„'})));
t.notThrows(() => m({unicorn: 'πŸ¦„', rain: {bow: '🌈'}}, m.object.deepEqual({unicorn: 'πŸ¦„', rain: {bow: '🌈'}})));
t.throws(() => m({unicorn: 'πŸ¦„'}, m.object.deepEqual({rainbow: '🌈'})), 'Expected object to be deeply equal to `{"rainbow":"🌈"}`, got `{"unicorn":"πŸ¦„"}`');
});

test('object.hasKeys', t => {
t.notThrows(() => m({unicorn: 'πŸ¦„'}, m.object.hasKeys('unicorn')));
t.notThrows(() => m({unicorn: 'πŸ¦„', rainbow: '🌈'}, m.object.hasKeys('unicorn', 'rainbow')));
t.throws(() => m({unicorn: 'πŸ¦„'}, m.object.hasKeys('unicorn', 'rainbow')), 'Expected object to have keys `["rainbow"]`');
});

test('object.hasAnyKeys', t => {
t.notThrows(() => m({unicorn: 'πŸ¦„'}, m.object.hasAnyKeys('unicorn', 'rainbow')));
t.notThrows(() => m({unicorn: 'πŸ¦„', rainbow: '🌈'}, m.object.hasAnyKeys('unicorn')));
t.throws(() => m({unicorn: 'πŸ¦„'}, m.object.hasAnyKeys('foo')), 'Expected object to have any key of `["foo"]`');
});

0 comments on commit 477f053

Please sign in to comment.