Browse Source

Add array validators (#20)

iss58
Sam Verschueren 7 years ago
committed by Sindre Sorhus
parent
commit
3306c69fbe
  1. 1
      package.json
  2. 151
      source/lib/predicates/array.ts
  3. 8
      source/ow.ts
  4. 69
      source/test/array.ts
  5. 1
      tsconfig.json

1
package.json

@ -54,6 +54,7 @@
"codecov": "^3.0.0", "codecov": "^3.0.0",
"del-cli": "^1.1.0", "del-cli": "^1.1.0",
"license-webpack-plugin": "^1.1.1", "license-webpack-plugin": "^1.1.1",
"lodash.isequal": "^4.5.0",
"nyc": "^11.2.1", "nyc": "^11.2.1",
"tslint": "^5.8.0", "tslint": "^5.8.0",
"tslint-xo": "^0.2.0", "tslint-xo": "^0.2.0",

151
source/lib/predicates/array.ts

@ -0,0 +1,151 @@
import * as isEqual from 'lodash.isequal';
import {ow} from '../../ow';
import {Predicate, Context} from './predicate';
export class ArrayPredicate extends Predicate<any[]> {
constructor(context?: Context) {
super('array', context);
}
/**
* Test an array to have a specific length.
*
* @param length The length of the array.
*/
length(length: number) {
return this.addValidator({
message: value => `Expected array to have length \`${length}\`, got \`${value.length}\``,
validator: value => value.length === length
});
}
/**
* Test an array to have a minimum length.
*
* @param length The minimum length of the array.
*/
minLength(length: number) {
return this.addValidator({
message: value => `Expected array to have a minimum length of \`${length}\`, got \`${value.length}\``,
validator: value => value.length >= length
});
}
/**
* Test an array to have a maximum length.
*
* @param length The maximum length of the array.
*/
maxLength(length: number) {
return this.addValidator({
message: value => `Expected array to have a maximum length of \`${length}\`, got \`${value.length}\``,
validator: value => value.length <= length
});
}
/**
* Test an array to start with a specific value. The value is tested by identity, not structure.
*
* @param searchElement The value that should be the start of the array.
*/
startsWith(searchElement: any) {
return this.addValidator({
message: value => `Expected array to start with \`${searchElement}\`, got \`${value[0]}\``,
validator: value => value[0] === searchElement
});
}
/**
* Test an array to end with a specific value. The value is tested by identity, not structure.
*
* @param searchElement The value that should be the end of the array.
*/
endsWith(searchElement: any) {
return this.addValidator({
message: value => `Expected array to end with \`${searchElement}\`, got \`${value[value.length - 1]}\``,
validator: value => value[value.length - 1] === searchElement
});
}
/**
* Test an array to include all the provided elements. The values are tested by identity, not structure.
*
* @param searchElements The values that should be included in the array.
*/
includes(...searchElements: any[]) {
return this.addValidator({
message: value => `Expected array to include all elements of \`${JSON.stringify(searchElements)}\`, got \`${JSON.stringify(value)}\``,
validator: value => searchElements.every(el => value.indexOf(el) !== -1)
});
}
/**
* Test an array to include any of the provided elements. The values are tested by identity, not structure.
*
* @param searchElements The values that should be included in the array.
*/
includesAny(...searchElements: any[]) {
return this.addValidator({
message: value => `Expected array to include any element of \`${JSON.stringify(searchElements)}\`, got \`${JSON.stringify(value)}\``,
validator: value => searchElements.some(el => value.indexOf(el) !== -1)
});
}
/**
* Test an array to be empty.
*/
get empty() {
return this.addValidator({
message: value => `Expected array to be empty, got \`${JSON.stringify(value)}\``,
validator: value => value.length === 0
});
}
/**
* Test an array to be not empty.
*/
get nonEmpty() {
return this.addValidator({
message: () => 'Expected array to not be empty',
validator: value => value.length > 0
});
}
/**
* Test an array to be deeply equal to the provided array.
*
* @param expected Expected value to match.
*/
deepEqual(expected: any[]) {
return this.addValidator({
message: value => `Expected array to be deeply equal to \`${JSON.stringify(expected)}\`, got \`${JSON.stringify(value)}\``,
validator: value => isEqual(value, expected)
});
}
/**
* Test all elements in the array to match to provided predicate.
*
* @param predicate The predicate that should be applied against every individual item.
*/
ofType<T>(predicate: Predicate<T>) {
let error;
return this.addValidator({
message: () => error,
validator: value => {
try {
for (const item of value) {
ow(item, predicate);
}
return true;
} catch (err) {
error = err.message;
return false;
}
}
});
}
}

8
source/ow.ts

@ -3,6 +3,7 @@ import {Predicate, validatorSymbol} from './lib/predicates/predicate';
import {StringPredicate} from './lib/predicates/string'; import {StringPredicate} from './lib/predicates/string';
import {NumberPredicate} from './lib/predicates/number'; import {NumberPredicate} from './lib/predicates/number';
import {BooleanPredicate} from './lib/predicates/boolean'; import {BooleanPredicate} from './lib/predicates/boolean';
import {ArrayPredicate} from './lib/predicates/array';
export interface Ow { export interface Ow {
(value: any, predicate: Predicate): void; (value: any, predicate: Predicate): void;
@ -18,6 +19,10 @@ export interface Ow {
* Test the value to be a boolean. * Test the value to be a boolean.
*/ */
boolean: BooleanPredicate; boolean: BooleanPredicate;
/**
* Test the value to be an array.
*/
array: ArrayPredicate;
} }
const main = (value: any, predicate: Predicate) => { const main = (value: any, predicate: Predicate) => {
@ -38,6 +43,9 @@ Object.defineProperties(main, {
}, },
boolean: { boolean: {
get: () => new BooleanPredicate() get: () => new BooleanPredicate()
},
array: {
get: () => new ArrayPredicate()
} }
}); });

69
source/test/array.ts

@ -0,0 +1,69 @@
import test from 'ava';
import * as m from '..';
test('array', t => {
t.notThrows(() => m([], m.array));
t.throws(() => m('12', m.array), 'Expected argument to be of type `array` but received type `string`');
});
test('array.length', t => {
t.notThrows(() => m(['foo'], m.array.length(1)));
t.notThrows(() => m(['foo', 'bar'], m.array.length(2)));
t.throws(() => m(['foo'], m.array.length(2)), 'Expected array to have length `2`, got `1`');
});
test('array.minLength', t => {
t.notThrows(() => m(['foo'], m.array.minLength(1)));
t.notThrows(() => m(['foo', 'bar'], m.array.minLength(1)));
t.throws(() => m(['foo'], m.array.minLength(2)), 'Expected array to have a minimum length of `2`, got `1`');
});
test('array.maxLength', t => {
t.notThrows(() => m(['foo'], m.array.maxLength(1)));
t.notThrows(() => m(['foo', 'bar'], m.array.maxLength(4)));
t.throws(() => m(['foo', 'bar'], m.array.maxLength(1)), 'Expected array to have a maximum length of `1`, got `2`');
});
test('array.startsWith', t => {
t.notThrows(() => m(['foo', 'bar'], m.array.startsWith('foo')));
t.throws(() => m(['foo', 'bar'], m.array.startsWith('bar')), 'Expected array to start with `bar`, got `foo`');
});
test('array.endsWith', t => {
t.notThrows(() => m(['foo', 'bar'], m.array.endsWith('bar')));
t.throws(() => m(['foo', 'bar'], m.array.endsWith('foo')), 'Expected array to end with `foo`, got `bar`');
});
test('array.includes', t => {
t.notThrows(() => m(['foo', 'bar'], m.array.includes('foo')));
t.notThrows(() => m(['foo', 'bar', 'unicorn'], m.array.includes('foo', 'bar')));
t.throws(() => m(['foo', 'bar'], m.array.includes('foo', 'unicorn')), 'Expected array to include all elements of `["foo","unicorn"]`, got `["foo","bar"]`');
});
test('array.includesAny', t => {
t.notThrows(() => m(['foo', 'bar'], m.array.includesAny('foo')));
t.notThrows(() => m(['foo', 'bar', 'unicorn'], m.array.includesAny('unicorn', 'rainbow')));
t.throws(() => m(['foo', 'bar'], m.array.includesAny('unicorn')), 'Expected array to include any element of `["unicorn"]`, got `["foo","bar"]`');
});
test('array.empty', t => {
t.notThrows(() => m([], m.array.empty));
t.throws(() => m(['foo'], m.array.empty), 'Expected array to be empty, got `["foo"]`');
});
test('array.nonEmpty', t => {
t.notThrows(() => m(['foo'], m.array.nonEmpty));
t.throws(() => m([], m.array.nonEmpty), 'Expected array to not be empty');
});
test('array.deepEqual', t => {
t.notThrows(() => m(['foo'], m.array.deepEqual(['foo'])));
t.notThrows(() => m(['foo', {id: 1}], m.array.deepEqual(['foo', {id: 1}])));
t.throws(() => m(['foo', {id: 1}], m.array.deepEqual(['foo', {id: 2}])), 'Expected array to be deeply equal to `["foo",{"id":2}]`, got `["foo",{"id":1}]`');
});
test('array.ofType', t => {
t.notThrows(() => m(['foo', 'bar'], m.array.ofType(m.string)));
t.notThrows(() => m(['foo', 'bar'], m.array.ofType(m.string.minLength(3))));
t.throws(() => m(['foo', 'b'], m.array.ofType(m.string.minLength(3))), 'Expected string to have a minimum length of `3`, got `b`');
});

1
tsconfig.json

@ -17,7 +17,6 @@
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true, "strictNullChecks": true,
"strictFunctionTypes": true, "strictFunctionTypes": true,
"alwaysStrict": true "alwaysStrict": true

Loading…
Cancel
Save