@wopjs/cast 0.1.6 → 0.1.8
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/README.md +152 -14
- package/dist/index.d.mts +30 -32
- package/dist/index.d.ts +30 -32
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -5
- package/src/is-to-as.test.ts +720 -187
- package/src/is-to-as.ts +44 -33
package/src/is-to-as.test.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PlainObject } from ".";
|
|
2
|
+
|
|
1
3
|
import { describe, it, expect } from "vitest";
|
|
2
4
|
|
|
3
5
|
import {
|
|
@@ -7,6 +9,7 @@ import {
|
|
|
7
9
|
asTrue,
|
|
8
10
|
isTruthy,
|
|
9
11
|
isFalsy,
|
|
12
|
+
toFalsy,
|
|
10
13
|
isBoolean,
|
|
11
14
|
toBoolean,
|
|
12
15
|
isNumber,
|
|
@@ -24,6 +27,7 @@ import {
|
|
|
24
27
|
asArray,
|
|
25
28
|
toNonEmptyArray,
|
|
26
29
|
isObject,
|
|
30
|
+
toObject,
|
|
27
31
|
asObject,
|
|
28
32
|
isPlainObject,
|
|
29
33
|
toPlainObject,
|
|
@@ -37,6 +41,8 @@ import {
|
|
|
37
41
|
toTruthy,
|
|
38
42
|
} from ".";
|
|
39
43
|
|
|
44
|
+
const castType = <T>(x: T): T => x;
|
|
45
|
+
|
|
40
46
|
describe("primitive.ts", () => {
|
|
41
47
|
it("isDefined", () => {
|
|
42
48
|
expect(isDefined(1)).toBe(true);
|
|
@@ -65,6 +71,33 @@ describe("primitive.ts", () => {
|
|
|
65
71
|
expect(isTruthy(true)).toBe(true);
|
|
66
72
|
expect(isTruthy(false)).toBe(false);
|
|
67
73
|
expect(isTruthy(1)).toBe(true);
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
// Type narrowing - excludes falsy from union
|
|
77
|
+
const val = castType<string | null | undefined>("hello");
|
|
78
|
+
if (isTruthy(val)) {
|
|
79
|
+
const check: string = val;
|
|
80
|
+
expect(check).toBe("hello");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
// Type narrowing - excludes all falsy types
|
|
86
|
+
const val = castType<number | false | null | undefined | "" | 0>(42);
|
|
87
|
+
if (isTruthy(val)) {
|
|
88
|
+
const check: number = val;
|
|
89
|
+
expect(check).toBe(42);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
// Type narrowing - narrows boolean to true
|
|
95
|
+
const val = castType<boolean>(true);
|
|
96
|
+
if (isTruthy(val)) {
|
|
97
|
+
const check: true = val;
|
|
98
|
+
expect(check).toBe(true);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
68
101
|
});
|
|
69
102
|
|
|
70
103
|
it("toTruthy", () => {
|
|
@@ -87,6 +120,71 @@ describe("primitive.ts", () => {
|
|
|
87
120
|
expect(isFalsy(true)).toBe(false);
|
|
88
121
|
expect(isFalsy(false)).toBe(true);
|
|
89
122
|
expect(isFalsy(1)).toBe(false);
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
// Type narrowing - extracts falsy from union
|
|
126
|
+
const val = castType<string | null | undefined>(null);
|
|
127
|
+
if (isFalsy(val)) {
|
|
128
|
+
const check: null | undefined = val;
|
|
129
|
+
expect(check).toBe(null);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
{
|
|
134
|
+
// Type narrowing - extracts all falsy types
|
|
135
|
+
const val = castType<number | false | null | undefined | "" | 0>(0);
|
|
136
|
+
if (isFalsy(val)) {
|
|
137
|
+
const check: false | null | undefined | "" | 0 = val;
|
|
138
|
+
expect(check).toBe(0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
{
|
|
143
|
+
// Type narrowing - narrows boolean to false
|
|
144
|
+
const val = castType<boolean>(false);
|
|
145
|
+
if (isFalsy(val)) {
|
|
146
|
+
const check: false = val;
|
|
147
|
+
expect(check).toBe(false);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("toFalsy", () => {
|
|
153
|
+
expect(toFalsy(false)).toBe(false);
|
|
154
|
+
expect(toFalsy(null)).toBe(null);
|
|
155
|
+
expect(toFalsy(undefined)).toBe(undefined);
|
|
156
|
+
expect(toFalsy(0)).toBe(0);
|
|
157
|
+
expect(toFalsy("")).toBe("");
|
|
158
|
+
expect(toFalsy(true)).toBe(undefined);
|
|
159
|
+
expect(toFalsy(1)).toBe(undefined);
|
|
160
|
+
expect(toFalsy("hello")).toBe(undefined);
|
|
161
|
+
|
|
162
|
+
{
|
|
163
|
+
// Type narrowing - extracts falsy from union
|
|
164
|
+
const val = castType<string | null | undefined>(null);
|
|
165
|
+
const result = toFalsy(val);
|
|
166
|
+
if (result !== undefined) {
|
|
167
|
+
const check: "" | null = result;
|
|
168
|
+
expect(check).toBe(null);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
{
|
|
173
|
+
// Type narrowing - returns undefined for truthy value
|
|
174
|
+
const val = castType<string | null>("hello");
|
|
175
|
+
const result: "" | null | undefined = toFalsy(val);
|
|
176
|
+
expect(result).toBe(undefined);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
// Type narrowing - extracts falsy from number union
|
|
181
|
+
const val = castType<number | false>(0);
|
|
182
|
+
const result = toFalsy(val);
|
|
183
|
+
if (result !== undefined) {
|
|
184
|
+
const check: 0 | false = result;
|
|
185
|
+
expect(check).toBe(0);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
90
188
|
});
|
|
91
189
|
|
|
92
190
|
it("isBoolean", () => {
|
|
@@ -153,46 +251,58 @@ describe("primitive.ts", () => {
|
|
|
153
251
|
expect(isArray([])).toBe(true);
|
|
154
252
|
expect(isArray({})).toBe(false);
|
|
155
253
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
254
|
+
{
|
|
255
|
+
// Type narrowing - preserves array type
|
|
256
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
257
|
+
if (isArray(arr)) {
|
|
258
|
+
const check: string[] = arr;
|
|
259
|
+
expect(check).toBe(arr);
|
|
260
|
+
}
|
|
161
261
|
}
|
|
162
262
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
263
|
+
{
|
|
264
|
+
// Type narrowing - extracts array from union
|
|
265
|
+
const arr = castType<string | string[]>(["a"]);
|
|
266
|
+
if (isArray(arr)) {
|
|
267
|
+
const check: string[] = arr;
|
|
268
|
+
expect(check).toBe(arr);
|
|
269
|
+
}
|
|
168
270
|
}
|
|
169
271
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
272
|
+
{
|
|
273
|
+
// Type narrowing - preserves readonly array
|
|
274
|
+
const arr = castType<readonly number[]>([1, 2]);
|
|
275
|
+
if (isArray(arr)) {
|
|
276
|
+
const check: readonly number[] = arr;
|
|
277
|
+
expect(check).toBe(arr);
|
|
278
|
+
}
|
|
175
279
|
}
|
|
176
280
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
281
|
+
{
|
|
282
|
+
// Type narrowing - preserves tuple
|
|
283
|
+
const arr = castType<[string, number]>(["a", 1]);
|
|
284
|
+
if (isArray(arr)) {
|
|
285
|
+
const check: [string, number] = arr;
|
|
286
|
+
expect(check).toBe(arr);
|
|
287
|
+
}
|
|
182
288
|
}
|
|
183
289
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
290
|
+
{
|
|
291
|
+
// Type narrowing - unknown input returns any[] | undefined
|
|
292
|
+
const arr = castType<unknown>(["a", "b"]);
|
|
293
|
+
if (isArray(arr)) {
|
|
294
|
+
const check: unknown[] = arr;
|
|
295
|
+
expect(check).toEqual(["a", "b"]);
|
|
296
|
+
}
|
|
189
297
|
}
|
|
190
298
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
299
|
+
{
|
|
300
|
+
// Type narrowing - non-array type (string) returns string & any[] | undefined
|
|
301
|
+
const arr = castType<string>("hello");
|
|
302
|
+
if (isArray(arr)) {
|
|
303
|
+
const check: unknown[] = arr;
|
|
304
|
+
expect(check).toBe(undefined);
|
|
305
|
+
}
|
|
196
306
|
}
|
|
197
307
|
});
|
|
198
308
|
|
|
@@ -200,47 +310,69 @@ describe("primitive.ts", () => {
|
|
|
200
310
|
expect(toArray([])).toEqual([]);
|
|
201
311
|
expect(toArray({})).toBe(undefined);
|
|
202
312
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
313
|
+
{
|
|
314
|
+
// Type narrowing - preserves array type
|
|
315
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
316
|
+
const result = toArray(arr);
|
|
317
|
+
if (result) {
|
|
318
|
+
const check: string[] = result;
|
|
319
|
+
expect(check).toBe(arr);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
{
|
|
324
|
+
// Type narrowing - extracts array from union
|
|
325
|
+
const arr = castType<string | string[]>(["a"]);
|
|
326
|
+
const result = toArray(arr);
|
|
327
|
+
if (result) {
|
|
328
|
+
const check: string[] = result;
|
|
329
|
+
expect(check).toBe(arr);
|
|
330
|
+
}
|
|
209
331
|
}
|
|
210
332
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
333
|
+
{
|
|
334
|
+
// Type narrowing - extracts array from array | undefined union
|
|
335
|
+
const arr = castType<string[] | undefined>(["a"]);
|
|
336
|
+
const result = toArray(arr);
|
|
337
|
+
if (result) {
|
|
338
|
+
const check: string[] = result;
|
|
339
|
+
expect(check).toEqual(["a"]);
|
|
340
|
+
}
|
|
217
341
|
}
|
|
218
342
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
343
|
+
{
|
|
344
|
+
// Type narrowing - preserves readonly array
|
|
345
|
+
const arr = castType<readonly number[]>([1, 2]);
|
|
346
|
+
const result = toArray(arr);
|
|
347
|
+
if (result) {
|
|
348
|
+
const check: readonly number[] = result;
|
|
349
|
+
expect(check).toBe(arr);
|
|
350
|
+
}
|
|
225
351
|
}
|
|
226
352
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
353
|
+
{
|
|
354
|
+
// Type narrowing - preserves tuple
|
|
355
|
+
const arr = castType<[string, number]>(["a", 1]);
|
|
356
|
+
const result = toArray(arr);
|
|
357
|
+
if (result) {
|
|
358
|
+
const check: [string, number] = result;
|
|
359
|
+
expect(check).toBe(arr);
|
|
360
|
+
}
|
|
233
361
|
}
|
|
234
362
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
363
|
+
{
|
|
364
|
+
// Type narrowing - unknown input returns unknown[] | undefined
|
|
365
|
+
const arr = castType<unknown>(["a", "b"]);
|
|
366
|
+
const result: unknown[] | undefined = toArray(arr);
|
|
367
|
+
expect(result).toEqual(["a", "b"]);
|
|
368
|
+
}
|
|
239
369
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
370
|
+
{
|
|
371
|
+
// Type narrowing - non-array type (string) returns unknown[] | undefined
|
|
372
|
+
const arr = castType<string>("hello");
|
|
373
|
+
const result: unknown[] | undefined = toArray(arr);
|
|
374
|
+
expect(result).toBe(undefined);
|
|
375
|
+
}
|
|
244
376
|
});
|
|
245
377
|
|
|
246
378
|
it("isNonEmptyArray", () => {
|
|
@@ -253,75 +385,88 @@ describe("primitive.ts", () => {
|
|
|
253
385
|
expect(isNonEmptyArray(undefined)).toBe(false);
|
|
254
386
|
expect(isNonEmptyArray("string")).toBe(false);
|
|
255
387
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
388
|
+
{
|
|
389
|
+
// Type narrowing - preserves array type
|
|
390
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
391
|
+
if (isNonEmptyArray(arr)) {
|
|
392
|
+
const check: string[] = arr;
|
|
393
|
+
expect(check).toBe(arr);
|
|
394
|
+
} else {
|
|
395
|
+
const _check: never = arr;
|
|
396
|
+
throw new Error("Unreachable");
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
{
|
|
401
|
+
// Type narrowing - extracts array from union
|
|
402
|
+
const arr = castType<string | string[]>(["a"]);
|
|
403
|
+
if (isNonEmptyArray(arr)) {
|
|
404
|
+
const check: string[] = arr;
|
|
405
|
+
expect(check).toBe(arr);
|
|
406
|
+
} else {
|
|
407
|
+
const _check: string = arr;
|
|
408
|
+
throw new Error("Unreachable");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
{
|
|
413
|
+
// Type narrowing - extracts array from union
|
|
414
|
+
const arr = castType<string | string[]>("a");
|
|
415
|
+
if (isNonEmptyArray(arr)) {
|
|
416
|
+
const _check: string[] = arr;
|
|
417
|
+
throw new Error("Unreachable");
|
|
418
|
+
} else {
|
|
419
|
+
const check: string = arr;
|
|
420
|
+
expect(check).toBe(arr);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
{
|
|
425
|
+
// Type narrowing - preserves readonly array
|
|
426
|
+
const arr = castType<readonly number[]>([1, 2]);
|
|
427
|
+
if (isNonEmptyArray(arr)) {
|
|
428
|
+
const check: readonly number[] = arr;
|
|
429
|
+
expect(check).toBe(arr);
|
|
430
|
+
} else {
|
|
431
|
+
const _check: readonly number[] = arr;
|
|
432
|
+
throw new Error("Unreachable");
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
{
|
|
437
|
+
// Type narrowing - preserves tuple
|
|
438
|
+
const arr = castType<[string, number]>(["a", 1]);
|
|
439
|
+
if (isNonEmptyArray(arr)) {
|
|
440
|
+
const check: [string, number] = arr;
|
|
441
|
+
expect(check).toBe(arr);
|
|
442
|
+
} else {
|
|
443
|
+
const _check: never = arr;
|
|
444
|
+
throw new Error("Unreachable");
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
{
|
|
449
|
+
// Type narrowing - unknown input returns unknown[]
|
|
450
|
+
const arr = castType<unknown>(["a", "b"]);
|
|
451
|
+
if (isNonEmptyArray(arr)) {
|
|
452
|
+
const check: unknown[] = arr;
|
|
453
|
+
expect(check).toEqual(["a", "b"]);
|
|
454
|
+
} else {
|
|
455
|
+
const _check: unknown = arr;
|
|
456
|
+
throw new Error("Unreachable");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
{
|
|
461
|
+
// Type narrowing - non-array type (string) returns never
|
|
462
|
+
const arr = castType<string>("hello");
|
|
463
|
+
if (isNonEmptyArray(arr)) {
|
|
464
|
+
const _check: unknown[] = arr;
|
|
465
|
+
throw new Error("Unreachable");
|
|
466
|
+
} else {
|
|
467
|
+
const check: string = arr;
|
|
468
|
+
expect(check).toBe("hello");
|
|
469
|
+
}
|
|
325
470
|
}
|
|
326
471
|
});
|
|
327
472
|
|
|
@@ -329,139 +474,527 @@ describe("primitive.ts", () => {
|
|
|
329
474
|
expect(asArray([])).toEqual([]);
|
|
330
475
|
expect(asArray({})).toEqual([]);
|
|
331
476
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
477
|
+
{
|
|
478
|
+
// Type narrowing - preserves array type
|
|
479
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
480
|
+
const result: string[] = asArray(arr);
|
|
481
|
+
expect(result).toBe(arr);
|
|
482
|
+
}
|
|
336
483
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
484
|
+
{
|
|
485
|
+
// Type narrowing - extracts array from union
|
|
486
|
+
const arr = castType<string | string[]>(["a"]);
|
|
487
|
+
const result: string[] = asArray(arr);
|
|
488
|
+
expect(result).toEqual(["a"]);
|
|
489
|
+
}
|
|
341
490
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
491
|
+
{
|
|
492
|
+
// Type narrowing - preserves readonly array
|
|
493
|
+
const arr = castType<readonly number[]>([1, 2]);
|
|
494
|
+
const result: readonly number[] = asArray(arr);
|
|
495
|
+
expect(result).toBe(arr);
|
|
496
|
+
}
|
|
346
497
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
498
|
+
{
|
|
499
|
+
// Type narrowing - preserves tuple
|
|
500
|
+
const arr = castType<[string, number]>(["a", 1]);
|
|
501
|
+
const result: [string, number] = asArray(arr);
|
|
502
|
+
expect(result).toBe(arr);
|
|
503
|
+
}
|
|
351
504
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
505
|
+
{
|
|
506
|
+
// Type narrowing - unknown input returns unknown[]
|
|
507
|
+
const arr = castType<unknown>(["a", "b"]);
|
|
508
|
+
const result: unknown[] = asArray(arr);
|
|
509
|
+
expect(result).toEqual(["a", "b"]);
|
|
510
|
+
}
|
|
356
511
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
512
|
+
{
|
|
513
|
+
// Type narrowing - non-array type (string) returns unknown[]
|
|
514
|
+
const arr = castType<string>("hello");
|
|
515
|
+
const result: unknown[] = asArray(arr);
|
|
516
|
+
expect(result).toEqual([]);
|
|
517
|
+
}
|
|
361
518
|
});
|
|
362
519
|
|
|
363
520
|
it("toNonEmptyArray", () => {
|
|
364
521
|
expect(toNonEmptyArray([1])).toEqual([1]);
|
|
365
522
|
expect(toNonEmptyArray([])).toBe(undefined);
|
|
366
523
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
524
|
+
{
|
|
525
|
+
// Type narrowing - preserves array type
|
|
526
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
527
|
+
const result = toNonEmptyArray(arr);
|
|
528
|
+
if (result) {
|
|
529
|
+
const check: string[] = result;
|
|
530
|
+
expect(check).toBe(arr);
|
|
531
|
+
}
|
|
373
532
|
}
|
|
374
533
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
534
|
+
{
|
|
535
|
+
// Type narrowing - extracts array from union
|
|
536
|
+
const arr = castType<string | string[]>(["a"]);
|
|
537
|
+
const result = toNonEmptyArray(arr);
|
|
538
|
+
if (result) {
|
|
539
|
+
const check: string[] = result;
|
|
540
|
+
expect(check).toBe(arr);
|
|
541
|
+
}
|
|
381
542
|
}
|
|
382
543
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
544
|
+
{
|
|
545
|
+
// Type narrowing - preserves readonly array
|
|
546
|
+
const arr = castType<readonly number[]>([1, 2]);
|
|
547
|
+
const result = toNonEmptyArray(arr);
|
|
548
|
+
if (result) {
|
|
549
|
+
const check: readonly number[] = result;
|
|
550
|
+
expect(check).toBe(arr);
|
|
551
|
+
}
|
|
389
552
|
}
|
|
390
553
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
554
|
+
{
|
|
555
|
+
// Type narrowing - preserves tuple
|
|
556
|
+
const arr = castType<[string, number]>(["a", 1]);
|
|
557
|
+
const result = toNonEmptyArray(arr);
|
|
558
|
+
if (result) {
|
|
559
|
+
const check: [string, number] = result;
|
|
560
|
+
expect(check).toBe(arr);
|
|
561
|
+
}
|
|
397
562
|
}
|
|
398
563
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
564
|
+
{
|
|
565
|
+
// Type narrowing - unknown input returns unknown[]
|
|
566
|
+
const arr = castType<unknown>(["a", "b"]);
|
|
567
|
+
let result = toNonEmptyArray(arr);
|
|
568
|
+
if (result) {
|
|
569
|
+
// rule out never
|
|
570
|
+
let check = result;
|
|
571
|
+
check = castType<unknown[]>(result);
|
|
572
|
+
expect(check).toEqual(["a", "b"]);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
403
575
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
576
|
+
{
|
|
577
|
+
// Type narrowing - non-array type (string) returns unknown[] | undefined
|
|
578
|
+
const arr = castType<string>("hello");
|
|
579
|
+
const result = toNonEmptyArray(arr);
|
|
580
|
+
expect(result).toBe(undefined);
|
|
581
|
+
if (result) {
|
|
582
|
+
// rule out never
|
|
583
|
+
let _check = result;
|
|
584
|
+
_check = castType<unknown[]>(result);
|
|
585
|
+
throw new Error("Unreachable");
|
|
586
|
+
}
|
|
587
|
+
}
|
|
408
588
|
});
|
|
409
589
|
|
|
410
590
|
it("isObject", () => {
|
|
411
591
|
expect(isObject({})).toBe(true);
|
|
412
592
|
expect(isObject([])).toBe(true);
|
|
413
593
|
expect(isObject(null)).toBe(false);
|
|
594
|
+
|
|
595
|
+
{
|
|
596
|
+
// Type narrowing - preserves object type
|
|
597
|
+
const obj = castType<{ a: string }>({ a: "hello" });
|
|
598
|
+
if (isObject(obj)) {
|
|
599
|
+
const check: { a: string } = obj;
|
|
600
|
+
expect(check).toBe(obj);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
{
|
|
605
|
+
// Type narrowing - extracts object from union
|
|
606
|
+
const obj = castType<string | { a: string }>({ a: "hello" });
|
|
607
|
+
if (isObject(obj)) {
|
|
608
|
+
const check: object = obj;
|
|
609
|
+
expect(check).toEqual({ a: "hello" });
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
{
|
|
614
|
+
// Type narrowing - arrays are objects
|
|
615
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
616
|
+
if (isObject(arr)) {
|
|
617
|
+
const check: string[] = arr;
|
|
618
|
+
expect(check).toBe(arr);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
{
|
|
623
|
+
// Type narrowing - unknown input narrows to object
|
|
624
|
+
const obj = castType<unknown>({ a: 1 });
|
|
625
|
+
if (isObject(obj)) {
|
|
626
|
+
const check: object = obj;
|
|
627
|
+
expect(check).toEqual({ a: 1 });
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
{
|
|
632
|
+
// Type narrowing - null | object union extracts object
|
|
633
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
634
|
+
if (isObject(obj)) {
|
|
635
|
+
const check: { x: number } = obj;
|
|
636
|
+
expect(check).toEqual({ x: 42 });
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("toObject", () => {
|
|
642
|
+
expect(toObject({})).toEqual({});
|
|
643
|
+
expect(toObject([])).toEqual([]);
|
|
644
|
+
expect(toObject(null)).toBe(undefined);
|
|
645
|
+
|
|
646
|
+
{
|
|
647
|
+
// Type narrowing - preserves object type
|
|
648
|
+
const obj = castType<{ a: string }>({ a: "hello" });
|
|
649
|
+
const result = toObject(obj);
|
|
650
|
+
if (result) {
|
|
651
|
+
const check: { a: string } = result;
|
|
652
|
+
expect(check).toBe(obj);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
{
|
|
657
|
+
// Type narrowing - extracts object from union
|
|
658
|
+
const obj = castType<string | { a: string }>({ a: "hello" });
|
|
659
|
+
const result = toObject(obj);
|
|
660
|
+
if (result) {
|
|
661
|
+
const check: { a: string } = result;
|
|
662
|
+
expect(check).toEqual({ a: "hello" });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
{
|
|
667
|
+
// Type narrowing - preserves array (arrays are objects)
|
|
668
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
669
|
+
const result = toObject(arr);
|
|
670
|
+
if (result) {
|
|
671
|
+
const check: string[] = result;
|
|
672
|
+
expect(check).toBe(arr);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
{
|
|
677
|
+
// Type narrowing - unknown input returns object | undefined
|
|
678
|
+
const obj = castType<unknown>({ a: 1 });
|
|
679
|
+
const result: object | undefined = toObject(obj);
|
|
680
|
+
expect(result).toEqual({ a: 1 });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
{
|
|
684
|
+
// Type narrowing - non-object type (string) returns undefined
|
|
685
|
+
const obj = castType<string>("hello");
|
|
686
|
+
const result: object | undefined = toObject(obj);
|
|
687
|
+
expect(result).toBe(undefined);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
{
|
|
691
|
+
// Type narrowing - null | object union extracts object
|
|
692
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
693
|
+
const result = toObject(obj);
|
|
694
|
+
if (result) {
|
|
695
|
+
const check: { x: number } = result;
|
|
696
|
+
expect(check).toEqual({ x: 42 });
|
|
697
|
+
}
|
|
698
|
+
}
|
|
414
699
|
});
|
|
415
700
|
|
|
416
701
|
it("asObject", () => {
|
|
417
702
|
expect(asObject({})).toEqual({});
|
|
418
703
|
expect(asObject([])).toEqual([]);
|
|
419
704
|
expect(asObject(null)).toEqual({});
|
|
705
|
+
|
|
706
|
+
{
|
|
707
|
+
// Type narrowing - preserves object type
|
|
708
|
+
const obj = castType<{ a: string }>({ a: "hello" });
|
|
709
|
+
const result: { a: string } = asObject(obj);
|
|
710
|
+
expect(result).toBe(obj);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
{
|
|
714
|
+
// Type narrowing - extracts object from union
|
|
715
|
+
const obj = castType<string | { a: string }>({ a: "hello" });
|
|
716
|
+
const result: { a: string } = asObject(obj);
|
|
717
|
+
expect(result).toEqual({ a: "hello" });
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
{
|
|
721
|
+
// Type narrowing - preserves array (arrays are objects)
|
|
722
|
+
const arr = castType<string[]>(["a", "b"]);
|
|
723
|
+
const result: string[] = asObject(arr);
|
|
724
|
+
expect(result).toBe(arr);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
{
|
|
728
|
+
// Type narrowing - unknown input returns object
|
|
729
|
+
const obj = castType<unknown>({ a: 1 });
|
|
730
|
+
const result: object = asObject(obj);
|
|
731
|
+
expect(result).toEqual({ a: 1 });
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
{
|
|
735
|
+
// Type narrowing - non-object type (string) returns object
|
|
736
|
+
const obj = castType<string>("hello");
|
|
737
|
+
const result: object = asObject(obj);
|
|
738
|
+
expect(result).toEqual({});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
{
|
|
742
|
+
// Type narrowing - null | object union extracts object
|
|
743
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
744
|
+
const result: { x: number } = asObject(obj);
|
|
745
|
+
expect(result).toEqual({ x: 42 });
|
|
746
|
+
}
|
|
420
747
|
});
|
|
421
748
|
|
|
422
749
|
it("isPlainObject", () => {
|
|
423
750
|
expect(isPlainObject({})).toBe(true);
|
|
424
751
|
expect(isPlainObject([])).toBe(false);
|
|
425
752
|
expect(isPlainObject(null)).toBe(false);
|
|
753
|
+
|
|
754
|
+
{
|
|
755
|
+
// Type narrowing - unknown input narrows to PlainObject
|
|
756
|
+
const obj = castType<unknown>({ a: 1 });
|
|
757
|
+
if (isPlainObject(obj)) {
|
|
758
|
+
const check: PlainObject = obj;
|
|
759
|
+
expect(check).toEqual({ a: 1 });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
{
|
|
764
|
+
// Type narrowing - excludes array from union
|
|
765
|
+
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
766
|
+
if (isPlainObject(obj)) {
|
|
767
|
+
const check: PlainObject = obj;
|
|
768
|
+
expect(check).toEqual({ a: 1 });
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
{
|
|
773
|
+
// Type narrowing - null | object union extracts PlainObject
|
|
774
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
775
|
+
if (isPlainObject(obj)) {
|
|
776
|
+
const check: { x: number } = obj;
|
|
777
|
+
expect(check).toEqual({ x: 42 });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
426
780
|
});
|
|
427
781
|
|
|
428
782
|
it("toPlainObject", () => {
|
|
429
783
|
expect(toPlainObject({})).toEqual({});
|
|
430
784
|
expect(toPlainObject([])).toBe(undefined);
|
|
431
785
|
expect(toPlainObject(null)).toBe(undefined);
|
|
786
|
+
|
|
787
|
+
{
|
|
788
|
+
// Type narrowing - unknown input returns PlainObject | undefined
|
|
789
|
+
const obj = castType<unknown>({ a: 1 });
|
|
790
|
+
const result: PlainObject | undefined = toPlainObject(obj);
|
|
791
|
+
expect(result).toEqual({ a: 1 });
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
{
|
|
795
|
+
// Type narrowing - array returns undefined
|
|
796
|
+
const arr = castType<number[]>([1, 2]);
|
|
797
|
+
const result: PlainObject | undefined = toPlainObject(arr);
|
|
798
|
+
expect(result).toBe(undefined);
|
|
799
|
+
}
|
|
432
800
|
});
|
|
433
801
|
|
|
434
802
|
it("asPlainObject", () => {
|
|
435
803
|
expect(asPlainObject({})).toEqual({});
|
|
436
804
|
expect(asPlainObject([])).toEqual({});
|
|
437
805
|
expect(asPlainObject(null)).toEqual({});
|
|
806
|
+
|
|
807
|
+
{
|
|
808
|
+
// Type narrowing - unknown input returns PlainObject
|
|
809
|
+
const obj = castType<unknown>({ a: 1 });
|
|
810
|
+
const result: PlainObject = asPlainObject(obj);
|
|
811
|
+
expect(result).toEqual({ a: 1 });
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
{
|
|
815
|
+
// Type narrowing - array returns empty object
|
|
816
|
+
const arr = castType<number[]>([1, 2]);
|
|
817
|
+
const result: PlainObject = asPlainObject(arr);
|
|
818
|
+
expect(result).toEqual({});
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
{
|
|
822
|
+
// Type narrowing - non-object returns empty object
|
|
823
|
+
const val = castType<string>("hello");
|
|
824
|
+
const result: PlainObject = asPlainObject(val);
|
|
825
|
+
expect(result).toEqual({});
|
|
826
|
+
}
|
|
438
827
|
});
|
|
439
828
|
|
|
440
829
|
it("isNonEmptyPlainObject", () => {
|
|
441
830
|
expect(isNonEmptyPlainObject({ a: 1 })).toBe(true);
|
|
442
831
|
expect(isNonEmptyPlainObject({})).toBe(false);
|
|
443
832
|
expect(isNonEmptyPlainObject([])).toBe(false);
|
|
833
|
+
|
|
834
|
+
{
|
|
835
|
+
// Type narrowing - unknown input narrows to PlainObject
|
|
836
|
+
const obj = castType<unknown>({ a: 1 });
|
|
837
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
838
|
+
const check: PlainObject = obj;
|
|
839
|
+
expect(check).toEqual({ a: 1 });
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
{
|
|
844
|
+
// Type narrowing - empty object returns false
|
|
845
|
+
const obj = castType<{ a?: number }>({});
|
|
846
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
847
|
+
const check: PlainObject = obj;
|
|
848
|
+
expect(check).toEqual({});
|
|
849
|
+
} else {
|
|
850
|
+
expect(obj).toEqual({});
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
{
|
|
855
|
+
// Type narrowing - excludes array
|
|
856
|
+
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
857
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
858
|
+
const check: PlainObject = obj;
|
|
859
|
+
expect(check).toEqual({ a: 1 });
|
|
860
|
+
}
|
|
861
|
+
}
|
|
444
862
|
});
|
|
445
863
|
|
|
446
864
|
it("toNonEmptyPlainObject", () => {
|
|
447
865
|
expect(toNonEmptyPlainObject({ a: 1 })).toEqual({ a: 1 });
|
|
448
866
|
expect(toNonEmptyPlainObject({})).toBe(undefined);
|
|
867
|
+
|
|
868
|
+
{
|
|
869
|
+
// Type narrowing - preserves object type
|
|
870
|
+
const obj = castType<{ a: number }>({ a: 1 });
|
|
871
|
+
const result = toNonEmptyPlainObject(obj);
|
|
872
|
+
if (result) {
|
|
873
|
+
const check: { a: number } = result;
|
|
874
|
+
expect(check).toEqual({ a: 1 });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
{
|
|
879
|
+
// Type narrowing - undefined input returns undefined
|
|
880
|
+
const obj = castType<{ a: number } | undefined>(undefined);
|
|
881
|
+
const result: { a: number } | undefined = toNonEmptyPlainObject(obj);
|
|
882
|
+
expect(result).toBe(undefined);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
{
|
|
886
|
+
// Type narrowing - empty object returns undefined
|
|
887
|
+
const obj = castType<{ a?: number }>({});
|
|
888
|
+
const result = toNonEmptyPlainObject(obj);
|
|
889
|
+
expect(result).toBe(undefined);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
{
|
|
893
|
+
// type narrowing - non-object type returns PlainObject | undefined
|
|
894
|
+
const obj = castType<number>(42);
|
|
895
|
+
const result: PlainObject | undefined = toNonEmptyPlainObject(obj);
|
|
896
|
+
expect(result).toBe(undefined);
|
|
897
|
+
}
|
|
449
898
|
});
|
|
450
899
|
|
|
451
900
|
it("isNonEmptyJSONObject", () => {
|
|
452
901
|
expect(isNonEmptyJSONObject({ a: 1 })).toBe(true);
|
|
453
902
|
expect(isNonEmptyJSONObject({})).toBe(false);
|
|
454
903
|
expect(isNonEmptyJSONObject([])).toBe(false);
|
|
904
|
+
expect(isNonEmptyJSONObject({ a: undefined })).toBe(false);
|
|
905
|
+
|
|
906
|
+
{
|
|
907
|
+
// Type narrowing - unknown input narrows to PlainObject
|
|
908
|
+
const obj = castType<unknown>({ a: 1 });
|
|
909
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
910
|
+
const check: PlainObject = obj;
|
|
911
|
+
expect(check).toEqual({ a: 1 });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
{
|
|
916
|
+
// Type narrowing - object with only undefined values returns false
|
|
917
|
+
const obj = castType<{ a?: number }>({ a: undefined });
|
|
918
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
919
|
+
const check: PlainObject = obj;
|
|
920
|
+
expect(check).toEqual({});
|
|
921
|
+
} else {
|
|
922
|
+
expect(obj).toEqual({ a: undefined });
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
{
|
|
927
|
+
// Type narrowing - excludes array
|
|
928
|
+
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
929
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
930
|
+
const check: PlainObject = obj;
|
|
931
|
+
expect(check).toEqual({ a: 1 });
|
|
932
|
+
}
|
|
933
|
+
}
|
|
455
934
|
});
|
|
456
935
|
|
|
457
936
|
it("toNonEmptyJSONObject", () => {
|
|
458
937
|
expect(toNonEmptyJSONObject({ a: 1 })).toEqual({ a: 1 });
|
|
459
938
|
expect(toNonEmptyJSONObject({})).toBe(undefined);
|
|
939
|
+
|
|
940
|
+
{
|
|
941
|
+
// Type narrowing - preserves object type
|
|
942
|
+
const obj = castType<{ a: number }>({ a: 1 });
|
|
943
|
+
const result = toNonEmptyJSONObject(obj);
|
|
944
|
+
if (result) {
|
|
945
|
+
const check: { a: number } = result;
|
|
946
|
+
expect(check).toEqual({ a: 1 });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
{
|
|
951
|
+
// Type narrowing - undefined input returns undefined
|
|
952
|
+
const obj = castType<{ a: number } | undefined>(undefined);
|
|
953
|
+
const result: { a: number } | undefined = toNonEmptyJSONObject(obj);
|
|
954
|
+
expect(result).toBe(undefined);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
{
|
|
958
|
+
// Type narrowing - object with only undefined values returns undefined
|
|
959
|
+
const obj = castType<{ a: number; b?: string }>({ a: undefined as unknown as number });
|
|
960
|
+
const result = toNonEmptyJSONObject(obj);
|
|
961
|
+
expect(result).toBe(undefined);
|
|
962
|
+
}
|
|
460
963
|
});
|
|
461
964
|
|
|
462
965
|
it("toPlainObjectOf", () => {
|
|
463
966
|
expect(toPlainObjectOf({ a: true, b: false }, isTrue)).toEqual({ a: true });
|
|
967
|
+
// @ts-expect-error type mismatched
|
|
464
968
|
expect(toPlainObjectOf({ a: 1, b: 2 }, isTrue)).toBe(undefined);
|
|
969
|
+
expect(toPlainObjectOf({ a: 1, b: 2 }, isTruthy)).toEqual({ a: 1, b: 2 });
|
|
970
|
+
expect(toPlainObjectOf(undefined, isTrue)).toBe(undefined);
|
|
971
|
+
|
|
972
|
+
{
|
|
973
|
+
// Type narrowing - result type matches predicate
|
|
974
|
+
const obj = castType<{ a: unknown; b: unknown }>({ a: "hello", b: 123 });
|
|
975
|
+
const result = toPlainObjectOf(obj, isString);
|
|
976
|
+
if (result) {
|
|
977
|
+
const check: { [key: PropertyKey]: string } = result;
|
|
978
|
+
expect(check).toEqual({ a: "hello" });
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
{
|
|
983
|
+
// Type narrowing - filters to number type
|
|
984
|
+
const obj = castType<{ a: unknown; b: unknown }>({ a: 1, b: "hello" });
|
|
985
|
+
const result = toPlainObjectOf(obj, isNumber);
|
|
986
|
+
if (result) {
|
|
987
|
+
const check: { [key: PropertyKey]: number } = result;
|
|
988
|
+
expect(check).toEqual({ a: 1 });
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
{
|
|
993
|
+
// Type narrowing - returns undefined when no values match
|
|
994
|
+
const obj = castType<{ a: unknown; b: unknown }>({ a: "hello", b: "world" });
|
|
995
|
+
const result = toPlainObjectOf(obj, isNumber);
|
|
996
|
+
expect(result).toBe(undefined);
|
|
997
|
+
}
|
|
465
998
|
});
|
|
466
999
|
|
|
467
1000
|
it("toPlainObjectOfTrue", () => {
|