@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.
@@ -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
- // Type narrowing - preserves array type
157
- const stringArr: string[] = ["a", "b"];
158
- if (isArray(stringArr)) {
159
- const _check: string[] = stringArr;
160
- expect(_check).toBe(stringArr);
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
- // Type narrowing - extracts array from union
164
- const unionValue: string | string[] = ["a"];
165
- if (isArray(unionValue)) {
166
- const _check: string[] = unionValue;
167
- expect(_check).toBe(unionValue);
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
- // Type narrowing - preserves readonly array
171
- const readonlyArr: readonly number[] = [1, 2];
172
- if (isArray(readonlyArr)) {
173
- const _check: readonly number[] = readonlyArr;
174
- expect(_check).toBe(readonlyArr);
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
- // Type narrowing - preserves tuple
178
- const tuple: [string, number] = ["a", 1];
179
- if (isArray(tuple)) {
180
- const _check: [string, number] = tuple;
181
- expect(_check).toBe(tuple);
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
- // Type narrowing - unknown input returns any[] | undefined
185
- const unknownValue: unknown = ["a", "b"];
186
- if (isArray(unknownValue)) {
187
- const _check: unknown[] = unknownValue;
188
- expect(_check).toEqual(["a", "b"]);
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
- // Type narrowing - non-array type (string) returns string & any[] | undefined
192
- const stringValue: string = "hello";
193
- if (isArray(stringValue)) {
194
- const _check: unknown[] = stringValue;
195
- expect(_check).toBe(undefined);
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
- // Type narrowing - preserves array type
204
- const stringArr: string[] = ["a", "b"];
205
- const result1 = toArray(stringArr);
206
- if (result1) {
207
- const _check: string[] = result1;
208
- expect(_check).toBe(stringArr);
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
- // Type narrowing - extracts array from union
212
- const unionValue: string | string[] = ["a"];
213
- const result2 = toArray(unionValue);
214
- if (result2) {
215
- const _check: string[] = result2;
216
- expect(_check).toBe(unionValue);
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
- // Type narrowing - preserves readonly array
220
- const readonlyArr: readonly number[] = [1, 2];
221
- const result3 = toArray(readonlyArr);
222
- if (result3) {
223
- const _check: readonly number[] = result3;
224
- expect(_check).toBe(readonlyArr);
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
- // Type narrowing - preserves tuple
228
- const tuple: [string, number] = ["a", 1];
229
- const result4 = toArray(tuple);
230
- if (result4) {
231
- const _check: [string, number] = result4;
232
- expect(_check).toBe(tuple);
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
- // Type narrowing - unknown input returns unknown[] | undefined
236
- const unknownValue: unknown = ["a", "b"];
237
- const result5: unknown[] | undefined = toArray(unknownValue);
238
- expect(result5).toEqual(["a", "b"]);
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
- // Type narrowing - non-array type (string) returns unknown[] | undefined
241
- const stringValue: string = "hello";
242
- const result6: unknown[] | undefined = toArray(stringValue);
243
- expect(result6).toBe(undefined);
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
- // Type narrowing - preserves array type
257
- const stringArr: string[] = ["a", "b"];
258
- if (isNonEmptyArray(stringArr)) {
259
- const _check: string[] = stringArr;
260
- expect(_check).toBe(stringArr);
261
- } else {
262
- const _check: never = stringArr;
263
- throw new Error("Unreachable");
264
- }
265
-
266
- // Type narrowing - extracts array from union
267
- const unionValue: string | string[] = ["a"];
268
- if (isNonEmptyArray(unionValue)) {
269
- const _check: string[] = unionValue;
270
- expect(_check).toBe(unionValue);
271
- } else {
272
- const _check: string = unionValue;
273
- throw new Error("Unreachable");
274
- }
275
-
276
- // Type narrowing - extracts array from union
277
- const stringLikeValue: string | string[] = "a";
278
- if (isNonEmptyArray(stringLikeValue)) {
279
- // @ts-expect-error Unreachable
280
- const _check: string[] = stringLikeValue;
281
- throw new Error("Unreachable");
282
- } else {
283
- const _check: string = stringLikeValue;
284
- expect(_check).toBe(stringLikeValue);
285
- }
286
-
287
- // Type narrowing - preserves readonly array
288
- const readonlyArr: readonly number[] = [1, 2];
289
- if (isNonEmptyArray(readonlyArr)) {
290
- const _check: readonly number[] = readonlyArr;
291
- expect(_check).toBe(readonlyArr);
292
- } else {
293
- const _check: readonly number[] = readonlyArr;
294
- throw new Error("Unreachable");
295
- }
296
-
297
- // Type narrowing - preserves tuple
298
- const tuple: [string, number] = ["a", 1];
299
- if (isNonEmptyArray(tuple)) {
300
- const _check: [string, number] = tuple;
301
- expect(_check).toBe(tuple);
302
- } else {
303
- const _check: never = tuple;
304
- throw new Error("Unreachable");
305
- }
306
-
307
- // Type narrowing - unknown input returns unknown[]
308
- const unknownValue: unknown = ["a", "b"];
309
- if (isNonEmptyArray(unknownValue)) {
310
- const _check: unknown[] = unknownValue;
311
- expect(_check).toEqual(["a", "b"]);
312
- } else {
313
- const _check: unknown = unknownValue;
314
- throw new Error("Unreachable");
315
- }
316
-
317
- // Type narrowing - non-array type (string) returns never
318
- const stringValue: string = "hello";
319
- if (isNonEmptyArray(stringValue)) {
320
- const _check: unknown[] = stringValue;
321
- throw new Error("Unreachable");
322
- } else {
323
- const _check: string = stringValue;
324
- expect(_check).toBe("hello");
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
- // Type narrowing - preserves array type
333
- const stringArr: string[] = ["a", "b"];
334
- const result1: string[] = asArray(stringArr);
335
- expect(result1).toBe(stringArr);
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
- // Type narrowing - extracts array from union
338
- const unionValue: string | string[] = ["a"];
339
- const result2: string[] = asArray(unionValue);
340
- expect(result2).toEqual(["a"]);
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
- // Type narrowing - preserves readonly array
343
- const readonlyArr: readonly number[] = [1, 2];
344
- const result3: readonly number[] = asArray(readonlyArr);
345
- expect(result3).toBe(readonlyArr);
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
- // Type narrowing - preserves tuple
348
- const tuple: [string, number] = ["a", 1];
349
- const result4: [string, number] = asArray(tuple);
350
- expect(result4).toBe(tuple);
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
- // Type narrowing - unknown input returns unknown[]
353
- const unknownValue: unknown = ["a", "b"];
354
- const result5: unknown[] = asArray(unknownValue);
355
- expect(result5).toEqual(["a", "b"]);
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
- // Type narrowing - non-array type (string) returns unknown[]
358
- const stringValue: string = "hello";
359
- const result6: unknown[] = asArray(stringValue);
360
- expect(result6).toEqual([]);
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
- // Type narrowing - preserves array type
368
- const stringArr: string[] = ["a", "b"];
369
- const result1 = toNonEmptyArray(stringArr);
370
- if (result1) {
371
- const _check: string[] = result1;
372
- expect(_check).toBe(stringArr);
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
- // Type narrowing - extracts array from union
376
- const unionValue: string | string[] = ["a"];
377
- const result2 = toNonEmptyArray(unionValue);
378
- if (result2) {
379
- const _check: string[] = result2;
380
- expect(_check).toBe(unionValue);
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
- // Type narrowing - preserves readonly array
384
- const readonlyArr: readonly number[] = [1, 2];
385
- const result3 = toNonEmptyArray(readonlyArr);
386
- if (result3) {
387
- const _check: readonly number[] = result3;
388
- expect(_check).toBe(readonlyArr);
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
- // Type narrowing - preserves tuple
392
- const tuple: [string, number] = ["a", 1];
393
- const result4 = toNonEmptyArray(tuple);
394
- if (result4) {
395
- const _check: [string, number] = result4;
396
- expect(_check).toBe(tuple);
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
- // Type narrowing - unknown input returns unknown[] | undefined
400
- const unknownValue: unknown = ["a", "b"];
401
- const result5: unknown[] | undefined = toNonEmptyArray(unknownValue);
402
- expect(result5).toEqual(["a", "b"]);
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
- // Type narrowing - non-array type (string) returns unknown[] | undefined
405
- const stringValue: string = "hello";
406
- const result6: unknown[] | undefined = toNonEmptyArray(stringValue);
407
- expect(result6).toBe(undefined);
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", () => {