UNPKG

5.99 kBJavaScriptView Raw
1import { deepEqual } from 'fast-equals';
2import { exact } from '../utils/match-shape.js';
3import ofType from '../utils/of-type.js';
4import { Predicate } from './predicate.js';
5export class ArrayPredicate extends Predicate {
6 /**
7 @hidden
8 */
9 constructor(options) {
10 super('array', options);
11 }
12 /**
13 Test an array to have a specific length.
14
15 @param length - The length of the array.
16 */
17 length(length) {
18 return this.addValidator({
19 message: (value, label) => `Expected ${label} to have length \`${length}\`, got \`${value.length}\``,
20 validator: value => value.length === length,
21 });
22 }
23 /**
24 Test an array to have a minimum length.
25
26 @param length - The minimum length of the array.
27 */
28 minLength(length) {
29 return this.addValidator({
30 message: (value, label) => `Expected ${label} to have a minimum length of \`${length}\`, got \`${value.length}\``,
31 validator: value => value.length >= length,
32 negatedMessage: (value, label) => `Expected ${label} to have a maximum length of \`${length - 1}\`, got \`${value.length}\``,
33 });
34 }
35 /**
36 Test an array to have a maximum length.
37
38 @param length - The maximum length of the array.
39 */
40 maxLength(length) {
41 return this.addValidator({
42 message: (value, label) => `Expected ${label} to have a maximum length of \`${length}\`, got \`${value.length}\``,
43 validator: value => value.length <= length,
44 negatedMessage: (value, label) => `Expected ${label} to have a minimum length of \`${length + 1}\`, got \`${value.length}\``,
45 });
46 }
47 /**
48 Test an array to start with a specific value. The value is tested by identity, not structure.
49
50 @param searchElement - The value that should be the start of the array.
51 */
52 startsWith(searchElement) {
53 return this.addValidator({
54 message: (value, label) => `Expected ${label} to start with \`${searchElement}\`, got \`${value[0]}\``,
55 validator: value => value[0] === searchElement,
56 });
57 }
58 /**
59 Test an array to end with a specific value. The value is tested by identity, not structure.
60
61 @param searchElement - The value that should be the end of the array.
62 */
63 endsWith(searchElement) {
64 return this.addValidator({
65 message: (value, label) => `Expected ${label} to end with \`${searchElement}\`, got \`${value.at(-1)}\``,
66 validator: value => value.at(-1) === searchElement,
67 });
68 }
69 /**
70 Test an array to include all the provided elements. The values are tested by identity, not structure.
71
72 @param searchElements - The values that should be included in the array.
73 */
74 includes(...searchElements) {
75 return this.addValidator({
76 message: (value, label) => `Expected ${label} to include all elements of \`${JSON.stringify(searchElements)}\`, got \`${JSON.stringify(value)}\``,
77 validator: value => searchElements.every(element => value.includes(element)),
78 });
79 }
80 /**
81 Test an array to include any of the provided elements. The values are tested by identity, not structure.
82
83 @param searchElements - The values that should be included in the array.
84 */
85 includesAny(...searchElements) {
86 return this.addValidator({
87 message: (value, label) => `Expected ${label} to include any element of \`${JSON.stringify(searchElements)}\`, got \`${JSON.stringify(value)}\``,
88 validator: value => searchElements.some(element => value.includes(element)),
89 });
90 }
91 /**
92 Test an array to be empty.
93 */
94 get empty() {
95 return this.addValidator({
96 message: (value, label) => `Expected ${label} to be empty, got \`${JSON.stringify(value)}\``,
97 validator: value => value.length === 0,
98 });
99 }
100 /**
101 Test an array to be not empty.
102 */
103 get nonEmpty() {
104 return this.addValidator({
105 message: (_, label) => `Expected ${label} to not be empty`,
106 validator: value => value.length > 0,
107 });
108 }
109 /**
110 Test an array to be deeply equal to the provided array.
111
112 @param expected - Expected value to match.
113 */
114 deepEqual(expected) {
115 return this.addValidator({
116 message: (value, label) => `Expected ${label} to be deeply equal to \`${JSON.stringify(expected)}\`, got \`${JSON.stringify(value)}\``,
117 validator: value => deepEqual(value, expected),
118 });
119 }
120 /**
121 Test all elements in the array to match to provided predicate.
122
123 @param predicate - The predicate that should be applied against every individual item.
124
125 @example
126 ```
127 ow(['a', 1], ow.array.ofType(ow.any(ow.string, ow.number)));
128 ```
129 */
130 ofType(predicate) {
131 // TODO [typescript@>=6] If higher-kinded types are supported natively by typescript, refactor `addValidator` to use them to avoid the usage of `any`. Otherwise, bump or remove this TODO.
132 return this.addValidator({
133 message: (_, label, error) => `(${label}) ${error}`,
134 validator: value => ofType(value, 'values', predicate),
135 });
136 }
137 /**
138 Test if the elements in the array exactly matches the elements placed at the same indices in the predicates array.
139
140 @param predicates - Predicates to test the array against. Describes what the tested array should look like.
141
142 @example
143 ```
144 ow(['1', 2], ow.array.exactShape([ow.string, ow.number]));
145 ```
146 */
147 exactShape(predicates) {
148 const shape = predicates;
149 return this.addValidator({
150 // eslint-disable-next-line @typescript-eslint/no-unsafe-call
151 message: (_, label, message) => `${message.replace('Expected', 'Expected element')} in ${label}`,
152 validator: object => exact(object, shape, undefined, true),
153 });
154 }
155}