@ditojs/utils 2.86.0 → 2.87.0
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/package.json +3 -3
- package/types/index.d.ts +144 -34
- package/types/tests/base.test-d.ts +172 -0
- package/types/tests/datapath.test-d.ts +75 -0
- package/types/tests/function.test-d.ts +137 -0
- package/types/tests/object.test-d.ts +190 -0
- package/types/tests/promise.test-d.ts +66 -0
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ditojs/utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.87.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Dito.js Utility Functions – Dito.js is a declarative and modern web framework, based on Objection.js, Koa.js and Vue.js",
|
|
6
|
-
"repository": "https://github.com/ditojs/dito/tree/
|
|
6
|
+
"repository": "https://github.com/ditojs/dito/tree/main/packages/utils",
|
|
7
7
|
"author": "Jürg Lehni <juerg@scratchdisk.com> (http://scratchdisk.com)",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"main": "./src/index.js",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"typescript": "^5.9.3"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "cbd05e604a1d212dfc91f9686b43bef75815a409"
|
|
42
42
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
/* ---------------------------------- base ---------------------------------- */
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Re-export of `Array.isArray` as a type guard.
|
|
8
|
+
*/
|
|
9
|
+
export const isArray: (arg: any) => arg is any[]
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Determines whether both supplied values are the same value, using the
|
|
8
13
|
* SameValue algorithm.
|
|
@@ -13,12 +18,17 @@ export function is(value1: any, value2: any): boolean
|
|
|
13
18
|
/**
|
|
14
19
|
* Determines whether the supplied value is a plain object.
|
|
15
20
|
*/
|
|
16
|
-
export function isPlainObject(arg: any):
|
|
21
|
+
export function isPlainObject(arg: any): arg is Record<string, unknown>
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines whether the supplied value is a plain `Array` instance.
|
|
25
|
+
*/
|
|
26
|
+
export function isPlainArray(arg: any): arg is any[]
|
|
17
27
|
|
|
18
28
|
/**
|
|
19
29
|
* Determines whether the supplied value is an object.
|
|
20
30
|
*/
|
|
21
|
-
export function isObject(arg: any):
|
|
31
|
+
export function isObject(arg: any): arg is Record<string, unknown>
|
|
22
32
|
|
|
23
33
|
/**
|
|
24
34
|
* Determines whether the supplied value is a function.
|
|
@@ -40,6 +50,11 @@ export function isString(arg: any): arg is string | String
|
|
|
40
50
|
*/
|
|
41
51
|
export function isBoolean(arg: any): arg is boolean
|
|
42
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Determines whether the supplied value is an ES module object.
|
|
55
|
+
*/
|
|
56
|
+
export function isModule(arg: any): boolean
|
|
57
|
+
|
|
43
58
|
/**
|
|
44
59
|
* Determines whether the supplied value is a Date object.
|
|
45
60
|
*/
|
|
@@ -58,24 +73,26 @@ export function isPromise(arg: any): arg is Promise<any>
|
|
|
58
73
|
/**
|
|
59
74
|
* Determines whether the supplied value is an integer.
|
|
60
75
|
*/
|
|
61
|
-
export function isInteger(arg: any):
|
|
76
|
+
export function isInteger(arg: any): arg is number
|
|
62
77
|
|
|
63
78
|
/**
|
|
64
79
|
* Determines whether the supplied value is an async function.
|
|
65
80
|
*/
|
|
66
|
-
export function isAsync(
|
|
81
|
+
export function isAsync(
|
|
82
|
+
arg: any
|
|
83
|
+
): arg is (...args: any[]) => Promise<any>
|
|
67
84
|
|
|
68
85
|
/**
|
|
69
86
|
* Determines whether the supplied value is array like, i.e. it has a length
|
|
70
87
|
• property between `0` and `Number.MAX_SAFE_INTEGER` and is not a function.
|
|
71
88
|
*/
|
|
72
|
-
export function isArrayLike(arg: any): arg is any
|
|
89
|
+
export function isArrayLike(arg: any): arg is ArrayLike<any>
|
|
73
90
|
|
|
74
91
|
/**
|
|
75
92
|
* Determines whether the supplied value can be considered empty,
|
|
76
93
|
* i.e. undefined, null, an empty string, or an object without properties.
|
|
77
94
|
*/
|
|
78
|
-
export function isEmpty(
|
|
95
|
+
export function isEmpty(arg: any): boolean
|
|
79
96
|
|
|
80
97
|
/**
|
|
81
98
|
* Returns the supplied value as an object. The most frequent use case is to
|
|
@@ -84,7 +101,9 @@ export function isEmpty(o: any): boolean
|
|
|
84
101
|
*
|
|
85
102
|
* @see {@link https://2ality.com/2011/04/javascript-converting-any-value-to.html JavaScript: converting any value to an object}
|
|
86
103
|
*/
|
|
87
|
-
export function asObject<
|
|
104
|
+
export function asObject<T>(
|
|
105
|
+
arg: T
|
|
106
|
+
): T extends null | undefined ? T : T & Object
|
|
88
107
|
|
|
89
108
|
/**
|
|
90
109
|
* Returns the supplied value as an array.
|
|
@@ -93,7 +112,9 @@ export function asObject<O extends {}, T extends any>(arg: T): T & O
|
|
|
93
112
|
* the supplied value is undefined, an empty array is returned. Otherwise an
|
|
94
113
|
* array is returned containing the supplied value as its only element.
|
|
95
114
|
*/
|
|
96
|
-
export function asArray<T>(
|
|
115
|
+
export function asArray<T>(
|
|
116
|
+
arg: T
|
|
117
|
+
): T extends any[] ? T : Exclude<T, undefined>[]
|
|
97
118
|
|
|
98
119
|
/**
|
|
99
120
|
* Returns the supplied value as a function.
|
|
@@ -102,7 +123,7 @@ export function asArray<T>(o: T): T extends any[] ? T : T[]
|
|
|
102
123
|
* Otherwise a function is returned that returns the value when called.
|
|
103
124
|
*/
|
|
104
125
|
export function asFunction<T>(
|
|
105
|
-
|
|
126
|
+
arg: T
|
|
106
127
|
): T extends Function ? T : () => T
|
|
107
128
|
|
|
108
129
|
/* --------------------------------- object --------------------------------- */
|
|
@@ -132,7 +153,7 @@ export function clone<T>(value: T, options?: {
|
|
|
132
153
|
/**
|
|
133
154
|
* Optional callback to process the cloned value.
|
|
134
155
|
*/
|
|
135
|
-
processValue?:
|
|
156
|
+
processValue?: (value: T) => T | undefined | void
|
|
136
157
|
}): T
|
|
137
158
|
|
|
138
159
|
/**
|
|
@@ -155,7 +176,12 @@ export function groupBy<T, K extends string | number | symbol>(
|
|
|
155
176
|
callback: (item: T) => K
|
|
156
177
|
): Record<K, T[]>
|
|
157
178
|
|
|
158
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Recursively merges multiple source objects into the target object. For
|
|
181
|
+
* arrays, merges objects at the same indices and concatenates
|
|
182
|
+
* non-mergeable values. Does not override root-level values with
|
|
183
|
+
* nullish values.
|
|
184
|
+
*/
|
|
159
185
|
export function mergeDeeply<ArgA, ArgB>(a: ArgA, b: ArgB): ArgA & ArgB
|
|
160
186
|
export function mergeDeeply<ArgA, ArgB, ArgC>(
|
|
161
187
|
a: ArgA,
|
|
@@ -177,7 +203,12 @@ export function mergeDeeply<ArgA, ArgB, ArgC, ArgD, ArgE>(
|
|
|
177
203
|
): ArgA & ArgB & ArgC & ArgD & ArgE
|
|
178
204
|
export function mergeDeeply(...args: any[]): any
|
|
179
205
|
|
|
180
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Recursively assigns values from source objects into the target object.
|
|
208
|
+
* Similar to `mergeDeeply` but without array concatenation — overwrites
|
|
209
|
+
* array values at the same indices instead. Does not override
|
|
210
|
+
* root-level values with nullish values.
|
|
211
|
+
*/
|
|
181
212
|
export function assignDeeply<ArgA, ArgB>(a: ArgA, b: ArgB): ArgA & ArgB
|
|
182
213
|
export function assignDeeply<ArgA, ArgB, ArgC>(
|
|
183
214
|
a: ArgA,
|
|
@@ -236,12 +267,17 @@ export function pickBy<T extends Dictionary<any>, K extends keyof T[keyof T]>(
|
|
|
236
267
|
): Partial<T>
|
|
237
268
|
export function pickBy<T extends Dictionary<any>>(
|
|
238
269
|
object: T,
|
|
239
|
-
callback
|
|
270
|
+
callback: (value: T[keyof T], key: keyof T, object: T) => any
|
|
240
271
|
): Partial<T>
|
|
241
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Transforms object keys by applying a callback function to each
|
|
275
|
+
* key-value pair. Returns a new object with the transformed keys and
|
|
276
|
+
* original values.
|
|
277
|
+
*/
|
|
242
278
|
export function mapKeys<T extends Dictionary<any>, K extends keyof any>(
|
|
243
279
|
object: T,
|
|
244
|
-
callback
|
|
280
|
+
callback: (key: keyof T, value: T[keyof T], object: T) => K
|
|
245
281
|
): Record<K, T[keyof T]>
|
|
246
282
|
|
|
247
283
|
/**
|
|
@@ -257,11 +293,20 @@ export function mapValues<
|
|
|
257
293
|
): Record<keyof T, T[keyof T][K]>
|
|
258
294
|
export function mapValues<T extends Dictionary<any>, K>(
|
|
259
295
|
object: T,
|
|
260
|
-
callback
|
|
296
|
+
callback: (value: T[keyof T], key: keyof T, object: T) => K
|
|
261
297
|
): Record<keyof T, K>
|
|
262
298
|
|
|
263
299
|
/* -------------------------------- promise -------------------------------- */
|
|
264
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Maps an async callback over an array with controlled concurrency.
|
|
303
|
+
* Executes all promises in parallel by default, or limits concurrent
|
|
304
|
+
* execution when the `concurrency` option is set.
|
|
305
|
+
*
|
|
306
|
+
* @param input An array or a promise that resolves to an array.
|
|
307
|
+
* @param callback Async function called for each element.
|
|
308
|
+
* @param options.concurrency Max concurrent promises (0 = unlimited).
|
|
309
|
+
*/
|
|
265
310
|
export function mapConcurrently<T, R>(
|
|
266
311
|
input: Promise<T[]> | T[],
|
|
267
312
|
callback: (value: T, index: number, array: T[]) => Promise<R> | R,
|
|
@@ -270,6 +315,13 @@ export function mapConcurrently<T, R>(
|
|
|
270
315
|
}
|
|
271
316
|
): Promise<R[]>
|
|
272
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Maps an async callback over an array sequentially, waiting for each
|
|
320
|
+
* promise to resolve before processing the next element.
|
|
321
|
+
*
|
|
322
|
+
* @param input An array or a promise that resolves to an array.
|
|
323
|
+
* @param callback Async function called for each element.
|
|
324
|
+
*/
|
|
273
325
|
export function mapSequentially<T, R>(
|
|
274
326
|
input: Promise<T[]> | T[],
|
|
275
327
|
callback: (value: T, index: number) => Promise<R> | R
|
|
@@ -403,7 +455,7 @@ export interface TimeFormat {
|
|
|
403
455
|
value: string,
|
|
404
456
|
type: Intl.DateTimeFormatPartTypes,
|
|
405
457
|
options: Omit<TimeFormat, 'format'>
|
|
406
|
-
) => string
|
|
458
|
+
) => string | undefined
|
|
407
459
|
}
|
|
408
460
|
|
|
409
461
|
export interface DateFormat {
|
|
@@ -426,7 +478,7 @@ export interface DateFormat {
|
|
|
426
478
|
value: string,
|
|
427
479
|
type: Intl.DateTimeFormatPartTypes,
|
|
428
480
|
options: Omit<DateFormat, 'format'>
|
|
429
|
-
) => string
|
|
481
|
+
) => string | undefined
|
|
430
482
|
}
|
|
431
483
|
|
|
432
484
|
export interface NumberFormat extends Intl.NumberFormatOptions {
|
|
@@ -437,11 +489,20 @@ export interface NumberFormat extends Intl.NumberFormatOptions {
|
|
|
437
489
|
) => string | undefined
|
|
438
490
|
}
|
|
439
491
|
|
|
492
|
+
/**
|
|
493
|
+
* Default format options for number, date, and time formatting.
|
|
494
|
+
*/
|
|
495
|
+
export const defaultFormats: {
|
|
496
|
+
number: NumberFormat
|
|
497
|
+
date: DateFormat
|
|
498
|
+
time: TimeFormat
|
|
499
|
+
}
|
|
500
|
+
|
|
440
501
|
/**
|
|
441
502
|
* Formats the value as a string.
|
|
442
503
|
*/
|
|
443
504
|
export function format(
|
|
444
|
-
value: Date,
|
|
505
|
+
value: Date | string | number | null | undefined,
|
|
445
506
|
options?: {
|
|
446
507
|
/**
|
|
447
508
|
* @default 'en-US'
|
|
@@ -450,8 +511,13 @@ export function format(
|
|
|
450
511
|
date?: boolean | DateFormat
|
|
451
512
|
time?: boolean | TimeFormat
|
|
452
513
|
number?: boolean | NumberFormat
|
|
514
|
+
defaults?: {
|
|
515
|
+
number?: NumberFormat
|
|
516
|
+
date?: DateFormat
|
|
517
|
+
time?: TimeFormat
|
|
518
|
+
}
|
|
453
519
|
}
|
|
454
|
-
): string
|
|
520
|
+
): string | null | undefined
|
|
455
521
|
/**
|
|
456
522
|
* Formats a date value as a string. If the value is not a Date,
|
|
457
523
|
* attempts to convert it to a Date first.
|
|
@@ -472,7 +538,7 @@ export function formatDate(
|
|
|
472
538
|
*/
|
|
473
539
|
time?: boolean | TimeFormat
|
|
474
540
|
}
|
|
475
|
-
): string
|
|
541
|
+
): string | null | undefined
|
|
476
542
|
/* -------------------------------- function -------------------------------- */
|
|
477
543
|
|
|
478
544
|
/**
|
|
@@ -646,7 +712,7 @@ export function toCallback<T1, T2>(
|
|
|
646
712
|
): (arg1: T1, arg2: T2, callback: (err: any) => void) => void
|
|
647
713
|
export function toCallback<T1, T2, R>(
|
|
648
714
|
fn: (arg1: T1, arg2: T2) => Promise<R>
|
|
649
|
-
): (arg1: T1, arg2: T2, callback: (err: any
|
|
715
|
+
): (arg1: T1, arg2: T2, callback: (err: any, result: R) => void) => void
|
|
650
716
|
export function toCallback<T1, T2, T3>(
|
|
651
717
|
fn: (arg1: T1, arg2: T2, arg3: T3) => Promise<void>
|
|
652
718
|
): (arg1: T1, arg2: T2, arg3: T3, callback: (err: any) => void) => void
|
|
@@ -656,7 +722,7 @@ export function toCallback<T1, T2, T3, R>(
|
|
|
656
722
|
arg1: T1,
|
|
657
723
|
arg2: T2,
|
|
658
724
|
arg3: T3,
|
|
659
|
-
callback: (err: any
|
|
725
|
+
callback: (err: any, result: R) => void
|
|
660
726
|
) => void
|
|
661
727
|
export function toCallback<T1, T2, T3, T4>(
|
|
662
728
|
fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<void>
|
|
@@ -674,7 +740,7 @@ export function toCallback<T1, T2, T3, T4, R>(
|
|
|
674
740
|
arg2: T2,
|
|
675
741
|
arg3: T3,
|
|
676
742
|
arg4: T4,
|
|
677
|
-
callback: (err: any
|
|
743
|
+
callback: (err: any, result: R) => void
|
|
678
744
|
) => void
|
|
679
745
|
export function toCallback<T1, T2, T3, T4, T5>(
|
|
680
746
|
fn: (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5) => Promise<void>
|
|
@@ -694,7 +760,7 @@ export function toCallback<T1, T2, T3, T4, T5, R>(
|
|
|
694
760
|
arg3: T3,
|
|
695
761
|
arg4: T4,
|
|
696
762
|
arg5: T5,
|
|
697
|
-
callback: (err: any
|
|
763
|
+
callback: (err: any, result: R) => void
|
|
698
764
|
) => void
|
|
699
765
|
export function toCallback<T1, T2, T3, T4, T5, T6>(
|
|
700
766
|
fn: (
|
|
@@ -723,9 +789,18 @@ export function toCallback<T1, T2, T3, T4, T5, T6, R>(
|
|
|
723
789
|
arg4: T4,
|
|
724
790
|
arg5: T5,
|
|
725
791
|
arg6: T6,
|
|
726
|
-
callback: (err: any
|
|
792
|
+
callback: (err: any, result: R) => void
|
|
727
793
|
) => void
|
|
728
794
|
|
|
795
|
+
/**
|
|
796
|
+
* Creates a Node.js-style error-first callback that resolves or rejects
|
|
797
|
+
* a promise. If the error argument is truthy, rejects the promise;
|
|
798
|
+
* otherwise resolves with the result.
|
|
799
|
+
*
|
|
800
|
+
* @param resolve Promise resolve function.
|
|
801
|
+
* @param reject Promise reject function.
|
|
802
|
+
* @returns Callback with signature `(err, res) => void`.
|
|
803
|
+
*/
|
|
729
804
|
export function toPromiseCallback<T, R>(
|
|
730
805
|
resolve: (value: T) => void,
|
|
731
806
|
reject: (reason: R) => void
|
|
@@ -741,6 +816,7 @@ export function toPromiseCallback<T, R>(
|
|
|
741
816
|
* @param path The data path (supports wildcards).
|
|
742
817
|
* @param handleError Optional error handler called when path is invalid.
|
|
743
818
|
* @returns Object with normalized paths as keys and values at those paths.
|
|
819
|
+
* @see {@link parseDataPath} for supported path formats.
|
|
744
820
|
*/
|
|
745
821
|
export function getEntriesAtDataPath(
|
|
746
822
|
obj: any,
|
|
@@ -748,15 +824,40 @@ export function getEntriesAtDataPath(
|
|
|
748
824
|
handleError?: (obj: any, part: string, index: number) => any
|
|
749
825
|
): Record<string, any>
|
|
750
826
|
|
|
827
|
+
/**
|
|
828
|
+
* Retrieves a value from a nested object or array at a given path.
|
|
829
|
+
* Supports property access notation (`obj.arr[0].prop`), JSON pointers
|
|
830
|
+
* (`/obj/arr/0/prop`), and wildcard matching (`*` for shallow, `**`
|
|
831
|
+
* for deep recursive).
|
|
832
|
+
*
|
|
833
|
+
* @param obj The object or array to retrieve from.
|
|
834
|
+
* @param path The data path (multiple formats supported).
|
|
835
|
+
* @param handleError Optional error handler called when the path is
|
|
836
|
+
* invalid, with `(obj, part, index)`.
|
|
837
|
+
* @see {@link parseDataPath} for supported path formats.
|
|
838
|
+
*/
|
|
751
839
|
export function getValueAtDataPath(
|
|
752
840
|
obj: any,
|
|
753
841
|
path: OrArrayOf<string>,
|
|
754
|
-
handleError?: (obj: any, part: string, index: number) =>
|
|
842
|
+
handleError?: (obj: any, part: string, index: number) => any
|
|
755
843
|
): any
|
|
756
844
|
|
|
845
|
+
/**
|
|
846
|
+
* Normalizes a data path to a standard format using forward slashes.
|
|
847
|
+
* Converts property access notation, JSON pointers, and relative paths
|
|
848
|
+
* to normalized relative path format. Resolves relative tokens (`..`
|
|
849
|
+
* and `.`).
|
|
850
|
+
* @see {@link parseDataPath} for supported path formats.
|
|
851
|
+
*/
|
|
757
852
|
export function normalizeDataPath(path: OrArrayOf<string>): string
|
|
758
853
|
|
|
759
|
-
|
|
854
|
+
/**
|
|
855
|
+
* Parses a data path string or array into an array of path segments.
|
|
856
|
+
* Supports property access notation (`obj.arr[0].prop`), JSON pointers
|
|
857
|
+
* (`/obj/arr/0/prop`), and relative paths. Always returns a new array
|
|
858
|
+
* to prevent mutation.
|
|
859
|
+
*/
|
|
860
|
+
export function parseDataPath(path: OrArrayOf<string>): string[]
|
|
760
861
|
|
|
761
862
|
/**
|
|
762
863
|
* Sets multiple values at data paths from an entries object.
|
|
@@ -764,12 +865,24 @@ export function parseDataPath(path: OrArrayOf<string>): string
|
|
|
764
865
|
* @param obj The object to modify.
|
|
765
866
|
* @param entries Object with data paths as keys and values to set.
|
|
766
867
|
* @returns The modified object.
|
|
868
|
+
* @see {@link parseDataPath} for supported path formats.
|
|
767
869
|
*/
|
|
768
870
|
export function setDataPathEntries<O>(
|
|
769
871
|
obj: O,
|
|
770
872
|
entries: Record<string, any>
|
|
771
873
|
): O
|
|
772
874
|
|
|
875
|
+
/**
|
|
876
|
+
* Sets a value in a nested object or array at a given path. Parses the
|
|
877
|
+
* path, navigates to the parent location, and assigns the value.
|
|
878
|
+
* Mutates the original object.
|
|
879
|
+
*
|
|
880
|
+
* @param obj The object or array to set the value on.
|
|
881
|
+
* @param path The data path to the target location.
|
|
882
|
+
* @param value The value to set.
|
|
883
|
+
* @returns The modified object.
|
|
884
|
+
* @see {@link parseDataPath} for supported path formats.
|
|
885
|
+
*/
|
|
773
886
|
export function setValueAtDataPath<O>(
|
|
774
887
|
obj: O,
|
|
775
888
|
path: OrArrayOf<string>,
|
|
@@ -792,19 +905,19 @@ export function mixin<T extends new (...args: any[]) => any>(
|
|
|
792
905
|
/* ---------------------------------- html ---------------------------------- */
|
|
793
906
|
|
|
794
907
|
/**
|
|
795
|
-
* Escapes quotes, ampersands, and
|
|
908
|
+
* Escapes double quotes, ampersands, and angle brackets (`"&<>`).
|
|
796
909
|
|
|
797
910
|
* @param html The html to escape.
|
|
798
911
|
* @returns The newly escaped html.
|
|
799
912
|
*/
|
|
800
|
-
export function escapeHtml(html: string): string
|
|
913
|
+
export function escapeHtml(html: string | null | undefined): string
|
|
801
914
|
|
|
802
915
|
/**
|
|
803
916
|
* Strips HTML tags from the string.
|
|
804
917
|
* @param html The string to strip.
|
|
805
918
|
* @returns The newly stripped string.
|
|
806
919
|
*/
|
|
807
|
-
export function stripHtml(html: string): string
|
|
920
|
+
export function stripHtml(html: string | null | undefined): string
|
|
808
921
|
|
|
809
922
|
/**
|
|
810
923
|
* Strips HTML tags from the string.
|
|
@@ -812,12 +925,9 @@ export function stripHtml(html: string): string
|
|
|
812
925
|
* @returns The newly stripped string.
|
|
813
926
|
* @deprecated Use stripHtml() instead
|
|
814
927
|
*/
|
|
815
|
-
export function stripTags(html: string): string
|
|
928
|
+
export function stripTags(html: string | null | undefined): string
|
|
816
929
|
|
|
817
930
|
/* -------------------------- typescript utilities -------------------------- */
|
|
818
|
-
type PropertyName = string | number | symbol
|
|
819
|
-
type NotVoid = {} | null | undefined
|
|
820
|
-
type List<T> = ArrayLike<T>
|
|
821
931
|
export interface ArrayLike<T> {
|
|
822
932
|
readonly length: number
|
|
823
933
|
readonly [n: number]: T
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { assertType, describe, expectTypeOf, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
isArray,
|
|
4
|
+
isPlainObject,
|
|
5
|
+
isFunction,
|
|
6
|
+
isNumber,
|
|
7
|
+
isString,
|
|
8
|
+
isBoolean,
|
|
9
|
+
isDate,
|
|
10
|
+
isRegExp,
|
|
11
|
+
isPromise,
|
|
12
|
+
isAsync,
|
|
13
|
+
isArrayLike,
|
|
14
|
+
asObject,
|
|
15
|
+
asArray,
|
|
16
|
+
asFunction
|
|
17
|
+
} from '../index.d.ts'
|
|
18
|
+
|
|
19
|
+
describe('type guards', () => {
|
|
20
|
+
it('isArray narrows unknown to any[]', () => {
|
|
21
|
+
const guard = {} as typeof isArray
|
|
22
|
+
const val: unknown = []
|
|
23
|
+
if (guard(val)) {
|
|
24
|
+
expectTypeOf(val).toEqualTypeOf<any[]>()
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('isPlainObject narrows union to record', () => {
|
|
29
|
+
const guard = {} as typeof isPlainObject
|
|
30
|
+
const val: string | Record<string, unknown> = {} as any
|
|
31
|
+
if (guard(val)) {
|
|
32
|
+
expectTypeOf(val).not.toBeAny()
|
|
33
|
+
expectTypeOf(val).toEqualTypeOf<Record<string, unknown>>()
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('isFunction narrows to callable', () => {
|
|
38
|
+
const guard = {} as typeof isFunction
|
|
39
|
+
const val: string | (() => void) = {} as any
|
|
40
|
+
if (guard(val)) {
|
|
41
|
+
expectTypeOf(val).not.toBeAny()
|
|
42
|
+
expectTypeOf(val).toBeCallableWith()
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('isNumber narrows union', () => {
|
|
47
|
+
const guard = {} as typeof isNumber
|
|
48
|
+
const val: string | number = {} as any
|
|
49
|
+
if (guard(val)) {
|
|
50
|
+
expectTypeOf(val).not.toBeAny()
|
|
51
|
+
expectTypeOf(val).toBeNumber()
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('isString narrows union', () => {
|
|
56
|
+
const guard = {} as typeof isString
|
|
57
|
+
const val: number | string = {} as any
|
|
58
|
+
if (guard(val)) {
|
|
59
|
+
expectTypeOf(val).not.toBeAny()
|
|
60
|
+
assertType<string | String>(val)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('isBoolean narrows union', () => {
|
|
65
|
+
const guard = {} as typeof isBoolean
|
|
66
|
+
const val: string | boolean = {} as any
|
|
67
|
+
if (guard(val)) {
|
|
68
|
+
expectTypeOf(val).not.toBeAny()
|
|
69
|
+
expectTypeOf(val).toBeBoolean()
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('isDate narrows to Date', () => {
|
|
74
|
+
const guard = {} as typeof isDate
|
|
75
|
+
const val: string | Date = {} as any
|
|
76
|
+
if (guard(val)) {
|
|
77
|
+
expectTypeOf(val).not.toBeAny()
|
|
78
|
+
expectTypeOf(val).toEqualTypeOf<Date>()
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('isRegExp narrows to RegExp', () => {
|
|
83
|
+
const guard = {} as typeof isRegExp
|
|
84
|
+
const val: string | RegExp = {} as any
|
|
85
|
+
if (guard(val)) {
|
|
86
|
+
expectTypeOf(val).not.toBeAny()
|
|
87
|
+
expectTypeOf(val).toEqualTypeOf<RegExp>()
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('isPromise narrows to Promise', () => {
|
|
92
|
+
const guard = {} as typeof isPromise
|
|
93
|
+
const val: string | Promise<number> = {} as any
|
|
94
|
+
if (guard(val)) {
|
|
95
|
+
expectTypeOf(val).not.toBeAny()
|
|
96
|
+
assertType<Promise<any>>(val)
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('isAsync narrows to async function', () => {
|
|
101
|
+
const guard = {} as typeof isAsync
|
|
102
|
+
const val: (() => void) | (() => Promise<string>) = {} as any
|
|
103
|
+
if (guard(val)) {
|
|
104
|
+
expectTypeOf(val).not.toBeAny()
|
|
105
|
+
assertType<(...args: any[]) => Promise<any>>(val)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('isArrayLike narrows to ArrayLike', () => {
|
|
110
|
+
const guard = {} as typeof isArrayLike
|
|
111
|
+
const val: string | ArrayLike<number> = {} as any
|
|
112
|
+
if (guard(val)) {
|
|
113
|
+
expectTypeOf(val).not.toBeAny()
|
|
114
|
+
expectTypeOf(val).toHaveProperty('length')
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('asObject', () => {
|
|
120
|
+
it('preserves object types', () => {
|
|
121
|
+
const fn = {} as typeof asObject
|
|
122
|
+
const obj = { a: 1 }
|
|
123
|
+
const result = fn(obj)
|
|
124
|
+
expectTypeOf(result).not.toBeAny()
|
|
125
|
+
expectTypeOf(result).toHaveProperty('a')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('returns null/undefined as-is', () => {
|
|
129
|
+
const fn = {} as typeof asObject
|
|
130
|
+
expectTypeOf(fn(null)).toBeNull()
|
|
131
|
+
expectTypeOf(fn(undefined)).toBeUndefined()
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('asArray', () => {
|
|
136
|
+
it('passes arrays through unchanged', () => {
|
|
137
|
+
const fn = {} as typeof asArray
|
|
138
|
+
const arr = [1, 2, 3]
|
|
139
|
+
const result = fn(arr)
|
|
140
|
+
expectTypeOf(result).not.toBeAny()
|
|
141
|
+
expectTypeOf(result).toEqualTypeOf<number[]>()
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('wraps non-array in array', () => {
|
|
145
|
+
const fn = {} as typeof asArray
|
|
146
|
+
const result = fn('hello')
|
|
147
|
+
expectTypeOf(result).not.toBeAny()
|
|
148
|
+
expectTypeOf(result).toEqualTypeOf<string[]>()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('returns empty array for undefined', () => {
|
|
152
|
+
const fn = {} as typeof asArray
|
|
153
|
+
expectTypeOf(fn(undefined)).toEqualTypeOf<never[]>()
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('asFunction', () => {
|
|
158
|
+
it('passes functions through unchanged', () => {
|
|
159
|
+
const fn = {} as typeof asFunction
|
|
160
|
+
const cb = (x: number) => x * 2
|
|
161
|
+
const result = fn(cb)
|
|
162
|
+
expectTypeOf(result).not.toBeAny()
|
|
163
|
+
expectTypeOf(result).toEqualTypeOf<(x: number) => number>()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('wraps non-function in thunk', () => {
|
|
167
|
+
const fn = {} as typeof asFunction
|
|
168
|
+
const result = fn(42)
|
|
169
|
+
expectTypeOf(result).not.toBeAny()
|
|
170
|
+
expectTypeOf(result).toEqualTypeOf<() => number>()
|
|
171
|
+
})
|
|
172
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
getValueAtDataPath,
|
|
4
|
+
getEntriesAtDataPath,
|
|
5
|
+
setValueAtDataPath,
|
|
6
|
+
setDataPathEntries,
|
|
7
|
+
normalizeDataPath,
|
|
8
|
+
parseDataPath
|
|
9
|
+
} from '../index.d.ts'
|
|
10
|
+
|
|
11
|
+
describe('getValueAtDataPath', () => {
|
|
12
|
+
it('error handler receives correct params', () => {
|
|
13
|
+
const fn = {} as typeof getValueAtDataPath
|
|
14
|
+
fn({}, 'a.b', (obj, part, index) => {
|
|
15
|
+
expectTypeOf(part).not.toBeAny()
|
|
16
|
+
expectTypeOf(part).toBeString()
|
|
17
|
+
expectTypeOf(index).not.toBeAny()
|
|
18
|
+
expectTypeOf(index).toBeNumber()
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('getEntriesAtDataPath', () => {
|
|
24
|
+
it('error handler receives correct params', () => {
|
|
25
|
+
const fn = {} as typeof getEntriesAtDataPath
|
|
26
|
+
fn({}, 'items.*', (obj, part, index) => {
|
|
27
|
+
expectTypeOf(part).not.toBeAny()
|
|
28
|
+
expectTypeOf(part).toBeString()
|
|
29
|
+
expectTypeOf(index).not.toBeAny()
|
|
30
|
+
expectTypeOf(index).toBeNumber()
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('setValueAtDataPath', () => {
|
|
36
|
+
it('preserves the object type', () => {
|
|
37
|
+
const fn = {} as typeof setValueAtDataPath
|
|
38
|
+
const obj = { a: { b: 1 } }
|
|
39
|
+
const result = fn(obj, 'a.b', 2)
|
|
40
|
+
expectTypeOf(result).not.toBeAny()
|
|
41
|
+
expectTypeOf(result).toEqualTypeOf<typeof obj>()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('setDataPathEntries', () => {
|
|
46
|
+
it('preserves the object type', () => {
|
|
47
|
+
const fn = {} as typeof setDataPathEntries
|
|
48
|
+
const obj = { x: 1, y: 2 }
|
|
49
|
+
const result = fn(obj, { x: 10 })
|
|
50
|
+
expectTypeOf(result).not.toBeAny()
|
|
51
|
+
expectTypeOf(result).toEqualTypeOf<typeof obj>()
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('parseDataPath', () => {
|
|
56
|
+
it('returns string array from string or array input', () => {
|
|
57
|
+
const fn = {} as typeof parseDataPath
|
|
58
|
+
const fromString = fn('a.b.c')
|
|
59
|
+
const fromArray = fn(['a', 'b'])
|
|
60
|
+
expectTypeOf(fromString).not.toBeAny()
|
|
61
|
+
expectTypeOf(fromString).toEqualTypeOf<string[]>()
|
|
62
|
+
expectTypeOf(fromArray).toEqualTypeOf<string[]>()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('normalizeDataPath', () => {
|
|
67
|
+
it('returns string from string or array input', () => {
|
|
68
|
+
const fn = {} as typeof normalizeDataPath
|
|
69
|
+
const fromString = fn('a.b')
|
|
70
|
+
const fromArray = fn(['a', 'b'])
|
|
71
|
+
expectTypeOf(fromString).not.toBeAny()
|
|
72
|
+
expectTypeOf(fromString).toBeString()
|
|
73
|
+
expectTypeOf(fromArray).toBeString()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { assertType, describe, expectTypeOf, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
debounce,
|
|
4
|
+
debounceAsync,
|
|
5
|
+
toAsync,
|
|
6
|
+
toCallback,
|
|
7
|
+
toPromiseCallback
|
|
8
|
+
} from '../index.d.ts'
|
|
9
|
+
|
|
10
|
+
describe('debounce', () => {
|
|
11
|
+
it('preserves original function signature', () => {
|
|
12
|
+
const fn = {} as typeof debounce
|
|
13
|
+
const original = (x: number, y: string) => x + y.length
|
|
14
|
+
const debounced = fn(original, 100)
|
|
15
|
+
expectTypeOf(debounced).not.toBeAny()
|
|
16
|
+
expectTypeOf(debounced).toBeCallableWith(1, 'hello')
|
|
17
|
+
expectTypeOf(debounced(1, 'a')).toBeNumber()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('adds cancel method', () => {
|
|
21
|
+
const fn = {} as typeof debounce
|
|
22
|
+
const debounced = fn(() => {}, 100)
|
|
23
|
+
expectTypeOf(debounced.cancel).not.toBeAny()
|
|
24
|
+
expectTypeOf(debounced.cancel).toBeFunction()
|
|
25
|
+
expectTypeOf(debounced.cancel()).toBeBoolean()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('accepts options object', () => {
|
|
29
|
+
const fn = {} as typeof debounce
|
|
30
|
+
fn((x: number) => x, { delay: 100, immediate: true })
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('rejects wrong argument types', () => {
|
|
34
|
+
const fn = {} as typeof debounce
|
|
35
|
+
const debounced = fn((x: number) => x, 100)
|
|
36
|
+
// @ts-expect-error - string not assignable to number
|
|
37
|
+
debounced('wrong')
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('debounceAsync', () => {
|
|
42
|
+
it('preserves async function signature', () => {
|
|
43
|
+
const fn = {} as typeof debounceAsync
|
|
44
|
+
const original = async (id: number) => ({
|
|
45
|
+
id,
|
|
46
|
+
name: 'test'
|
|
47
|
+
})
|
|
48
|
+
const debounced = fn(original, 200)
|
|
49
|
+
expectTypeOf(debounced).not.toBeAny()
|
|
50
|
+
expectTypeOf(debounced).toBeCallableWith(1)
|
|
51
|
+
expectTypeOf(debounced(1))
|
|
52
|
+
.resolves.toHaveProperty('name')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('toAsync', () => {
|
|
57
|
+
it('converts error-first callback to promise', () => {
|
|
58
|
+
const fn = {} as typeof toAsync
|
|
59
|
+
const readFile = (
|
|
60
|
+
path: string,
|
|
61
|
+
cb: (err: any, data: Buffer) => void
|
|
62
|
+
) => {}
|
|
63
|
+
const asyncReadFile = fn(readFile)
|
|
64
|
+
expectTypeOf(asyncReadFile).not.toBeAny()
|
|
65
|
+
expectTypeOf(asyncReadFile).toBeCallableWith('test.txt')
|
|
66
|
+
expectTypeOf(asyncReadFile('x'))
|
|
67
|
+
.resolves.toEqualTypeOf<Buffer>()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('converts void callback to promise', () => {
|
|
71
|
+
const fn = {} as typeof toAsync
|
|
72
|
+
const doThing = (cb: (err?: any) => void) => {}
|
|
73
|
+
const asyncDoThing = fn(doThing)
|
|
74
|
+
expectTypeOf(asyncDoThing).not.toBeAny()
|
|
75
|
+
expectTypeOf(asyncDoThing())
|
|
76
|
+
.resolves.toEqualTypeOf<void>()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('handles multiple arguments', () => {
|
|
80
|
+
const fn = {} as typeof toAsync
|
|
81
|
+
const write = (
|
|
82
|
+
path: string,
|
|
83
|
+
data: string,
|
|
84
|
+
cb: (err: any, written: number) => void
|
|
85
|
+
) => {}
|
|
86
|
+
const asyncWrite = fn(write)
|
|
87
|
+
expectTypeOf(asyncWrite).not.toBeAny()
|
|
88
|
+
expectTypeOf(asyncWrite).toBeCallableWith('f', 'data')
|
|
89
|
+
expectTypeOf(asyncWrite('f', 'd'))
|
|
90
|
+
.resolves.toBeNumber()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('toCallback', () => {
|
|
95
|
+
it('converts async fn to callback style', () => {
|
|
96
|
+
const fn = {} as typeof toCallback
|
|
97
|
+
const asyncFn = async (x: number): Promise<string> => `${x}`
|
|
98
|
+
const cbFn = fn(asyncFn)
|
|
99
|
+
expectTypeOf(cbFn).not.toBeAny()
|
|
100
|
+
expectTypeOf(cbFn).toBeCallableWith(
|
|
101
|
+
42,
|
|
102
|
+
(err: any, result: string) => {}
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('handles void return', () => {
|
|
107
|
+
const fn = {} as typeof toCallback
|
|
108
|
+
const asyncFn = async (x: string): Promise<void> => {}
|
|
109
|
+
const cbFn = fn(asyncFn)
|
|
110
|
+
expectTypeOf(cbFn).not.toBeAny()
|
|
111
|
+
expectTypeOf(cbFn).toBeCallableWith(
|
|
112
|
+
'hello',
|
|
113
|
+
(err: any) => {}
|
|
114
|
+
)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('toPromiseCallback', () => {
|
|
119
|
+
it('returns callback matching resolve/reject types', () => {
|
|
120
|
+
const fn = {} as typeof toPromiseCallback
|
|
121
|
+
const cb = fn<string, Error>(
|
|
122
|
+
value => {
|
|
123
|
+
expectTypeOf(value).not.toBeAny()
|
|
124
|
+
expectTypeOf(value).toBeString()
|
|
125
|
+
},
|
|
126
|
+
reason => {
|
|
127
|
+
expectTypeOf(reason).not.toBeAny()
|
|
128
|
+
expectTypeOf(reason).toEqualTypeOf<Error>()
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
expectTypeOf(cb).not.toBeAny()
|
|
132
|
+
expectTypeOf(cb).toBeCallableWith(
|
|
133
|
+
new Error(),
|
|
134
|
+
'result'
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
clone,
|
|
4
|
+
groupBy,
|
|
5
|
+
mergeDeeply,
|
|
6
|
+
assignDeeply,
|
|
7
|
+
pick,
|
|
8
|
+
pickBy,
|
|
9
|
+
mapKeys,
|
|
10
|
+
mapValues
|
|
11
|
+
} from '../index.d.ts'
|
|
12
|
+
|
|
13
|
+
describe('clone', () => {
|
|
14
|
+
it('preserves the input type', () => {
|
|
15
|
+
const fn = {} as typeof clone
|
|
16
|
+
const obj = { a: 1, b: 'hello' }
|
|
17
|
+
const result = fn(obj)
|
|
18
|
+
expectTypeOf(result).not.toBeAny()
|
|
19
|
+
expectTypeOf(result).toEqualTypeOf<typeof obj>()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('processValue callback receives correct type', () => {
|
|
23
|
+
const fn = {} as typeof clone
|
|
24
|
+
fn(
|
|
25
|
+
{ x: 1 },
|
|
26
|
+
{
|
|
27
|
+
processValue(value) {
|
|
28
|
+
expectTypeOf(value).not.toBeAny()
|
|
29
|
+
expectTypeOf(value).toEqualTypeOf<{ x: number }>()
|
|
30
|
+
return value
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('groupBy', () => {
|
|
38
|
+
it('infers callback param type from array', () => {
|
|
39
|
+
const fn = {} as typeof groupBy
|
|
40
|
+
const items = [
|
|
41
|
+
{ name: 'a', category: 'x' },
|
|
42
|
+
{ name: 'b', category: 'y' }
|
|
43
|
+
]
|
|
44
|
+
fn(items, item => {
|
|
45
|
+
expectTypeOf(item).not.toBeAny()
|
|
46
|
+
expectTypeOf(item).toEqualTypeOf<{
|
|
47
|
+
name: string
|
|
48
|
+
category: string
|
|
49
|
+
}>()
|
|
50
|
+
return item.category
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('accepts property key shorthand', () => {
|
|
55
|
+
const fn = {} as typeof groupBy
|
|
56
|
+
const items = [{ type: 'a' as const, v: 1 }]
|
|
57
|
+
const result = fn(items, 'type')
|
|
58
|
+
expectTypeOf(result).not.toBeAny()
|
|
59
|
+
expectTypeOf(result).toEqualTypeOf<
|
|
60
|
+
Record<string, { type: 'a'; v: number }[]>
|
|
61
|
+
>()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('works with record input', () => {
|
|
65
|
+
const fn = {} as typeof groupBy
|
|
66
|
+
const obj: Record<string, { group: string }> = {}
|
|
67
|
+
fn(obj, item => {
|
|
68
|
+
expectTypeOf(item).not.toBeAny()
|
|
69
|
+
expectTypeOf(item).toEqualTypeOf<{ group: string }>()
|
|
70
|
+
return item.group
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('mergeDeeply', () => {
|
|
76
|
+
it('produces intersection of two args', () => {
|
|
77
|
+
const fn = {} as typeof mergeDeeply
|
|
78
|
+
const result = fn(
|
|
79
|
+
{} as { a: number },
|
|
80
|
+
{} as { b: string }
|
|
81
|
+
)
|
|
82
|
+
expectTypeOf(result).not.toBeAny()
|
|
83
|
+
expectTypeOf(result).toHaveProperty('a')
|
|
84
|
+
expectTypeOf(result).toHaveProperty('b')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('produces intersection of three args', () => {
|
|
88
|
+
const fn = {} as typeof mergeDeeply
|
|
89
|
+
const result = fn(
|
|
90
|
+
{} as { a: number },
|
|
91
|
+
{} as { b: string },
|
|
92
|
+
{} as { c: boolean }
|
|
93
|
+
)
|
|
94
|
+
expectTypeOf(result).not.toBeAny()
|
|
95
|
+
expectTypeOf(result).toHaveProperty('a')
|
|
96
|
+
expectTypeOf(result).toHaveProperty('b')
|
|
97
|
+
expectTypeOf(result).toHaveProperty('c')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe('assignDeeply', () => {
|
|
102
|
+
it('produces intersection of two args', () => {
|
|
103
|
+
const fn = {} as typeof assignDeeply
|
|
104
|
+
const result = fn(
|
|
105
|
+
{} as { a: number },
|
|
106
|
+
{} as { b: string }
|
|
107
|
+
)
|
|
108
|
+
expectTypeOf(result).not.toBeAny()
|
|
109
|
+
expectTypeOf(result).toHaveProperty('a')
|
|
110
|
+
expectTypeOf(result).toHaveProperty('b')
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('pick', () => {
|
|
115
|
+
it('returns union of argument types', () => {
|
|
116
|
+
const fn = {} as typeof pick
|
|
117
|
+
const result = fn(
|
|
118
|
+
{} as number | undefined,
|
|
119
|
+
{} as string
|
|
120
|
+
)
|
|
121
|
+
expectTypeOf(result).not.toBeAny()
|
|
122
|
+
expectTypeOf(result).toEqualTypeOf<number | undefined | string>()
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('pickBy', () => {
|
|
127
|
+
it('callback receives value, key, and object', () => {
|
|
128
|
+
const fn = {} as typeof pickBy
|
|
129
|
+
const obj: Record<string, number> = {}
|
|
130
|
+
fn(obj, (value, key, source) => {
|
|
131
|
+
expectTypeOf(value).not.toBeAny()
|
|
132
|
+
expectTypeOf(value).toBeNumber()
|
|
133
|
+
expectTypeOf(key).toBeString()
|
|
134
|
+
expectTypeOf(source).toEqualTypeOf(obj)
|
|
135
|
+
return value > 1
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('returns partial of input type', () => {
|
|
140
|
+
const fn = {} as typeof pickBy
|
|
141
|
+
const obj = {} as { a: number; b: number }
|
|
142
|
+
const result = fn(obj, () => true)
|
|
143
|
+
expectTypeOf(result).not.toBeAny()
|
|
144
|
+
expectTypeOf(result).toEqualTypeOf<Partial<{ a: number; b: number }>>()
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('mapKeys', () => {
|
|
149
|
+
it('callback receives key, value, and object', () => {
|
|
150
|
+
const fn = {} as typeof mapKeys
|
|
151
|
+
const obj: Record<string, number> = {}
|
|
152
|
+
fn(obj, (key, value, source) => {
|
|
153
|
+
expectTypeOf(value).not.toBeAny()
|
|
154
|
+
expectTypeOf(key).toBeString()
|
|
155
|
+
expectTypeOf(value).toBeNumber()
|
|
156
|
+
expectTypeOf(source).toEqualTypeOf(obj)
|
|
157
|
+
return `prefix_${key}`
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe('mapValues', () => {
|
|
163
|
+
it('callback receives value, key, and object', () => {
|
|
164
|
+
const fn = {} as typeof mapValues
|
|
165
|
+
const obj: Record<string, number> = {}
|
|
166
|
+
fn(obj, (value, key, source) => {
|
|
167
|
+
expectTypeOf(value).not.toBeAny()
|
|
168
|
+
expectTypeOf(value).toBeNumber()
|
|
169
|
+
expectTypeOf(key).toBeString()
|
|
170
|
+
expectTypeOf(source).toEqualTypeOf(obj)
|
|
171
|
+
return String(value)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('result values match callback return type', () => {
|
|
176
|
+
const fn = {} as typeof mapValues
|
|
177
|
+
const obj: Record<string, number> = {}
|
|
178
|
+
const result = fn(obj, value => String(value))
|
|
179
|
+
expectTypeOf(result).not.toBeAny()
|
|
180
|
+
expectTypeOf(result).toEqualTypeOf<Record<string, string>>()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('property shorthand extracts nested value', () => {
|
|
184
|
+
const fn = {} as typeof mapValues
|
|
185
|
+
const obj: Record<string, { nested: number }> = {}
|
|
186
|
+
const result = fn(obj, 'nested')
|
|
187
|
+
expectTypeOf(result).not.toBeAny()
|
|
188
|
+
expectTypeOf(result).toEqualTypeOf<Record<string, number>>()
|
|
189
|
+
})
|
|
190
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest'
|
|
2
|
+
import type { mapConcurrently, mapSequentially } from '../index.d.ts'
|
|
3
|
+
|
|
4
|
+
describe('mapConcurrently', () => {
|
|
5
|
+
it('infers callback param from input array type', () => {
|
|
6
|
+
const fn = {} as typeof mapConcurrently
|
|
7
|
+
fn([1, 2, 3], async (value, index, array) => {
|
|
8
|
+
expectTypeOf(value).not.toBeAny()
|
|
9
|
+
expectTypeOf(value).toBeNumber()
|
|
10
|
+
expectTypeOf(index).toBeNumber()
|
|
11
|
+
expectTypeOf(array).toEqualTypeOf<number[]>()
|
|
12
|
+
return String(value)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('result type matches callback return', () => {
|
|
17
|
+
const fn = {} as typeof mapConcurrently
|
|
18
|
+
const result = fn(
|
|
19
|
+
[1, 2],
|
|
20
|
+
async value => ({ doubled: value * 2 })
|
|
21
|
+
)
|
|
22
|
+
expectTypeOf(result)
|
|
23
|
+
.resolves.items.not.toBeAny()
|
|
24
|
+
expectTypeOf(result)
|
|
25
|
+
.resolves.items.toEqualTypeOf<{ doubled: number }>()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('accepts promise input', () => {
|
|
29
|
+
const fn = {} as typeof mapConcurrently
|
|
30
|
+
const input = Promise.resolve(['a', 'b'])
|
|
31
|
+
fn(input, async value => {
|
|
32
|
+
expectTypeOf(value).not.toBeAny()
|
|
33
|
+
expectTypeOf(value).toBeString()
|
|
34
|
+
return value.length
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('accepts synchronous callback', () => {
|
|
39
|
+
const fn = {} as typeof mapConcurrently
|
|
40
|
+
const result = fn([1, 2], value => `${value}`)
|
|
41
|
+
expectTypeOf(result).resolves.items.not.toBeAny()
|
|
42
|
+
expectTypeOf(result).resolves.items.toBeString()
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('mapSequentially', () => {
|
|
47
|
+
it('infers callback param from input array type', () => {
|
|
48
|
+
const fn = {} as typeof mapSequentially
|
|
49
|
+
fn(['a', 'b'], async (value, index) => {
|
|
50
|
+
expectTypeOf(value).not.toBeAny()
|
|
51
|
+
expectTypeOf(value).toBeString()
|
|
52
|
+
expectTypeOf(index).toBeNumber()
|
|
53
|
+
return value.length
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('result type matches callback return', () => {
|
|
58
|
+
const fn = {} as typeof mapSequentially
|
|
59
|
+
const result = fn(
|
|
60
|
+
[{ id: 1 }],
|
|
61
|
+
async item => item.id
|
|
62
|
+
)
|
|
63
|
+
expectTypeOf(result).resolves.items.not.toBeAny()
|
|
64
|
+
expectTypeOf(result).resolves.items.toBeNumber()
|
|
65
|
+
})
|
|
66
|
+
})
|