Skip to content

Commit

Permalink
Add array.exactShape predicate (#187)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
ilkerceng and sindresorhus authored Jan 19, 2021
1 parent 0372eb0 commit dbef144
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 2 deletions.
21 changes: 21 additions & 0 deletions source/predicates/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import isEqual = require('lodash.isequal');
import {BasePredicate} from './base-predicate';
import {Predicate, PredicateOptions} from './predicate';
import ow from '..';
import {Shape} from './object';
import {exact} from '../utils/match-shape';

export class ArrayPredicate<T = unknown> extends Predicate<T[]> {
/**
Expand Down Expand Up @@ -159,4 +161,23 @@ export class ArrayPredicate<T = unknown> extends Predicate<T[]> {
}
}) as ArrayPredicate<any>;
}

/**
Test if the elements in the array exactly matches the elements placed at the same indices in the predicates array.
@param predicates - Predicates to test the array against. Describes what the tested array should look like.
@example
```
ow(['1', 2], ow.array.exactShape([ow.string, ow.number]));
```
*/
exactShape(predicates: Predicate[]) {
const shape = predicates as unknown as Shape;

return this.addValidator({
message: (_, label, message) => `${message.replace('Expected', 'Expected element')} in ${label}`,
validator: object => exact(object, shape, undefined, true)
});
}
}
4 changes: 2 additions & 2 deletions source/utils/match-shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Test if the `object` matches the `shape` exactly.
@param shape - Shape to test the object against.
@param parent - Name of the parent property.
*/
export function exact(object: Record<string, any>, shape: Shape, parent?: string): boolean | string {
export function exact(object: Record<string, any>, shape: Shape, parent?: string, isArray?: boolean): boolean | string {
const stack = generateStackTrace();
try {
const objectKeys = new Set<string>(Object.keys(object));
Expand Down Expand Up @@ -104,7 +104,7 @@ export function exact(object: Record<string, any>, shape: Shape, parent?: string
if (objectKeys.size > 0) {
const firstKey = [...objectKeys.keys()][0]!;
const label = parent ? `${parent}.${firstKey}` : firstKey;
return `Did not expect property \`${label}\` to exist, got \`${object[firstKey]}\``;
return `Did not expect ${isArray ? 'element' : 'property'} \`${label}\` to exist, got \`${object[firstKey]}\``;
}

return true;
Expand Down
18 changes: 18 additions & 0 deletions test/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,21 @@ test('array.ofType', t => {
ow(['foo', 'b'], 'foo', ow.array.ofType(ow.string.minLength(3)));
}, '(array `foo`) Expected string to have a minimum length of `3`, got `b`');
});

test('array.exactShape', t => {
t.notThrows(() => {
ow(['🦄', 2, 3, true, {isFirstCommit: true}], ow.array.exactShape([ow.string, ow.number, ow.number, ow.boolean, ow.object.exactShape({isFirstCommit: ow.boolean})]));
});

t.throws(() => {
ow(['🦄', 2, 'nope', true, {isFirstCommit: true}], ow.array.exactShape([ow.string, ow.number, ow.number, ow.boolean, ow.object.exactShape({isFirstCommit: ow.string})]));
}, 'Expected element `2` to be of type `number` but received type `string` in array');

t.throws(() => {
ow(['🦄', 'nope', {isFirstCommit: true}], ow.array.exactShape([ow.string, ow.string, ow.object.exactShape({isFirstCommit: ow.boolean}), ow.number, ow.boolean]));
}, 'Expected element `3` to be of type `number` but received type `undefined` in array');

t.throws(() => {
ow(['🦄', {isFirstCommit: true}, 'nope', 5, {accepted: false}], ow.array.exactShape([ow.string, ow.object.exactShape({isFirstCommit: ow.boolean}), ow.string]));
}, 'Did not expect element `3` to exist, got `5` in array');
});

0 comments on commit dbef144

Please sign in to comment.