Browse Source

Add object predicate (#48)

iss58
Sam Verschueren 7 years ago
committed by Sindre Sorhus
parent
commit
3b570b41df
  1. 8
      source/index.ts
  2. 113
      source/lib/predicates/object.ts
  3. 2
      source/lib/utils/of-type.ts
  4. 60
      source/test/object.ts

8
source/index.ts

@ -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';
@ -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.
*/
@ -198,6 +203,9 @@ Object.defineProperties(main, {
array: {
get: () => new ArrayPredicate()
},
object: {
get: () => new ObjectPredicate()
},
date: {
get: () => new DatePredicate()
},

113
source/lib/predicates/object.ts

@ -0,0 +1,113 @@
import is from '@sindresorhus/is';
import isEqual = require('lodash.isequal'); // tslint:disable-line:no-require-imports
import {Predicate, Context} from './predicate';
import hasItems from '../utils/has-items';
import ofType from '../utils/of-type';
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 all the values in the object to match the provided predicate.
*
* @param predicate The predicate that should be applied against every value in the object.
*/
valuesOfType<T>(predicate: Predicate<T>) {
return this.addValidator({
message: (_, error) => error,
validator: (object: any) => {
const values = Object.keys(object).map(key => object[key]);
return ofType(values, predicate);
}
});
}
/**
* Test an object to be deeply equal to the provided object.
*
* @param expected Expected object to match.
*/
deepEqual(expected: object) {
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 be of a specific instance type.
*
* @param instance The expected instance type of the object.
*/
instanceOf(instance: any) {
return this.addValidator({
message: (object: any) => {
let name = object.constructor.name;
if (!name || name === 'Object') {
name = JSON.stringify(object);
}
return `Expected \`${name}\` to be of type \`${instance.name}\``;
},
validator: object => object instanceof instance
});
}
/**
* 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: any) => keys.some(key => object[key] !== undefined)
});
}
}

2
source/lib/utils/of-type.ts

@ -8,7 +8,7 @@ import {Predicate} from '../predicates/predicate';
* @param source Source collection to test.
* @param predicate Predicate to test every item in the source collection against.
*/
export default (source: IterableIterator<any> | Set<any>, predicate: Predicate): boolean | string => {
export default (source: IterableIterator<any> | Set<any> | any[], predicate: Predicate): boolean | string => {
try {
for (const item of source) {
ow(item, predicate);

60
source/test/object.ts

@ -0,0 +1,60 @@
import test from 'ava';
import m from '..';
class Unicorn {} // tslint:disable-line
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.valuesOfType', t => {
t.notThrows(() => m({unicorn: '🦄'}, m.object.valuesOfType(m.string)));
t.notThrows(() => m({unicorn: '🦄', rainbow: '🌈'}, m.object.valuesOfType(m.string)));
t.notThrows(() => m({unicorn: 1, rainbow: 2}, m.object.valuesOfType(m.number)));
t.throws(() => m({unicorn: '🦄', rainbow: 2}, m.object.valuesOfType(m.string)), 'Expected argument to be of type `string` but received type `number`');
t.throws(() => m({unicorn: 'a', rainbow: 'b'}, m.object.valuesOfType(m.string.minLength(2))), 'Expected string to have a minimum length of `2`, got `a`');
});
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.instanceOf', t => {
t.notThrows(() => m(new Error('🦄'), m.object.instanceOf(Error)));
t.notThrows(() => m(new Unicorn(), m.object.instanceOf(Unicorn)));
t.throws(() => m(new Unicorn(), m.object.instanceOf(Error)), 'Expected `Unicorn` to be of type `Error`');
t.throws(() => m(new Error('🦄'), m.object.instanceOf(Unicorn)), 'Expected `Error` to be of type `Unicorn`');
t.throws(() => m({unicorn: '🦄'}, m.object.instanceOf(Unicorn)), 'Expected `{"unicorn":"🦄"}` to be of type `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"]`');
});
Loading…
Cancel
Save