@dungarees/core 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/application.d.ts +27 -21
- package/application.js +14 -15
- package/error.d.ts +6 -0
- package/error.js +14 -0
- package/error.test.d.ts +1 -0
- package/error.test.js +44 -0
- package/event.d.ts +12 -0
- package/event.js +8 -0
- package/event.test.d.ts +1 -0
- package/event.test.js +22 -0
- package/fake.d.ts +5 -5
- package/fake.js +5 -10
- package/fake.test.js +8 -0
- package/marbles-vitest.d.ts +18 -14
- package/marbles-vitest.js +57 -52
- package/marbles-vitest.test.js +26 -14
- package/marbles.d.ts +5 -4
- package/marbles.js +39 -36
- package/package.json +28 -2
- package/tsconfig.base.json +35 -0
- package/type-util.d.ts +17 -9
- package/type-util.test.js +71 -59
- package/util.d.ts +30 -12
- package/util.js +102 -82
- package/util.test.js +239 -22
package/util.js
CHANGED
|
@@ -36,107 +36,89 @@ export const toCamelCase = (segments) => {
|
|
|
36
36
|
};
|
|
37
37
|
export const kebabCase2camelCase = (kebabCase) => toCamelCase(fromKebabCase(kebabCase));
|
|
38
38
|
export const camelCase2kebabCase = (camelCase) => toKebabCase(fromCamelCase(camelCase));
|
|
39
|
+
// `null` has to be excluded explicitly: `typeof null` is `'object'`, so without this a null value
|
|
40
|
+
// reaches the key walks below and throws instead of simply not matching.
|
|
41
|
+
const isKeyedObject = (input) => typeof input === 'object' && input !== null;
|
|
42
|
+
const isDataViewEqual = (a, b) => {
|
|
43
|
+
if (a.byteLength !== b.byteLength) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
for (let index = a.byteLength; index-- !== 0;) {
|
|
47
|
+
if (a.getUint8(index) !== b.getUint8(index)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
// `valueOf` and `toString` exist on every object, so the only useful question is whether this one
|
|
54
|
+
// replaced them with something that describes its value.
|
|
55
|
+
const hasOwnValueOf = (value) => value.valueOf !== Object.prototype.valueOf && typeof value.valueOf === 'function';
|
|
56
|
+
// Called through a parameter whose own `toString` is declared, so the call is not read as a
|
|
57
|
+
// stringification of a plain object.
|
|
58
|
+
const toOwnString = (value) => value.toString();
|
|
59
|
+
const hasOwnToString = (value) => value.toString !== Object.prototype.toString && typeof value.toString === 'function';
|
|
39
60
|
export const isDeepEqual = (a, b) => {
|
|
40
61
|
// in order to support circular references we have to keep track of visited objects.
|
|
41
62
|
// for that reason we have to create new function for each invocation.
|
|
42
63
|
const visited = new WeakMap();
|
|
43
|
-
// eslint-disable-next-line complexity
|
|
44
64
|
const inner = (a, b) => {
|
|
45
65
|
// in case strict equality - there is nothing to check anymore.
|
|
46
66
|
if (a === b) {
|
|
47
67
|
return true;
|
|
48
68
|
}
|
|
49
|
-
// in case any of values is not an object, there is nothing to do, except to check strict
|
|
50
|
-
|
|
69
|
+
// in case any of values is not an object, there is nothing to do, except to check strict
|
|
70
|
+
// equality.
|
|
71
|
+
if (!isKeyedObject(a) || !isKeyedObject(b)) {
|
|
51
72
|
// looks weird, but it is most efficient way to test NaN.
|
|
52
|
-
// otherwise we have to involve Number.isNaN, which causes context switch and therefore is
|
|
53
|
-
//
|
|
73
|
+
// otherwise we have to involve Number.isNaN, which causes context switch and therefore is
|
|
74
|
+
// slower.
|
|
54
75
|
return a !== a && b !== b;
|
|
55
76
|
}
|
|
56
77
|
// if constructors are different, objects are definitely not equal.
|
|
57
78
|
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
|
|
58
79
|
return false;
|
|
59
80
|
}
|
|
60
|
-
|
|
61
|
-
if (constructor === Date) {
|
|
81
|
+
if (a instanceof Date && b instanceof Date) {
|
|
62
82
|
return a.getTime() === b.getTime();
|
|
63
83
|
}
|
|
64
|
-
if (
|
|
84
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
65
85
|
return a.source === b.source && a.flags === b.flags;
|
|
66
86
|
}
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
for (const value of a) {
|
|
72
|
-
if (!b.has(value)) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return true;
|
|
87
|
+
if (a instanceof Set && b instanceof Set) {
|
|
88
|
+
return a.size === b.size && [...a].every((value) => b.has(value));
|
|
77
89
|
}
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
if (a.byteLength !== b.byteLength)
|
|
89
|
-
return false;
|
|
90
|
-
for (let i = a.byteLength; i-- !== 0;) {
|
|
91
|
-
if (a.getUint8(i) !== b.getUint8(i)) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return true;
|
|
90
|
+
if (a instanceof ArrayBuffer && b instanceof ArrayBuffer) {
|
|
91
|
+
return isDataViewEqual(new DataView(a), new DataView(b));
|
|
92
|
+
}
|
|
93
|
+
if (a instanceof DataView && b instanceof DataView) {
|
|
94
|
+
return isDataViewEqual(a, b);
|
|
95
|
+
}
|
|
96
|
+
// this is a TypedArray.
|
|
97
|
+
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
|
|
98
|
+
return isDataViewEqual(new DataView(a.buffer), new DataView(b.buffer));
|
|
96
99
|
}
|
|
97
100
|
// Check circular references
|
|
98
|
-
if (visited.
|
|
101
|
+
if (visited.get(a) === b) {
|
|
99
102
|
return true;
|
|
100
103
|
}
|
|
101
104
|
visited.set(a, b);
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
for (let i = a.length; i-- !== 0;) {
|
|
107
|
-
if (!inner(a[i], b[i])) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return true;
|
|
105
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
106
|
+
return a.length === b.length && a.every((item, index) => inner(item, b[index]));
|
|
112
107
|
}
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
for (const entry of a) {
|
|
118
|
-
if (!b.has(entry[0]) || !inner(entry[1], b.get(entry[0]))) {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return true;
|
|
108
|
+
if (a instanceof Map && b instanceof Map) {
|
|
109
|
+
return (a.size === b.size && [...a].every(([key, value]) => b.has(key) && inner(value, b.get(key))));
|
|
123
110
|
}
|
|
124
|
-
// at this point, we've handled all possible data containers and we can compare objects as
|
|
125
|
-
|
|
111
|
+
// at this point, we've handled all possible data containers and we can compare objects as
|
|
112
|
+
// plain.
|
|
113
|
+
if (hasOwnValueOf(a) && hasOwnValueOf(b)) {
|
|
126
114
|
return a.valueOf() === b.valueOf();
|
|
127
115
|
}
|
|
128
|
-
if (a
|
|
129
|
-
return a
|
|
116
|
+
if (hasOwnToString(a) && hasOwnToString(b)) {
|
|
117
|
+
return toOwnString(a) === toOwnString(b);
|
|
130
118
|
}
|
|
131
119
|
const aKeys = Object.keys(a);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
key = aKeys[l];
|
|
135
|
-
if (!Object.hasOwn(b, key) || !inner(a[key], b[key])) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return Object.keys(b).length === aKeys.length;
|
|
120
|
+
return (aKeys.length === Object.keys(b).length &&
|
|
121
|
+
aKeys.every((key) => Object.hasOwn(b, key) && inner(a[key], b[key])));
|
|
140
122
|
};
|
|
141
123
|
return inner(a, b);
|
|
142
124
|
};
|
|
@@ -144,19 +126,17 @@ export const deepEqualPartial = (actual, expected) => {
|
|
|
144
126
|
if (actual === undefined || actual === null) {
|
|
145
127
|
return true;
|
|
146
128
|
}
|
|
147
|
-
|
|
148
|
-
if (!isObject(expected)) {
|
|
129
|
+
if (!isKeyedObject(expected)) {
|
|
149
130
|
return expected === actual;
|
|
150
131
|
}
|
|
151
|
-
if (!
|
|
132
|
+
if (!isKeyedObject(actual)) {
|
|
152
133
|
return false;
|
|
153
134
|
}
|
|
154
135
|
return Object.keys(actual).every((key) => {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return actual[key] === expected[key];
|
|
136
|
+
const value = actual[key];
|
|
137
|
+
return value instanceof Object
|
|
138
|
+
? deepEqualPartial(expected[key], value)
|
|
139
|
+
: value === expected[key];
|
|
160
140
|
});
|
|
161
141
|
};
|
|
162
142
|
export const findByPattern = (patterns, itemToMatch) => patterns
|
|
@@ -210,7 +190,7 @@ export const assertTypeByGuard = ({ value, guard, message, }) => {
|
|
|
210
190
|
}
|
|
211
191
|
return value;
|
|
212
192
|
};
|
|
213
|
-
export const assertPredicate = ({ value, predicate, message
|
|
193
|
+
export const assertPredicate = ({ value, predicate, message }) => {
|
|
214
194
|
if (!predicate(value)) {
|
|
215
195
|
throw new Error(getErrorMessage(message, value));
|
|
216
196
|
}
|
|
@@ -222,15 +202,55 @@ export const assertImpossible = (message) => {
|
|
|
222
202
|
const getErrorMessage = (message, value) => typeof message === 'function' ? message(value) : message;
|
|
223
203
|
export const unPrototypeProperties = (obj, keys) => {
|
|
224
204
|
const propertyEntries = keys.map((key) => {
|
|
225
|
-
const
|
|
226
|
-
return [key,
|
|
205
|
+
const property = obj[key];
|
|
206
|
+
return [key, isFunction(property) ? property.bind(obj) : property];
|
|
227
207
|
});
|
|
228
208
|
return Object.fromEntries(propertyEntries);
|
|
229
209
|
};
|
|
230
210
|
const isFunction = (value) => value instanceof Function;
|
|
231
|
-
export const
|
|
232
|
-
|
|
211
|
+
export const boolFromThrow = (fn) => {
|
|
212
|
+
try {
|
|
213
|
+
fn();
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
export const boolFromThrowAsync = async (fn) => {
|
|
221
|
+
try {
|
|
222
|
+
await fn();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
233
228
|
};
|
|
229
|
+
export function mapConst(array, transformer) {
|
|
230
|
+
return transformer === undefined
|
|
231
|
+
? (t) => {
|
|
232
|
+
return array.map((item, index) => t(item, index));
|
|
233
|
+
}
|
|
234
|
+
: array.map((item, index) => transformer(item, index));
|
|
235
|
+
}
|
|
236
|
+
export function mapConstKeysToEntries(array, transformer) {
|
|
237
|
+
return transformer === undefined
|
|
238
|
+
? (t) => {
|
|
239
|
+
return array.map((item, index) => [
|
|
240
|
+
item,
|
|
241
|
+
t(item, index),
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
: array.map((item, index) => [
|
|
245
|
+
item,
|
|
246
|
+
transformer(item, index),
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
234
249
|
export const objectFromConstEntries = (entries) => {
|
|
235
250
|
return Object.fromEntries(entries);
|
|
236
251
|
};
|
|
252
|
+
export function mapObjectFromKeys(keys, transformer) {
|
|
253
|
+
return transformer === undefined
|
|
254
|
+
? (t) => objectFromConstEntries(mapConstKeysToEntries(keys)(t))
|
|
255
|
+
: objectFromConstEntries(mapConstKeysToEntries(keys, transformer));
|
|
256
|
+
}
|
package/util.test.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { assertDefined, assertPredicate, assertTypeByGuard,
|
|
2
|
-
import {
|
|
3
|
-
import { expect, test } from 'vitest';
|
|
1
|
+
import { assertDefined, assertImpossible, assertPredicate, assertTypeByGuard, boolFromThrow, boolFromThrowAsync, camelCase2kebabCase, capitalize, deepEqualPartial, findByPattern, isDeepEqual, isDefined, join, kebabCase2camelCase, makeObjectFromStringLiteral, mapConst, mapConstKeysToEntries, mapObjectFromKeys, objectFromConstEntries, optionalPatternToList, pluralize, split, unPrototypeProperties, } from './util.js';
|
|
2
|
+
import { expect, expectTypeOf, test } from 'vitest';
|
|
4
3
|
test('makeObjectFromStringLiteral', () => {
|
|
5
4
|
const obj = makeObjectFromStringLiteral('key', 1);
|
|
6
5
|
expect(obj['key']).toBe(1);
|
|
7
6
|
});
|
|
8
7
|
test('split is working in a typesafe way', () => {
|
|
9
8
|
const splitted = split('a.a.a', '.');
|
|
10
|
-
|
|
9
|
+
expectTypeOf().toEqualTypeOf();
|
|
11
10
|
expect(splitted).toEqual(['a', 'a', 'a']);
|
|
12
11
|
});
|
|
13
12
|
test('join is working in a typesafe way', () => {
|
|
14
13
|
const joined = join(['a', 'a', 'a'], '.');
|
|
15
|
-
|
|
14
|
+
expectTypeOf().toEqualTypeOf();
|
|
16
15
|
expect(joined).toBe('a.a.a');
|
|
17
16
|
});
|
|
18
17
|
test('capitalize', () => {
|
|
19
18
|
const capitalized = capitalize('apple');
|
|
20
|
-
|
|
19
|
+
expectTypeOf().toEqualTypeOf();
|
|
21
20
|
expect(capitalized).toBe('Apple');
|
|
22
21
|
});
|
|
23
22
|
test('pluralize', () => {
|
|
@@ -34,14 +33,14 @@ test('kebabCase2camelCase', () => {
|
|
|
34
33
|
const camelCase = 'firstSecondThird';
|
|
35
34
|
const output = kebabCase2camelCase(kebabCase);
|
|
36
35
|
expect(output).toBe(camelCase);
|
|
37
|
-
|
|
36
|
+
expectTypeOf().toEqualTypeOf();
|
|
38
37
|
});
|
|
39
38
|
test('camelCase2kebabCase', () => {
|
|
40
39
|
const kebabCase = 'first-second-third';
|
|
41
40
|
const camelCase = 'firstSecondThird';
|
|
42
41
|
const output = camelCase2kebabCase(camelCase);
|
|
43
42
|
expect(output).toBe(kebabCase);
|
|
44
|
-
|
|
43
|
+
expectTypeOf().toEqualTypeOf();
|
|
45
44
|
});
|
|
46
45
|
test('deepEqualPartial', () => {
|
|
47
46
|
expect(deepEqualPartial({}, {})).toBe(true);
|
|
@@ -52,18 +51,22 @@ test('deepEqualPartial', () => {
|
|
|
52
51
|
expect(deepEqualPartial({ foo: { baz: 'bar' } }, { foo: { baz: 'bar' }, quux: 'kill me' })).toBe(true);
|
|
53
52
|
expect(deepEqualPartial({ foo: { baz: 'baz' } }, { foo: { baz: 'bar' }, quux: 'kill me' })).toBe(false);
|
|
54
53
|
});
|
|
54
|
+
test('deepEqualPartial treats a null expected as unequal rather than throwing', () => {
|
|
55
|
+
expect(deepEqualPartial({ baz: 54 }, null)).toBe(false);
|
|
56
|
+
expect(deepEqualPartial({ baz: 54 }, undefined)).toBe(false);
|
|
57
|
+
expect(deepEqualPartial(null, null)).toBe(true);
|
|
58
|
+
});
|
|
55
59
|
test('findByPattern', () => {
|
|
56
|
-
// eslint-disable-next-line
|
|
57
60
|
const value1 = findByPattern([], '');
|
|
58
|
-
|
|
61
|
+
expectTypeOf().toEqualTypeOf();
|
|
59
62
|
expect(value1).toBeUndefined();
|
|
60
63
|
// @ts-expect-error itemToMatch has to follow the pattern type
|
|
61
64
|
findByPattern([], {});
|
|
62
65
|
// @ts-expect-error itemToMatch type has to be a serializable type
|
|
63
66
|
const _ = (findByPattern);
|
|
64
|
-
|
|
67
|
+
expect(_).toBeDefined();
|
|
65
68
|
const value2 = findByPattern([{ pattern: 'ab', value: 1 }], 'ab');
|
|
66
|
-
|
|
69
|
+
expectTypeOf().toEqualTypeOf();
|
|
67
70
|
expect(value2).toBe(1);
|
|
68
71
|
const value3 = findByPattern([{ pattern: 'ab', value: 1 }], 'no-match');
|
|
69
72
|
expect(value3).toBe(undefined);
|
|
@@ -103,12 +106,12 @@ test('findByPattern', () => {
|
|
|
103
106
|
});
|
|
104
107
|
test('optionalPatternToList', () => {
|
|
105
108
|
const list1 = optionalPatternToList(1);
|
|
106
|
-
|
|
109
|
+
expectTypeOf().toEqualTypeOf();
|
|
107
110
|
expect(list1).toEqual([{ value: 1 }]);
|
|
108
111
|
const list2 = optionalPatternToList([{ value: 1 }]);
|
|
109
|
-
|
|
112
|
+
expectTypeOf().toEqualTypeOf();
|
|
110
113
|
expect(list2).toEqual([{ value: 1 }]);
|
|
111
|
-
|
|
114
|
+
expectTypeOf().toEqualTypeOf();
|
|
112
115
|
});
|
|
113
116
|
test('isDefined', () => {
|
|
114
117
|
expect(isDefined(0)).toStrictEqual(true);
|
|
@@ -164,7 +167,7 @@ test('unPrototypeProperties', () => {
|
|
|
164
167
|
}
|
|
165
168
|
const instance = new Test(1);
|
|
166
169
|
const noPrototype = unPrototypeProperties(instance, ['method']);
|
|
167
|
-
|
|
170
|
+
expectTypeOf().toEqualTypeOf();
|
|
168
171
|
expect(noPrototype.method()).toBe(1);
|
|
169
172
|
const method = noPrototype.method;
|
|
170
173
|
expect(method()).toBe(1);
|
|
@@ -172,23 +175,87 @@ test('unPrototypeProperties', () => {
|
|
|
172
175
|
expect(Object.hasOwn(noPrototype, 'method2')).toBe(false);
|
|
173
176
|
const clone = { ...noPrototype };
|
|
174
177
|
expect(clone.method()).toBe(1);
|
|
175
|
-
|
|
176
|
-
expect(() => clone.method2()).toThrow('method2 is not a function');
|
|
178
|
+
expect(Object.hasOwn(clone, 'method2')).toBe(false);
|
|
177
179
|
// @ts-expect-error method3 is not a property of clone
|
|
178
180
|
unPrototypeProperties(clone, ['method3']);
|
|
179
181
|
});
|
|
180
182
|
test('mapConst', () => {
|
|
181
183
|
const input = ['a', 'b', 'c'];
|
|
182
184
|
const output = mapConst(input)((value) => `${value}-const`);
|
|
183
|
-
|
|
185
|
+
expectTypeOf().toEqualTypeOf();
|
|
184
186
|
expect(output).toEqual(['a-const', 'b-const', 'c-const']);
|
|
185
187
|
});
|
|
188
|
+
test('mapConst expets a lambda with a correct return type', () => {
|
|
189
|
+
const input = ['a', 'b', 'c'];
|
|
190
|
+
// @ts-expect-error The return type of the lambda should depend on the input value
|
|
191
|
+
mapConst(input)(() => 1);
|
|
192
|
+
});
|
|
186
193
|
test('mapConst infers input type from array', () => {
|
|
187
194
|
const input = ['a', 'b', 'c'];
|
|
188
|
-
|
|
189
|
-
|
|
195
|
+
const output = [];
|
|
196
|
+
mapConst(input)((value, index) => {
|
|
197
|
+
expectTypeOf().toEqualTypeOf();
|
|
198
|
+
expectTypeOf(index).toEqualTypeOf();
|
|
199
|
+
output.push(index);
|
|
200
|
+
return `${value}-const`;
|
|
201
|
+
});
|
|
202
|
+
expect(output).toEqual([0, 1, 2]);
|
|
203
|
+
});
|
|
204
|
+
test('mapConst non-curried infers input type from array', () => {
|
|
205
|
+
const input = ['a', 'b', 'c'];
|
|
206
|
+
const output = [];
|
|
207
|
+
const result = mapConst(input, (value, index) => {
|
|
208
|
+
expectTypeOf().toEqualTypeOf();
|
|
209
|
+
expectTypeOf(index).toEqualTypeOf();
|
|
210
|
+
output.push(index);
|
|
211
|
+
return 1;
|
|
212
|
+
});
|
|
213
|
+
expectTypeOf().toEqualTypeOf();
|
|
214
|
+
expect(result).toEqual([1, 1, 1]);
|
|
215
|
+
expect(output).toEqual([0, 1, 2]);
|
|
216
|
+
});
|
|
217
|
+
test('mapConstKeysToEntries infers input type from array', () => {
|
|
218
|
+
const input = ['a', 'b', 'c'];
|
|
219
|
+
const output = mapConstKeysToEntries(input)((value) => `${value}-const`);
|
|
220
|
+
expectTypeOf().toEqualTypeOf();
|
|
221
|
+
expect(output).toEqual([
|
|
222
|
+
['a', 'a-const'],
|
|
223
|
+
['b', 'b-const'],
|
|
224
|
+
['c', 'c-const'],
|
|
225
|
+
]);
|
|
226
|
+
});
|
|
227
|
+
test('mapConstKeysToEntries expets a lambda with a correct return type', () => {
|
|
228
|
+
const input = ['a', 'b', 'c'];
|
|
229
|
+
// @ts-expect-error The return type of the lambda should depend on the input value
|
|
230
|
+
mapConstKeysToEntries(input)(() => 1);
|
|
231
|
+
});
|
|
232
|
+
test('mapConstKeysToEntries infers input type from array', () => {
|
|
233
|
+
const input = ['a', 'b', 'c'];
|
|
234
|
+
const output = [];
|
|
235
|
+
const a = mapConstKeysToEntries(input, (value, index) => {
|
|
236
|
+
expectTypeOf().toEqualTypeOf();
|
|
237
|
+
expectTypeOf(index).toEqualTypeOf();
|
|
238
|
+
output.push(index);
|
|
239
|
+
return 1;
|
|
240
|
+
});
|
|
241
|
+
expectTypeOf().toEqualTypeOf();
|
|
242
|
+
expect(a).toEqual([
|
|
243
|
+
['a', 1],
|
|
244
|
+
['b', 1],
|
|
245
|
+
['c', 1],
|
|
246
|
+
]);
|
|
247
|
+
expect(output).toEqual([0, 1, 2]);
|
|
248
|
+
});
|
|
249
|
+
test('mapConstKeysToEntries infers input type from array', () => {
|
|
250
|
+
const input = ['a', 'b', 'c'];
|
|
251
|
+
const output = [];
|
|
252
|
+
mapConstKeysToEntries(input)((value, index) => {
|
|
253
|
+
expectTypeOf().toEqualTypeOf();
|
|
254
|
+
expectTypeOf(index).toEqualTypeOf();
|
|
255
|
+
output.push(index);
|
|
190
256
|
return `${value}-const`;
|
|
191
257
|
});
|
|
258
|
+
expect(output).toEqual([0, 1, 2]);
|
|
192
259
|
});
|
|
193
260
|
test('objectFromConstEntries', () => {
|
|
194
261
|
const entries = [
|
|
@@ -197,6 +264,156 @@ test('objectFromConstEntries', () => {
|
|
|
197
264
|
['c', 3],
|
|
198
265
|
];
|
|
199
266
|
const obj = objectFromConstEntries(entries);
|
|
200
|
-
|
|
267
|
+
expectTypeOf().toEqualTypeOf();
|
|
201
268
|
expect(obj).toEqual({ a: 1, b: 2, c: 3 });
|
|
202
269
|
});
|
|
270
|
+
test('mapObjectFromKeys', () => {
|
|
271
|
+
const keys = ['a', 'b', 'c'];
|
|
272
|
+
const obj = mapObjectFromKeys(keys)((key, index) => {
|
|
273
|
+
expectTypeOf().toEqualTypeOf();
|
|
274
|
+
expectTypeOf(index).toEqualTypeOf();
|
|
275
|
+
return `${key}-const`;
|
|
276
|
+
});
|
|
277
|
+
expectTypeOf().toEqualTypeOf();
|
|
278
|
+
expect(obj).toEqual({ a: 'a-const', b: 'b-const', c: 'c-const' });
|
|
279
|
+
});
|
|
280
|
+
test('mapObjectFromKeys non-curried infers input type from array', () => {
|
|
281
|
+
const keys = ['a', 'b', 'c'];
|
|
282
|
+
const obj = mapObjectFromKeys(keys, (key, index) => {
|
|
283
|
+
expectTypeOf().toEqualTypeOf();
|
|
284
|
+
expectTypeOf(index).toEqualTypeOf();
|
|
285
|
+
return 1;
|
|
286
|
+
});
|
|
287
|
+
expectTypeOf().toEqualTypeOf();
|
|
288
|
+
expect(obj).toEqual({ a: 1, b: 1, c: 1 });
|
|
289
|
+
});
|
|
290
|
+
test('boolFromThrow returns true when function does not throw', () => {
|
|
291
|
+
expect(boolFromThrow(() => { })).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
test('boolFromThrow returns false when function throws', () => {
|
|
294
|
+
expect(boolFromThrow(() => {
|
|
295
|
+
throw new Error('fail');
|
|
296
|
+
})).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
test('boolFromThrowAsync returns true when async function does not throw', async () => {
|
|
299
|
+
await expect(boolFromThrowAsync(async () => { })).resolves.toBe(true);
|
|
300
|
+
});
|
|
301
|
+
test('boolFromThrowAsync returns false when async function rejects', async () => {
|
|
302
|
+
await expect(boolFromThrowAsync(async () => {
|
|
303
|
+
throw new Error('fail');
|
|
304
|
+
})).resolves.toBe(false);
|
|
305
|
+
});
|
|
306
|
+
test('isDeepEqual compares primitives by strict equality, with NaN equal to itself', () => {
|
|
307
|
+
expect(isDeepEqual(1, 1)).toBe(true);
|
|
308
|
+
expect(isDeepEqual(1, 2)).toBe(false);
|
|
309
|
+
expect(isDeepEqual('a', 'a')).toBe(true);
|
|
310
|
+
expect(isDeepEqual(1, '1')).toBe(false);
|
|
311
|
+
expect(isDeepEqual(NaN, NaN)).toBe(true);
|
|
312
|
+
expect(isDeepEqual(undefined, undefined)).toBe(true);
|
|
313
|
+
expect(isDeepEqual(null, null)).toBe(true);
|
|
314
|
+
expect(isDeepEqual(null, {})).toBe(false);
|
|
315
|
+
expect(isDeepEqual({}, null)).toBe(false);
|
|
316
|
+
});
|
|
317
|
+
test('isDeepEqual compares plain objects by their own keys', () => {
|
|
318
|
+
expect(isDeepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
|
|
319
|
+
expect(isDeepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
|
|
320
|
+
expect(isDeepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
|
|
321
|
+
expect(isDeepEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } })).toBe(true);
|
|
322
|
+
expect(isDeepEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } })).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
test('isDeepEqual compares arrays element-wise', () => {
|
|
325
|
+
expect(isDeepEqual([1, 2, 3], [1, 2, 3])).toBe(true);
|
|
326
|
+
expect(isDeepEqual([1, 2], [1, 2, 3])).toBe(false);
|
|
327
|
+
expect(isDeepEqual([{ a: 1 }], [{ a: 1 }])).toBe(true);
|
|
328
|
+
expect(isDeepEqual([{ a: 1 }], [{ a: 2 }])).toBe(false);
|
|
329
|
+
expect(isDeepEqual([], [])).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
test('isDeepEqual treats a different prototype as unequal', () => {
|
|
332
|
+
class A {
|
|
333
|
+
constructor(x) {
|
|
334
|
+
this.x = x;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
class B {
|
|
338
|
+
constructor(x) {
|
|
339
|
+
this.x = x;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
expect(isDeepEqual(new A(1), new B(1))).toBe(false);
|
|
343
|
+
expect(isDeepEqual([1], { 0: 1 })).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
test('isDeepEqual compares Dates by time', () => {
|
|
346
|
+
expect(isDeepEqual(new Date(1000), new Date(1000))).toBe(true);
|
|
347
|
+
expect(isDeepEqual(new Date(1000), new Date(2000))).toBe(false);
|
|
348
|
+
});
|
|
349
|
+
test('isDeepEqual compares RegExps by source and flags', () => {
|
|
350
|
+
expect(isDeepEqual(/ab+/g, /ab+/g)).toBe(true);
|
|
351
|
+
expect(isDeepEqual(/ab+/g, /ab+/i)).toBe(false);
|
|
352
|
+
expect(isDeepEqual(/ab+/g, /ac+/g)).toBe(false);
|
|
353
|
+
});
|
|
354
|
+
test('isDeepEqual compares Sets by size and membership', () => {
|
|
355
|
+
expect(isDeepEqual(new Set([1, 2]), new Set([1, 2]))).toBe(true);
|
|
356
|
+
expect(isDeepEqual(new Set([1, 2]), new Set([2, 1]))).toBe(true);
|
|
357
|
+
expect(isDeepEqual(new Set([1, 2]), new Set([1, 3]))).toBe(false);
|
|
358
|
+
expect(isDeepEqual(new Set([1]), new Set([1, 2]))).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
test('isDeepEqual compares Maps by size and entries', () => {
|
|
361
|
+
expect(isDeepEqual(new Map([['a', 1]]), new Map([['a', 1]]))).toBe(true);
|
|
362
|
+
expect(isDeepEqual(new Map([['a', 1]]), new Map([['a', 2]]))).toBe(false);
|
|
363
|
+
expect(isDeepEqual(new Map([['a', 1]]), new Map([['b', 1]]))).toBe(false);
|
|
364
|
+
expect(isDeepEqual(new Map([['a', { b: 1 }]]), new Map([['a', { b: 1 }]]))).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
test('isDeepEqual compares ArrayBuffers and typed arrays byte by byte', () => {
|
|
367
|
+
const bufferOf = (bytes) => new Uint8Array(bytes).buffer;
|
|
368
|
+
expect(isDeepEqual(bufferOf([1, 2, 3]), bufferOf([1, 2, 3]))).toBe(true);
|
|
369
|
+
expect(isDeepEqual(bufferOf([1, 2, 3]), bufferOf([1, 2, 4]))).toBe(false);
|
|
370
|
+
expect(isDeepEqual(bufferOf([1, 2]), bufferOf([1, 2, 3]))).toBe(false);
|
|
371
|
+
expect(isDeepEqual(new Uint8Array([1, 2]), new Uint8Array([1, 2]))).toBe(true);
|
|
372
|
+
expect(isDeepEqual(new Uint8Array([1, 2]), new Uint8Array([1, 3]))).toBe(false);
|
|
373
|
+
expect(isDeepEqual(new DataView(bufferOf([9])), new DataView(bufferOf([9])))).toBe(true);
|
|
374
|
+
expect(isDeepEqual(new DataView(bufferOf([9])), new DataView(bufferOf([8])))).toBe(false);
|
|
375
|
+
});
|
|
376
|
+
test('isDeepEqual survives circular references', () => {
|
|
377
|
+
const a = { name: 'a' };
|
|
378
|
+
a.self = a;
|
|
379
|
+
const b = { name: 'a' };
|
|
380
|
+
b.self = b;
|
|
381
|
+
expect(isDeepEqual(a, b)).toBe(true);
|
|
382
|
+
const c = { name: 'c' };
|
|
383
|
+
c.self = c;
|
|
384
|
+
expect(isDeepEqual(a, c)).toBe(false);
|
|
385
|
+
});
|
|
386
|
+
test('isDeepEqual prefers a custom valueOf when one is defined', () => {
|
|
387
|
+
class Money {
|
|
388
|
+
constructor(cents) {
|
|
389
|
+
this.cents = cents;
|
|
390
|
+
}
|
|
391
|
+
valueOf() {
|
|
392
|
+
return this.cents;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
expect(isDeepEqual(new Money(100), new Money(100))).toBe(true);
|
|
396
|
+
expect(isDeepEqual(new Money(100), new Money(200))).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
test('isDeepEqual falls back to a custom toString when one is defined', () => {
|
|
399
|
+
class Tag {
|
|
400
|
+
constructor(label) {
|
|
401
|
+
this.label = label;
|
|
402
|
+
}
|
|
403
|
+
toString() {
|
|
404
|
+
return this.label;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
expect(isDeepEqual(new Tag('x'), new Tag('x'))).toBe(true);
|
|
408
|
+
expect(isDeepEqual(new Tag('x'), new Tag('y'))).toBe(false);
|
|
409
|
+
});
|
|
410
|
+
test('isDeepEqual compares subclasses of built-ins by their built-in semantics', () => {
|
|
411
|
+
class MySet extends Set {
|
|
412
|
+
}
|
|
413
|
+
class MyMap extends Map {
|
|
414
|
+
}
|
|
415
|
+
expect(isDeepEqual(new MySet([1]), new MySet([1]))).toBe(true);
|
|
416
|
+
expect(isDeepEqual(new MySet([1]), new MySet([2]))).toBe(false);
|
|
417
|
+
expect(isDeepEqual(new MyMap([['a', 1]]), new MyMap([['a', 1]]))).toBe(true);
|
|
418
|
+
expect(isDeepEqual(new MyMap([['a', 1]]), new MyMap([['a', 2]]))).toBe(false);
|
|
419
|
+
});
|