@wopjs/cast 0.1.10 → 0.1.11
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/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/is-to-as.test.ts +1096 -9
- package/src/is-to-as.ts +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ type _ = undefined;
|
|
|
2
2
|
/** Returns `U` if `T` is `never` or `any`, otherwise returns `T`. */
|
|
3
3
|
type SetDefaultType<T, U> = [T, U][T extends any ? (0 extends 1 & T ? 1 : 0) : 1];
|
|
4
4
|
/** Returns `true` if `x` is not `undefined`. */
|
|
5
|
-
declare const isDefined: <T>(x: T |
|
|
5
|
+
declare const isDefined: <T>(x: T | _) => x is Exclude<T, _>;
|
|
6
6
|
declare const isTrue: (x: unknown) => x is true;
|
|
7
7
|
/** Returns `true` if `x` is `true`, otherwise returns `undefined`. */
|
|
8
8
|
declare const toTrue: (x: unknown) => true | _;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ type _ = undefined;
|
|
|
2
2
|
/** Returns `U` if `T` is `never` or `any`, otherwise returns `T`. */
|
|
3
3
|
type SetDefaultType<T, U> = [T, U][T extends any ? (0 extends 1 & T ? 1 : 0) : 1];
|
|
4
4
|
/** Returns `true` if `x` is not `undefined`. */
|
|
5
|
-
declare const isDefined: <T>(x: T |
|
|
5
|
+
declare const isDefined: <T>(x: T | _) => x is Exclude<T, _>;
|
|
6
6
|
declare const isTrue: (x: unknown) => x is true;
|
|
7
7
|
/** Returns `true` if `x` is `true`, otherwise returns `undefined`. */
|
|
8
8
|
declare const toTrue: (x: unknown) => true | _;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/is-to-as.ts","../src/returns.ts"],"names":[],"mappings":";;;AAAA,IAAM,CAAA,GAAI,MAAA;AAOH,IAAM,SAAA,GAAY,CAAI,CAAA,
|
|
1
|
+
{"version":3,"sources":["../src/is-to-as.ts","../src/returns.ts"],"names":[],"mappings":"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","file":"index.js"}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/is-to-as.ts","../src/returns.ts"],"names":[],"mappings":";AAAA,IAAM,CAAA,GAAI,MAAA;AAOH,IAAM,SAAA,GAAY,CAAI,CAAA,
|
|
1
|
+
{"version":3,"sources":["../src/is-to-as.ts","../src/returns.ts"],"names":[],"mappings":"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","file":"index.mjs"}
|
package/package.json
CHANGED
package/src/is-to-as.test.ts
CHANGED
|
@@ -41,12 +41,40 @@ import {
|
|
|
41
41
|
toTruthy,
|
|
42
42
|
} from ".";
|
|
43
43
|
|
|
44
|
+
/** Bypass TypeScript static type inference. */
|
|
44
45
|
const castType = <T>(x: T): T => x;
|
|
45
46
|
|
|
46
47
|
describe("primitive.ts", () => {
|
|
47
48
|
it("isDefined", () => {
|
|
48
49
|
expect(isDefined(1)).toBe(true);
|
|
49
50
|
expect(isDefined(undefined)).toBe(false);
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
// Type narrowing - excludes undefined from type
|
|
54
|
+
const val = castType<string>("hello");
|
|
55
|
+
if (isDefined(val)) {
|
|
56
|
+
const check: string = val;
|
|
57
|
+
expect(check).toBe("hello");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
// Type narrowing - excludes undefined from union
|
|
63
|
+
const val = castType<string | undefined>("hello");
|
|
64
|
+
if (isDefined(val)) {
|
|
65
|
+
const check: string = val;
|
|
66
|
+
expect(check).toBe("hello");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
// Type narrowing - excludes undefined but keeps null
|
|
72
|
+
const val = castType<number | null | undefined>(42);
|
|
73
|
+
if (isDefined(val)) {
|
|
74
|
+
const check: number | null = val;
|
|
75
|
+
expect(check).toBe(42);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
50
78
|
});
|
|
51
79
|
|
|
52
80
|
it("isTrue", () => {
|
|
@@ -236,15 +264,242 @@ describe("primitive.ts", () => {
|
|
|
236
264
|
});
|
|
237
265
|
|
|
238
266
|
it("isNonEmptyString", () => {
|
|
267
|
+
// Runtime behavior - truthy cases
|
|
239
268
|
expect(isNonEmptyString("hello")).toBe(true);
|
|
269
|
+
expect(isNonEmptyString("a")).toBe(true);
|
|
270
|
+
expect(isNonEmptyString(" ")).toBe(true);
|
|
271
|
+
expect(isNonEmptyString(" ")).toBe(true);
|
|
272
|
+
expect(isNonEmptyString("\t")).toBe(true);
|
|
273
|
+
expect(isNonEmptyString("\n")).toBe(true);
|
|
274
|
+
expect(isNonEmptyString("\r\n")).toBe(true);
|
|
275
|
+
expect(isNonEmptyString("0")).toBe(true);
|
|
276
|
+
expect(isNonEmptyString("false")).toBe(true);
|
|
277
|
+
expect(isNonEmptyString("null")).toBe(true);
|
|
278
|
+
expect(isNonEmptyString("undefined")).toBe(true);
|
|
279
|
+
expect(isNonEmptyString("Hello, 世界")).toBe(true);
|
|
280
|
+
expect(isNonEmptyString("🎉")).toBe(true);
|
|
281
|
+
expect(isNonEmptyString("\u0000")).toBe(true); // null character
|
|
282
|
+
expect(isNonEmptyString(String("hello"))).toBe(true);
|
|
283
|
+
|
|
284
|
+
// Runtime behavior - falsy cases
|
|
240
285
|
expect(isNonEmptyString("")).toBe(false);
|
|
286
|
+
expect(isNonEmptyString(String(""))).toBe(false);
|
|
241
287
|
expect(isNonEmptyString(1)).toBe(false);
|
|
288
|
+
expect(isNonEmptyString(0)).toBe(false);
|
|
289
|
+
expect(isNonEmptyString(-1)).toBe(false);
|
|
290
|
+
expect(isNonEmptyString(NaN)).toBe(false);
|
|
291
|
+
expect(isNonEmptyString(Infinity)).toBe(false);
|
|
292
|
+
expect(isNonEmptyString(true)).toBe(false);
|
|
293
|
+
expect(isNonEmptyString(false)).toBe(false);
|
|
294
|
+
expect(isNonEmptyString(null)).toBe(false);
|
|
295
|
+
expect(isNonEmptyString(undefined)).toBe(false);
|
|
296
|
+
expect(isNonEmptyString({})).toBe(false);
|
|
297
|
+
expect(isNonEmptyString({ toString: () => "hello" })).toBe(false);
|
|
298
|
+
expect(isNonEmptyString([])).toBe(false);
|
|
299
|
+
expect(isNonEmptyString(["a", "b"])).toBe(false);
|
|
300
|
+
expect(isNonEmptyString(() => "hello")).toBe(false);
|
|
301
|
+
expect(isNonEmptyString(Symbol("hello"))).toBe(false);
|
|
302
|
+
expect(isNonEmptyString(new Date())).toBe(false);
|
|
303
|
+
expect(isNonEmptyString(/regex/)).toBe(false);
|
|
304
|
+
expect(isNonEmptyString(new String("hello"))).toBe(false); // String object, not primitive
|
|
305
|
+
|
|
306
|
+
{
|
|
307
|
+
// Type narrowing - unknown input narrows to string
|
|
308
|
+
const str = castType<unknown>("hello");
|
|
309
|
+
if (isNonEmptyString(str)) {
|
|
310
|
+
const check: string = str;
|
|
311
|
+
expect(check).toBe("hello");
|
|
312
|
+
} else {
|
|
313
|
+
throw new Error("Unreachable");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
// Type narrowing - empty string returns false
|
|
319
|
+
const str = castType<string>("");
|
|
320
|
+
if (isNonEmptyString(str)) {
|
|
321
|
+
const _check: string = str;
|
|
322
|
+
throw new Error("Unreachable");
|
|
323
|
+
} else {
|
|
324
|
+
expect(str).toBe("");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
{
|
|
329
|
+
// Type narrowing - extracts string from union
|
|
330
|
+
const str = castType<string | number>("hello");
|
|
331
|
+
if (isNonEmptyString(str)) {
|
|
332
|
+
const check: string = str;
|
|
333
|
+
expect(check).toBe("hello");
|
|
334
|
+
} else {
|
|
335
|
+
const _check: string | number = str;
|
|
336
|
+
throw new Error("Unreachable");
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
{
|
|
341
|
+
// Type narrowing - number in union returns false
|
|
342
|
+
const str = castType<string | number>(42);
|
|
343
|
+
if (isNonEmptyString(str)) {
|
|
344
|
+
const _check: string = str;
|
|
345
|
+
throw new Error("Unreachable");
|
|
346
|
+
} else {
|
|
347
|
+
const check: string | number = str;
|
|
348
|
+
expect(check).toBe(42);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
{
|
|
353
|
+
// Type narrowing - null | string union
|
|
354
|
+
const str = castType<string | null>("hello");
|
|
355
|
+
if (isNonEmptyString(str)) {
|
|
356
|
+
const check: string = str;
|
|
357
|
+
expect(check).toBe("hello");
|
|
358
|
+
} else {
|
|
359
|
+
throw new Error("Unreachable");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
{
|
|
364
|
+
// Type narrowing - null returns false
|
|
365
|
+
const str = castType<string | null>(null);
|
|
366
|
+
if (isNonEmptyString(str)) {
|
|
367
|
+
const _check: string = str;
|
|
368
|
+
throw new Error("Unreachable");
|
|
369
|
+
} else {
|
|
370
|
+
const check: string | null = str;
|
|
371
|
+
expect(check).toBe(null);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
{
|
|
376
|
+
// Type narrowing - any input
|
|
377
|
+
const str = castType<any>("hello");
|
|
378
|
+
if (isNonEmptyString(str)) {
|
|
379
|
+
const check: string = str;
|
|
380
|
+
expect(check).toBe("hello");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
{
|
|
385
|
+
// Type narrowing - generic function
|
|
386
|
+
const _fn = <T>(x: T): void => {
|
|
387
|
+
if (isNonEmptyString(x)) {
|
|
388
|
+
const y: string = x;
|
|
389
|
+
expect(typeof y).toBe("string");
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
}
|
|
242
393
|
});
|
|
243
394
|
|
|
244
395
|
it("toNonEmptyString", () => {
|
|
396
|
+
// Runtime behavior - returns value for non-empty strings
|
|
245
397
|
expect(toNonEmptyString("hello")).toBe("hello");
|
|
398
|
+
expect(toNonEmptyString("a")).toBe("a");
|
|
399
|
+
expect(toNonEmptyString(" ")).toBe(" ");
|
|
400
|
+
expect(toNonEmptyString(" ")).toBe(" ");
|
|
401
|
+
expect(toNonEmptyString("\t")).toBe("\t");
|
|
402
|
+
expect(toNonEmptyString("\n")).toBe("\n");
|
|
403
|
+
expect(toNonEmptyString("\r\n")).toBe("\r\n");
|
|
404
|
+
expect(toNonEmptyString("0")).toBe("0");
|
|
405
|
+
expect(toNonEmptyString("false")).toBe("false");
|
|
406
|
+
expect(toNonEmptyString("null")).toBe("null");
|
|
407
|
+
expect(toNonEmptyString("undefined")).toBe("undefined");
|
|
408
|
+
expect(toNonEmptyString("Hello, 世界")).toBe("Hello, 世界");
|
|
409
|
+
expect(toNonEmptyString("🎉")).toBe("🎉");
|
|
410
|
+
expect(toNonEmptyString("\u0000")).toBe("\u0000");
|
|
411
|
+
expect(toNonEmptyString(String("hello"))).toBe("hello");
|
|
412
|
+
|
|
413
|
+
// Runtime behavior - returns undefined for empty string and non-strings
|
|
246
414
|
expect(toNonEmptyString("")).toBe(undefined);
|
|
415
|
+
expect(toNonEmptyString(String(""))).toBe(undefined);
|
|
247
416
|
expect(toNonEmptyString(1)).toBe(undefined);
|
|
417
|
+
expect(toNonEmptyString(0)).toBe(undefined);
|
|
418
|
+
expect(toNonEmptyString(-1)).toBe(undefined);
|
|
419
|
+
expect(toNonEmptyString(NaN)).toBe(undefined);
|
|
420
|
+
expect(toNonEmptyString(Infinity)).toBe(undefined);
|
|
421
|
+
expect(toNonEmptyString(true)).toBe(undefined);
|
|
422
|
+
expect(toNonEmptyString(false)).toBe(undefined);
|
|
423
|
+
expect(toNonEmptyString(null)).toBe(undefined);
|
|
424
|
+
expect(toNonEmptyString(undefined)).toBe(undefined);
|
|
425
|
+
expect(toNonEmptyString({})).toBe(undefined);
|
|
426
|
+
expect(toNonEmptyString({ toString: () => "hello" })).toBe(undefined);
|
|
427
|
+
expect(toNonEmptyString([])).toBe(undefined);
|
|
428
|
+
expect(toNonEmptyString(["a", "b"])).toBe(undefined);
|
|
429
|
+
expect(toNonEmptyString(() => "hello")).toBe(undefined);
|
|
430
|
+
expect(toNonEmptyString(Symbol("hello"))).toBe(undefined);
|
|
431
|
+
expect(toNonEmptyString(new Date())).toBe(undefined);
|
|
432
|
+
expect(toNonEmptyString(/regex/)).toBe(undefined);
|
|
433
|
+
expect(toNonEmptyString(new String("hello"))).toBe(undefined);
|
|
434
|
+
|
|
435
|
+
{
|
|
436
|
+
// Type narrowing - unknown input returns string | undefined
|
|
437
|
+
const str = castType<unknown>("hello");
|
|
438
|
+
const result: string | undefined = toNonEmptyString(str);
|
|
439
|
+
expect(result).toBe("hello");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
{
|
|
443
|
+
// Type narrowing - empty string returns undefined
|
|
444
|
+
const str = castType<string>("");
|
|
445
|
+
const result = toNonEmptyString(str);
|
|
446
|
+
expect(result).toBe(undefined);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
{
|
|
450
|
+
// Type narrowing - extracts string from union
|
|
451
|
+
const str = castType<string | number>("hello");
|
|
452
|
+
const result = toNonEmptyString(str);
|
|
453
|
+
if (result) {
|
|
454
|
+
const check: string = result;
|
|
455
|
+
expect(check).toBe("hello");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
{
|
|
460
|
+
// Type narrowing - number in union returns undefined
|
|
461
|
+
const str = castType<string | number>(42);
|
|
462
|
+
const result = toNonEmptyString(str);
|
|
463
|
+
expect(result).toBe(undefined);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
{
|
|
467
|
+
// Type narrowing - null | string union
|
|
468
|
+
const str = castType<string | null>("hello");
|
|
469
|
+
const result = toNonEmptyString(str);
|
|
470
|
+
if (result) {
|
|
471
|
+
const check: string = result;
|
|
472
|
+
expect(check).toBe("hello");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
{
|
|
477
|
+
// Type narrowing - null returns undefined
|
|
478
|
+
const str = castType<string | null>(null);
|
|
479
|
+
const result = toNonEmptyString(str);
|
|
480
|
+
expect(result).toBe(undefined);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
{
|
|
484
|
+
// Type narrowing - any input returns string | undefined
|
|
485
|
+
const str = castType<any>("hello");
|
|
486
|
+
const result: string | undefined = toNonEmptyString(str);
|
|
487
|
+
expect(result).toBe("hello");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
{
|
|
491
|
+
// Type narrowing - optional chaining works
|
|
492
|
+
const str = castType<unknown>("hello");
|
|
493
|
+
const upper = toNonEmptyString(str)?.toUpperCase();
|
|
494
|
+
expect(upper).toBe("HELLO");
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
{
|
|
498
|
+
// Type narrowing - optional chaining with empty string
|
|
499
|
+
const str = castType<unknown>("");
|
|
500
|
+
const upper = toNonEmptyString(str)?.toUpperCase();
|
|
501
|
+
expect(upper).toBe(undefined);
|
|
502
|
+
}
|
|
248
503
|
});
|
|
249
504
|
|
|
250
505
|
it("isArray", () => {
|
|
@@ -395,14 +650,54 @@ describe("primitive.ts", () => {
|
|
|
395
650
|
});
|
|
396
651
|
|
|
397
652
|
it("isNonEmptyArray", () => {
|
|
398
|
-
// Runtime behavior
|
|
653
|
+
// Runtime behavior - truthy cases
|
|
399
654
|
expect(isNonEmptyArray([1])).toBe(true);
|
|
400
655
|
expect(isNonEmptyArray([1, 2, 3])).toBe(true);
|
|
656
|
+
expect(isNonEmptyArray(["a", "b", "c"])).toBe(true);
|
|
657
|
+
expect(isNonEmptyArray([undefined])).toBe(true);
|
|
658
|
+
expect(isNonEmptyArray([null])).toBe(true);
|
|
659
|
+
expect(isNonEmptyArray([false])).toBe(true);
|
|
660
|
+
expect(isNonEmptyArray([0])).toBe(true);
|
|
661
|
+
expect(isNonEmptyArray([""])).toBe(true);
|
|
662
|
+
expect(isNonEmptyArray([{}])).toBe(true);
|
|
663
|
+
expect(isNonEmptyArray([[]])).toBe(true);
|
|
664
|
+
expect(isNonEmptyArray([() => {}])).toBe(true);
|
|
665
|
+
expect(isNonEmptyArray(new Array(1).fill(undefined))).toBe(true);
|
|
666
|
+
expect(isNonEmptyArray(Array.from({ length: 1 }))).toBe(true);
|
|
667
|
+
// eslint-disable-next-line no-sparse-arrays
|
|
668
|
+
expect(isNonEmptyArray([, , 1])).toBe(true); // sparse array with value
|
|
669
|
+
expect(isNonEmptyArray(Object.assign([], { 0: "a" }))).toBe(true);
|
|
670
|
+
|
|
671
|
+
// Runtime behavior - falsy cases
|
|
401
672
|
expect(isNonEmptyArray([])).toBe(false);
|
|
673
|
+
expect(isNonEmptyArray([])).toBe(false);
|
|
674
|
+
expect(isNonEmptyArray(new Array(0))).toBe(false);
|
|
675
|
+
expect(isNonEmptyArray(Array.from({ length: 0 }))).toBe(false);
|
|
402
676
|
expect(isNonEmptyArray({})).toBe(false);
|
|
677
|
+
expect(isNonEmptyArray({ length: 1, 0: "a" })).toBe(false); // array-like object
|
|
678
|
+
expect(isNonEmptyArray({ length: 0 })).toBe(false);
|
|
403
679
|
expect(isNonEmptyArray(null)).toBe(false);
|
|
404
680
|
expect(isNonEmptyArray(undefined)).toBe(false);
|
|
405
681
|
expect(isNonEmptyArray("string")).toBe(false);
|
|
682
|
+
expect(isNonEmptyArray("")).toBe(false);
|
|
683
|
+
expect(isNonEmptyArray("abc")).toBe(false); // string is not array
|
|
684
|
+
expect(isNonEmptyArray(123)).toBe(false);
|
|
685
|
+
expect(isNonEmptyArray(0)).toBe(false);
|
|
686
|
+
expect(isNonEmptyArray(NaN)).toBe(false);
|
|
687
|
+
expect(isNonEmptyArray(true)).toBe(false);
|
|
688
|
+
expect(isNonEmptyArray(false)).toBe(false);
|
|
689
|
+
expect(isNonEmptyArray(Symbol())).toBe(false);
|
|
690
|
+
expect(isNonEmptyArray(() => {})).toBe(false);
|
|
691
|
+
expect(isNonEmptyArray(function () {})).toBe(false);
|
|
692
|
+
expect(isNonEmptyArray(new Date())).toBe(false);
|
|
693
|
+
expect(isNonEmptyArray(/regex/)).toBe(false);
|
|
694
|
+
expect(isNonEmptyArray(new Map([[0, "a"]]))).toBe(false);
|
|
695
|
+
expect(isNonEmptyArray(new Set([1, 2]))).toBe(false);
|
|
696
|
+
expect(isNonEmptyArray(new Int8Array([1, 2]))).toBe(false); // typed arrays are not Array.isArray
|
|
697
|
+
|
|
698
|
+
// Sparse arrays (length > 0 but no actual elements)
|
|
699
|
+
const sparseArray = new Array(3);
|
|
700
|
+
expect(isNonEmptyArray(sparseArray)).toBe(true); // length is 3, so truthy
|
|
406
701
|
|
|
407
702
|
{
|
|
408
703
|
// Type narrowing - preserves array type
|
|
@@ -429,7 +724,7 @@ describe("primitive.ts", () => {
|
|
|
429
724
|
}
|
|
430
725
|
|
|
431
726
|
{
|
|
432
|
-
// Type narrowing - extracts array from union
|
|
727
|
+
// Type narrowing - extracts array from union (string case)
|
|
433
728
|
const arr = castType<string | string[]>("a");
|
|
434
729
|
if (isNonEmptyArray(arr)) {
|
|
435
730
|
const _check: string[] = arr;
|
|
@@ -440,6 +735,18 @@ describe("primitive.ts", () => {
|
|
|
440
735
|
}
|
|
441
736
|
}
|
|
442
737
|
|
|
738
|
+
{
|
|
739
|
+
// Type narrowing - empty array returns false
|
|
740
|
+
const arr = castType<string[]>([]);
|
|
741
|
+
if (isNonEmptyArray(arr)) {
|
|
742
|
+
const _check: string[] = arr;
|
|
743
|
+
throw new Error("Unreachable");
|
|
744
|
+
} else {
|
|
745
|
+
const check: string[] = arr;
|
|
746
|
+
expect(check).toEqual([]);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
443
750
|
{
|
|
444
751
|
// Type narrowing - preserves readonly array
|
|
445
752
|
const arr = castType<readonly number[]>([1, 2]);
|
|
@@ -487,6 +794,48 @@ describe("primitive.ts", () => {
|
|
|
487
794
|
expect(check).toBe("hello");
|
|
488
795
|
}
|
|
489
796
|
}
|
|
797
|
+
|
|
798
|
+
{
|
|
799
|
+
// Type narrowing - null | array union
|
|
800
|
+
const arr = castType<number[] | null>([1, 2]);
|
|
801
|
+
if (isNonEmptyArray(arr)) {
|
|
802
|
+
const check: number[] = arr;
|
|
803
|
+
expect(check).toEqual([1, 2]);
|
|
804
|
+
} else {
|
|
805
|
+
throw new Error("Unreachable");
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
{
|
|
810
|
+
// Type narrowing - null returns false
|
|
811
|
+
const arr = castType<number[] | null>(null);
|
|
812
|
+
if (isNonEmptyArray(arr)) {
|
|
813
|
+
const _check: number[] = arr;
|
|
814
|
+
throw new Error("Unreachable");
|
|
815
|
+
} else {
|
|
816
|
+
const check: null = arr;
|
|
817
|
+
expect(check).toBe(null);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
{
|
|
822
|
+
// Type narrowing - any input
|
|
823
|
+
const arr = castType<any>([1, 2]);
|
|
824
|
+
if (isNonEmptyArray(arr)) {
|
|
825
|
+
const check: unknown[] = arr;
|
|
826
|
+
expect(check).toEqual([1, 2]);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
{
|
|
831
|
+
// Type narrowing - generic function
|
|
832
|
+
const _fn = <T>(x: T): void => {
|
|
833
|
+
if (isNonEmptyArray(x)) {
|
|
834
|
+
const y: readonly unknown[] = x;
|
|
835
|
+
expect(Array.isArray(y)).toBe(true);
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
}
|
|
490
839
|
});
|
|
491
840
|
|
|
492
841
|
it("asArray", () => {
|
|
@@ -539,8 +888,54 @@ describe("primitive.ts", () => {
|
|
|
539
888
|
});
|
|
540
889
|
|
|
541
890
|
it("toNonEmptyArray", () => {
|
|
891
|
+
// Runtime behavior - returns value for non-empty arrays
|
|
542
892
|
expect(toNonEmptyArray([1])).toEqual([1]);
|
|
893
|
+
expect(toNonEmptyArray([1, 2, 3])).toEqual([1, 2, 3]);
|
|
894
|
+
expect(toNonEmptyArray(["a", "b", "c"])).toEqual(["a", "b", "c"]);
|
|
895
|
+
expect(toNonEmptyArray([undefined])).toEqual([undefined]);
|
|
896
|
+
expect(toNonEmptyArray([null])).toEqual([null]);
|
|
897
|
+
expect(toNonEmptyArray([false])).toEqual([false]);
|
|
898
|
+
expect(toNonEmptyArray([0])).toEqual([0]);
|
|
899
|
+
expect(toNonEmptyArray([""])).toEqual([""]);
|
|
900
|
+
expect(toNonEmptyArray([{}])).toEqual([{}]);
|
|
901
|
+
expect(toNonEmptyArray([[]])).toEqual([[]]);
|
|
902
|
+
const fn = () => {};
|
|
903
|
+
expect(toNonEmptyArray([fn])).toEqual([fn]);
|
|
904
|
+
|
|
905
|
+
// Returns same reference for non-empty arrays
|
|
906
|
+
const arr = [1, 2, 3];
|
|
907
|
+
expect(toNonEmptyArray(arr)).toBe(arr);
|
|
908
|
+
|
|
909
|
+
// Sparse arrays (length > 0)
|
|
910
|
+
const sparseArray = new Array(3);
|
|
911
|
+
expect(toNonEmptyArray(sparseArray)).toBe(sparseArray);
|
|
912
|
+
|
|
913
|
+
// Runtime behavior - returns undefined for empty arrays and non-arrays
|
|
914
|
+
expect(toNonEmptyArray([])).toBe(undefined);
|
|
543
915
|
expect(toNonEmptyArray([])).toBe(undefined);
|
|
916
|
+
expect(toNonEmptyArray(new Array(0))).toBe(undefined);
|
|
917
|
+
expect(toNonEmptyArray(Array.from({ length: 0 }))).toBe(undefined);
|
|
918
|
+
expect(toNonEmptyArray({})).toBe(undefined);
|
|
919
|
+
expect(toNonEmptyArray({ length: 1, 0: "a" })).toBe(undefined); // array-like object
|
|
920
|
+
expect(toNonEmptyArray({ length: 0 })).toBe(undefined);
|
|
921
|
+
expect(toNonEmptyArray(null)).toBe(undefined);
|
|
922
|
+
expect(toNonEmptyArray(undefined)).toBe(undefined);
|
|
923
|
+
expect(toNonEmptyArray("string")).toBe(undefined);
|
|
924
|
+
expect(toNonEmptyArray("")).toBe(undefined);
|
|
925
|
+
expect(toNonEmptyArray("abc")).toBe(undefined);
|
|
926
|
+
expect(toNonEmptyArray(123)).toBe(undefined);
|
|
927
|
+
expect(toNonEmptyArray(0)).toBe(undefined);
|
|
928
|
+
expect(toNonEmptyArray(NaN)).toBe(undefined);
|
|
929
|
+
expect(toNonEmptyArray(true)).toBe(undefined);
|
|
930
|
+
expect(toNonEmptyArray(false)).toBe(undefined);
|
|
931
|
+
expect(toNonEmptyArray(Symbol())).toBe(undefined);
|
|
932
|
+
expect(toNonEmptyArray(() => {})).toBe(undefined);
|
|
933
|
+
expect(toNonEmptyArray(function () {})).toBe(undefined);
|
|
934
|
+
expect(toNonEmptyArray(new Date())).toBe(undefined);
|
|
935
|
+
expect(toNonEmptyArray(/regex/)).toBe(undefined);
|
|
936
|
+
expect(toNonEmptyArray(new Map([[0, "a"]]))).toBe(undefined);
|
|
937
|
+
expect(toNonEmptyArray(new Set([1, 2]))).toBe(undefined);
|
|
938
|
+
expect(toNonEmptyArray(new Int8Array([1, 2]))).toBe(undefined);
|
|
544
939
|
|
|
545
940
|
{
|
|
546
941
|
// Type narrowing - preserves array type
|
|
@@ -549,6 +944,8 @@ describe("primitive.ts", () => {
|
|
|
549
944
|
if (result) {
|
|
550
945
|
const check: string[] = result;
|
|
551
946
|
expect(check).toBe(arr);
|
|
947
|
+
} else {
|
|
948
|
+
throw new Error("Unreachable");
|
|
552
949
|
}
|
|
553
950
|
}
|
|
554
951
|
|
|
@@ -562,6 +959,20 @@ describe("primitive.ts", () => {
|
|
|
562
959
|
}
|
|
563
960
|
}
|
|
564
961
|
|
|
962
|
+
{
|
|
963
|
+
// Type narrowing - string in union returns undefined
|
|
964
|
+
const arr = castType<string | string[]>("hello");
|
|
965
|
+
const result = toNonEmptyArray(arr);
|
|
966
|
+
expect(result).toBe(undefined);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
{
|
|
970
|
+
// Type narrowing - empty array returns undefined
|
|
971
|
+
const arr = castType<string[]>([]);
|
|
972
|
+
const result = toNonEmptyArray(arr);
|
|
973
|
+
expect(result).toBe(undefined);
|
|
974
|
+
}
|
|
975
|
+
|
|
565
976
|
{
|
|
566
977
|
// Type narrowing - preserves readonly array
|
|
567
978
|
const arr = castType<readonly number[]>([1, 2]);
|
|
@@ -583,7 +994,7 @@ describe("primitive.ts", () => {
|
|
|
583
994
|
}
|
|
584
995
|
|
|
585
996
|
{
|
|
586
|
-
// Type narrowing - unknown input returns unknown[]
|
|
997
|
+
// Type narrowing - unknown input returns unknown[] | undefined
|
|
587
998
|
const arr = castType<unknown>(["a", "b"]);
|
|
588
999
|
let result = toNonEmptyArray(arr);
|
|
589
1000
|
if (result) {
|
|
@@ -606,6 +1017,61 @@ describe("primitive.ts", () => {
|
|
|
606
1017
|
throw new Error("Unreachable");
|
|
607
1018
|
}
|
|
608
1019
|
}
|
|
1020
|
+
|
|
1021
|
+
{
|
|
1022
|
+
// Type narrowing - null | array union
|
|
1023
|
+
const arr = castType<number[] | null>([1, 2]);
|
|
1024
|
+
const result = toNonEmptyArray(arr);
|
|
1025
|
+
if (result) {
|
|
1026
|
+
const check: number[] = result;
|
|
1027
|
+
expect(check).toEqual([1, 2]);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
{
|
|
1032
|
+
// Type narrowing - null returns undefined
|
|
1033
|
+
const arr = castType<number[] | null>(null);
|
|
1034
|
+
const result = toNonEmptyArray(arr);
|
|
1035
|
+
expect(result).toBe(undefined);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
{
|
|
1039
|
+
// Type narrowing - undefined | array union
|
|
1040
|
+
const arr = castType<number[] | undefined>([1, 2]);
|
|
1041
|
+
const result = toNonEmptyArray(arr);
|
|
1042
|
+
if (result) {
|
|
1043
|
+
const check: number[] = result;
|
|
1044
|
+
expect(check).toEqual([1, 2]);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
{
|
|
1049
|
+
// Type narrowing - any input returns unknown[] | undefined
|
|
1050
|
+
const arr = castType<any>([1, 2]);
|
|
1051
|
+
const result: unknown[] | undefined = toNonEmptyArray(arr);
|
|
1052
|
+
expect(result).toEqual([1, 2]);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
{
|
|
1056
|
+
// Type narrowing - optional chaining works
|
|
1057
|
+
const arr = castType<unknown>([1, 2, 3]);
|
|
1058
|
+
const length = toNonEmptyArray(arr)?.length;
|
|
1059
|
+
expect(length).toBe(3);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
{
|
|
1063
|
+
// Type narrowing - optional chaining with empty array
|
|
1064
|
+
const arr = castType<unknown>([]);
|
|
1065
|
+
const length = toNonEmptyArray(arr)?.length;
|
|
1066
|
+
expect(length).toBe(undefined);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
{
|
|
1070
|
+
// Type narrowing - map over result
|
|
1071
|
+
const arr = castType<number[]>([1, 2, 3]);
|
|
1072
|
+
const doubled = toNonEmptyArray(arr)?.map(x => x * 2);
|
|
1073
|
+
expect(doubled).toEqual([2, 4, 6]);
|
|
1074
|
+
}
|
|
609
1075
|
});
|
|
610
1076
|
|
|
611
1077
|
it("isObject", () => {
|
|
@@ -848,9 +1314,81 @@ describe("primitive.ts", () => {
|
|
|
848
1314
|
});
|
|
849
1315
|
|
|
850
1316
|
it("isNonEmptyPlainObject", () => {
|
|
1317
|
+
// Runtime behavior - truthy cases
|
|
851
1318
|
expect(isNonEmptyPlainObject({ a: 1 })).toBe(true);
|
|
1319
|
+
expect(isNonEmptyPlainObject({ a: undefined })).toBe(true);
|
|
1320
|
+
expect(isNonEmptyPlainObject({ a: null })).toBe(true);
|
|
1321
|
+
expect(isNonEmptyPlainObject({ a: false })).toBe(true);
|
|
1322
|
+
expect(isNonEmptyPlainObject({ a: 0 })).toBe(true);
|
|
1323
|
+
expect(isNonEmptyPlainObject({ a: "" })).toBe(true);
|
|
1324
|
+
expect(isNonEmptyPlainObject({ "": 1 })).toBe(true);
|
|
1325
|
+
expect(isNonEmptyPlainObject({ 0: "a" })).toBe(true);
|
|
1326
|
+
expect(isNonEmptyPlainObject({ a: 1, b: 2, c: 3 })).toBe(true);
|
|
1327
|
+
expect(isNonEmptyPlainObject({ nested: { deep: { value: 1 } } })).toBe(true);
|
|
1328
|
+
expect(isNonEmptyPlainObject(Object.create(null, { a: { value: 1, enumerable: true } }))).toBe(true);
|
|
1329
|
+
|
|
1330
|
+
// Runtime behavior - falsy cases
|
|
852
1331
|
expect(isNonEmptyPlainObject({})).toBe(false);
|
|
853
1332
|
expect(isNonEmptyPlainObject([])).toBe(false);
|
|
1333
|
+
expect(isNonEmptyPlainObject([1, 2, 3])).toBe(false);
|
|
1334
|
+
expect(isNonEmptyPlainObject("hello")).toBe(false);
|
|
1335
|
+
expect(isNonEmptyPlainObject("")).toBe(false);
|
|
1336
|
+
expect(isNonEmptyPlainObject(1)).toBe(false);
|
|
1337
|
+
expect(isNonEmptyPlainObject(0)).toBe(false);
|
|
1338
|
+
expect(isNonEmptyPlainObject(NaN)).toBe(false);
|
|
1339
|
+
expect(isNonEmptyPlainObject(null)).toBe(false);
|
|
1340
|
+
expect(isNonEmptyPlainObject(undefined)).toBe(false);
|
|
1341
|
+
expect(isNonEmptyPlainObject(false)).toBe(false);
|
|
1342
|
+
expect(isNonEmptyPlainObject(true)).toBe(false);
|
|
1343
|
+
expect(isNonEmptyPlainObject(Symbol())).toBe(false);
|
|
1344
|
+
expect(isNonEmptyPlainObject(() => {})).toBe(false);
|
|
1345
|
+
expect(isNonEmptyPlainObject(function () {})).toBe(false);
|
|
1346
|
+
expect(isNonEmptyPlainObject(new Date())).toBe(false);
|
|
1347
|
+
expect(isNonEmptyPlainObject(/regex/)).toBe(false);
|
|
1348
|
+
expect(isNonEmptyPlainObject(new Map([["a", 1]]))).toBe(false);
|
|
1349
|
+
expect(isNonEmptyPlainObject(new Set([1, 2]))).toBe(false);
|
|
1350
|
+
|
|
1351
|
+
// Objects with only inherited properties (no own properties)
|
|
1352
|
+
const proto = { inherited: 1 };
|
|
1353
|
+
const objWithInherited = Object.create(proto);
|
|
1354
|
+
expect(isNonEmptyPlainObject(objWithInherited)).toBe(false);
|
|
1355
|
+
|
|
1356
|
+
// Objects with own properties shadow inherited
|
|
1357
|
+
const objWithOwn = Object.create(proto);
|
|
1358
|
+
objWithOwn.own = 2;
|
|
1359
|
+
expect(isNonEmptyPlainObject(objWithOwn)).toBe(true);
|
|
1360
|
+
|
|
1361
|
+
// Objects with symbol keys (symbols are not enumerated by for...in)
|
|
1362
|
+
const sym = Symbol("key");
|
|
1363
|
+
const objWithSymbol = { [sym]: 1 };
|
|
1364
|
+
expect(isNonEmptyPlainObject(objWithSymbol)).toBe(false);
|
|
1365
|
+
|
|
1366
|
+
// Objects with both string and symbol keys
|
|
1367
|
+
const objWithBoth = { a: 1, [sym]: 2 };
|
|
1368
|
+
expect(isNonEmptyPlainObject(objWithBoth)).toBe(true);
|
|
1369
|
+
|
|
1370
|
+
// Objects with non-enumerable properties
|
|
1371
|
+
const objWithNonEnumerable = {};
|
|
1372
|
+
Object.defineProperty(objWithNonEnumerable, "hidden", { value: 1, enumerable: false });
|
|
1373
|
+
expect(isNonEmptyPlainObject(objWithNonEnumerable)).toBe(false);
|
|
1374
|
+
|
|
1375
|
+
// Object.create(null) with properties
|
|
1376
|
+
const nullProtoObj = Object.create(null);
|
|
1377
|
+
nullProtoObj.a = 1;
|
|
1378
|
+
expect(isNonEmptyPlainObject(nullProtoObj)).toBe(true);
|
|
1379
|
+
|
|
1380
|
+
// Empty Object.create(null)
|
|
1381
|
+
expect(isNonEmptyPlainObject(Object.create(null))).toBe(false);
|
|
1382
|
+
|
|
1383
|
+
// Class instances (they are plain objects in the shallow sense)
|
|
1384
|
+
class MyClass {
|
|
1385
|
+
prop = 1;
|
|
1386
|
+
}
|
|
1387
|
+
expect(isNonEmptyPlainObject(new MyClass())).toBe(true);
|
|
1388
|
+
|
|
1389
|
+
// Empty class instance
|
|
1390
|
+
class EmptyClass {}
|
|
1391
|
+
expect(isNonEmptyPlainObject(new EmptyClass())).toBe(false);
|
|
854
1392
|
|
|
855
1393
|
{
|
|
856
1394
|
// Type narrowing - unknown input narrows to PlainObject
|
|
@@ -858,6 +1396,8 @@ describe("primitive.ts", () => {
|
|
|
858
1396
|
if (isNonEmptyPlainObject(obj)) {
|
|
859
1397
|
const check: PlainObject = obj;
|
|
860
1398
|
expect(check).toEqual({ a: 1 });
|
|
1399
|
+
} else {
|
|
1400
|
+
throw new Error("Unreachable");
|
|
861
1401
|
}
|
|
862
1402
|
}
|
|
863
1403
|
|
|
@@ -865,8 +1405,8 @@ describe("primitive.ts", () => {
|
|
|
865
1405
|
// Type narrowing - empty object returns false
|
|
866
1406
|
const obj = castType<{ a?: number }>({});
|
|
867
1407
|
if (isNonEmptyPlainObject(obj)) {
|
|
868
|
-
const
|
|
869
|
-
|
|
1408
|
+
const _check: { a?: number } = obj;
|
|
1409
|
+
throw new Error("Unreachable");
|
|
870
1410
|
} else {
|
|
871
1411
|
expect(obj).toEqual({});
|
|
872
1412
|
}
|
|
@@ -875,16 +1415,158 @@ describe("primitive.ts", () => {
|
|
|
875
1415
|
{
|
|
876
1416
|
// Type narrowing - excludes array
|
|
877
1417
|
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
1418
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
1419
|
+
const check: { a: number } = obj;
|
|
1420
|
+
expect(check).toEqual({ a: 1 });
|
|
1421
|
+
} else {
|
|
1422
|
+
const _check: number[] = obj;
|
|
1423
|
+
throw new Error("Unreachable");
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
{
|
|
1428
|
+
// Type narrowing - array returns false
|
|
1429
|
+
const arr = castType<{ a: number } | number[]>([1, 2, 3]);
|
|
1430
|
+
if (isNonEmptyPlainObject(arr)) {
|
|
1431
|
+
const _check: { a: number } = arr;
|
|
1432
|
+
throw new Error("Unreachable");
|
|
1433
|
+
} else {
|
|
1434
|
+
const check: number[] = arr;
|
|
1435
|
+
expect(check).toEqual([1, 2, 3]);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
{
|
|
1440
|
+
// Type narrowing - null | object union extracts PlainObject
|
|
1441
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
1442
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
1443
|
+
const check: { x: number } = obj;
|
|
1444
|
+
expect(check).toEqual({ x: 42 });
|
|
1445
|
+
} else {
|
|
1446
|
+
const _check: null = obj;
|
|
1447
|
+
throw new Error("Unreachable");
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
{
|
|
1452
|
+
// Type narrowing - null returns false
|
|
1453
|
+
const obj = castType<null | { x: number }>(null);
|
|
1454
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
1455
|
+
const _check: { x: number } = obj;
|
|
1456
|
+
throw new Error("Unreachable");
|
|
1457
|
+
} else {
|
|
1458
|
+
const check: null = obj;
|
|
1459
|
+
expect(check).toBe(null);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
{
|
|
1464
|
+
// Type narrowing - preserves object type
|
|
1465
|
+
const obj = castType<{ a: string; b: number }>({ a: "hello", b: 42 });
|
|
1466
|
+
if (isNonEmptyPlainObject(obj)) {
|
|
1467
|
+
const check: { a: string; b: number } = obj;
|
|
1468
|
+
expect(check).toEqual({ a: "hello", b: 42 });
|
|
1469
|
+
} else {
|
|
1470
|
+
throw new Error("Unreachable");
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
{
|
|
1475
|
+
// Type narrowing - any input
|
|
1476
|
+
const obj = castType<any>({ a: 1 });
|
|
878
1477
|
if (isNonEmptyPlainObject(obj)) {
|
|
879
1478
|
const check: PlainObject = obj;
|
|
880
1479
|
expect(check).toEqual({ a: 1 });
|
|
881
1480
|
}
|
|
882
1481
|
}
|
|
1482
|
+
|
|
1483
|
+
{
|
|
1484
|
+
// Type narrowing - generic function
|
|
1485
|
+
const _fn = <T>(x: T): void => {
|
|
1486
|
+
if (isNonEmptyPlainObject(x)) {
|
|
1487
|
+
const y: object = x;
|
|
1488
|
+
expect(typeof y).toBe("object");
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
883
1492
|
});
|
|
884
1493
|
|
|
885
1494
|
it("toNonEmptyPlainObject", () => {
|
|
1495
|
+
// Runtime behavior - returns value for non-empty plain objects
|
|
886
1496
|
expect(toNonEmptyPlainObject({ a: 1 })).toEqual({ a: 1 });
|
|
1497
|
+
expect(toNonEmptyPlainObject({ a: undefined })).toEqual({ a: undefined });
|
|
1498
|
+
expect(toNonEmptyPlainObject({ a: null })).toEqual({ a: null });
|
|
1499
|
+
expect(toNonEmptyPlainObject({ a: false })).toEqual({ a: false });
|
|
1500
|
+
expect(toNonEmptyPlainObject({ a: 0 })).toEqual({ a: 0 });
|
|
1501
|
+
expect(toNonEmptyPlainObject({ a: "" })).toEqual({ a: "" });
|
|
1502
|
+
expect(toNonEmptyPlainObject({ "": 1 })).toEqual({ "": 1 });
|
|
1503
|
+
expect(toNonEmptyPlainObject({ 0: "a" })).toEqual({ 0: "a" });
|
|
1504
|
+
expect(toNonEmptyPlainObject({ a: 1, b: 2, c: 3 })).toEqual({ a: 1, b: 2, c: 3 });
|
|
1505
|
+
expect(toNonEmptyPlainObject({ nested: { deep: { value: 1 } } })).toEqual({ nested: { deep: { value: 1 } } });
|
|
1506
|
+
|
|
1507
|
+
// Runtime behavior - returns undefined for empty or non-objects
|
|
887
1508
|
expect(toNonEmptyPlainObject({})).toBe(undefined);
|
|
1509
|
+
expect(toNonEmptyPlainObject([])).toBe(undefined);
|
|
1510
|
+
expect(toNonEmptyPlainObject([1, 2, 3])).toBe(undefined);
|
|
1511
|
+
expect(toNonEmptyPlainObject("hello")).toBe(undefined);
|
|
1512
|
+
expect(toNonEmptyPlainObject("")).toBe(undefined);
|
|
1513
|
+
expect(toNonEmptyPlainObject(1)).toBe(undefined);
|
|
1514
|
+
expect(toNonEmptyPlainObject(0)).toBe(undefined);
|
|
1515
|
+
expect(toNonEmptyPlainObject(NaN)).toBe(undefined);
|
|
1516
|
+
expect(toNonEmptyPlainObject(null)).toBe(undefined);
|
|
1517
|
+
expect(toNonEmptyPlainObject(undefined)).toBe(undefined);
|
|
1518
|
+
expect(toNonEmptyPlainObject(false)).toBe(undefined);
|
|
1519
|
+
expect(toNonEmptyPlainObject(true)).toBe(undefined);
|
|
1520
|
+
expect(toNonEmptyPlainObject(Symbol())).toBe(undefined);
|
|
1521
|
+
expect(toNonEmptyPlainObject(() => {})).toBe(undefined);
|
|
1522
|
+
expect(toNonEmptyPlainObject(function () {})).toBe(undefined);
|
|
1523
|
+
expect(toNonEmptyPlainObject(new Date())).toBe(undefined);
|
|
1524
|
+
expect(toNonEmptyPlainObject(/regex/)).toBe(undefined);
|
|
1525
|
+
expect(toNonEmptyPlainObject(new Map([["a", 1]]))).toBe(undefined);
|
|
1526
|
+
expect(toNonEmptyPlainObject(new Set([1, 2]))).toBe(undefined);
|
|
1527
|
+
|
|
1528
|
+
// Objects with only inherited properties (no own properties)
|
|
1529
|
+
const proto = { inherited: 1 };
|
|
1530
|
+
const objWithInherited = Object.create(proto);
|
|
1531
|
+
expect(toNonEmptyPlainObject(objWithInherited)).toBe(undefined);
|
|
1532
|
+
|
|
1533
|
+
// Objects with own properties shadow inherited
|
|
1534
|
+
const objWithOwn = Object.create(proto);
|
|
1535
|
+
objWithOwn.own = 2;
|
|
1536
|
+
expect(toNonEmptyPlainObject(objWithOwn)).toBe(objWithOwn);
|
|
1537
|
+
|
|
1538
|
+
// Objects with symbol keys (symbols are not enumerated by for...in)
|
|
1539
|
+
const sym = Symbol("key");
|
|
1540
|
+
const objWithSymbol = { [sym]: 1 };
|
|
1541
|
+
expect(toNonEmptyPlainObject(objWithSymbol)).toBe(undefined);
|
|
1542
|
+
|
|
1543
|
+
// Objects with both string and symbol keys
|
|
1544
|
+
const objWithBoth = { a: 1, [sym]: 2 };
|
|
1545
|
+
expect(toNonEmptyPlainObject(objWithBoth)).toBe(objWithBoth);
|
|
1546
|
+
|
|
1547
|
+
// Objects with non-enumerable properties
|
|
1548
|
+
const objWithNonEnumerable = {};
|
|
1549
|
+
Object.defineProperty(objWithNonEnumerable, "hidden", { value: 1, enumerable: false });
|
|
1550
|
+
expect(toNonEmptyPlainObject(objWithNonEnumerable)).toBe(undefined);
|
|
1551
|
+
|
|
1552
|
+
// Object.create(null) with properties
|
|
1553
|
+
const nullProtoObj = Object.create(null);
|
|
1554
|
+
nullProtoObj.a = 1;
|
|
1555
|
+
expect(toNonEmptyPlainObject(nullProtoObj)).toBe(nullProtoObj);
|
|
1556
|
+
|
|
1557
|
+
// Empty Object.create(null)
|
|
1558
|
+
expect(toNonEmptyPlainObject(Object.create(null))).toBe(undefined);
|
|
1559
|
+
|
|
1560
|
+
// Class instances (they are plain objects in the shallow sense)
|
|
1561
|
+
class MyClass {
|
|
1562
|
+
prop = 1;
|
|
1563
|
+
}
|
|
1564
|
+
const instance = new MyClass();
|
|
1565
|
+
expect(toNonEmptyPlainObject(instance)).toBe(instance);
|
|
1566
|
+
|
|
1567
|
+
// Empty class instance
|
|
1568
|
+
class EmptyClass {}
|
|
1569
|
+
expect(toNonEmptyPlainObject(new EmptyClass())).toBe(undefined);
|
|
888
1570
|
|
|
889
1571
|
{
|
|
890
1572
|
// Type narrowing - preserves object type
|
|
@@ -893,6 +1575,8 @@ describe("primitive.ts", () => {
|
|
|
893
1575
|
if (result) {
|
|
894
1576
|
const check: { a: number } = result;
|
|
895
1577
|
expect(check).toEqual({ a: 1 });
|
|
1578
|
+
} else {
|
|
1579
|
+
throw new Error("Unreachable");
|
|
896
1580
|
}
|
|
897
1581
|
}
|
|
898
1582
|
|
|
@@ -911,18 +1595,179 @@ describe("primitive.ts", () => {
|
|
|
911
1595
|
}
|
|
912
1596
|
|
|
913
1597
|
{
|
|
914
|
-
//
|
|
1598
|
+
// Type narrowing - non-object type returns PlainObject | undefined
|
|
915
1599
|
const obj = castType<number>(42);
|
|
916
1600
|
const result: PlainObject | undefined = toNonEmptyPlainObject(obj);
|
|
917
1601
|
expect(result).toBe(undefined);
|
|
918
1602
|
}
|
|
1603
|
+
|
|
1604
|
+
{
|
|
1605
|
+
// Type narrowing - unknown input returns PlainObject | undefined
|
|
1606
|
+
const obj = castType<unknown>({ a: 1 });
|
|
1607
|
+
const result: PlainObject | undefined = toNonEmptyPlainObject(obj);
|
|
1608
|
+
expect(result).toEqual({ a: 1 });
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
{
|
|
1612
|
+
// Type narrowing - any input returns PlainObject | undefined
|
|
1613
|
+
const obj = castType<any>({ a: 1 });
|
|
1614
|
+
const result: PlainObject | undefined = toNonEmptyPlainObject(obj);
|
|
1615
|
+
expect(result).toEqual({ a: 1 });
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
{
|
|
1619
|
+
// Type narrowing - extracts object from union with array
|
|
1620
|
+
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
1621
|
+
const result = toNonEmptyPlainObject(obj);
|
|
1622
|
+
if (result) {
|
|
1623
|
+
const check: { a: number } = result;
|
|
1624
|
+
expect(check).toEqual({ a: 1 });
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
{
|
|
1629
|
+
// Type narrowing - array in union returns undefined
|
|
1630
|
+
const obj = castType<{ a: number } | number[]>([1, 2, 3]);
|
|
1631
|
+
const result = toNonEmptyPlainObject(obj);
|
|
1632
|
+
expect(result).toBe(undefined);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
{
|
|
1636
|
+
// Type narrowing - null | object union extracts PlainObject
|
|
1637
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
1638
|
+
const result = toNonEmptyPlainObject(obj);
|
|
1639
|
+
if (result) {
|
|
1640
|
+
const check: { x: number } = result;
|
|
1641
|
+
expect(check).toEqual({ x: 42 });
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
{
|
|
1646
|
+
// Type narrowing - null returns undefined
|
|
1647
|
+
const obj = castType<null | { x: number }>(null);
|
|
1648
|
+
const result = toNonEmptyPlainObject(obj);
|
|
1649
|
+
expect(result).toBe(undefined);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
{
|
|
1653
|
+
// Type narrowing - preserves complex object type
|
|
1654
|
+
const obj = castType<{ a: string; b: number; c: boolean }>({ a: "hello", b: 42, c: true });
|
|
1655
|
+
const result = toNonEmptyPlainObject(obj);
|
|
1656
|
+
if (result) {
|
|
1657
|
+
const check: { a: string; b: number; c: boolean } = result;
|
|
1658
|
+
expect(check).toEqual({ a: "hello", b: 42, c: true });
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
{
|
|
1663
|
+
// Type narrowing - optional chaining works
|
|
1664
|
+
const obj = castType<unknown>({ a: 1 });
|
|
1665
|
+
const keys = Object.keys(toNonEmptyPlainObject(obj) ?? {});
|
|
1666
|
+
expect(keys).toEqual(["a"]);
|
|
1667
|
+
}
|
|
919
1668
|
});
|
|
920
1669
|
|
|
921
1670
|
it("isNonEmptyJSONObject", () => {
|
|
1671
|
+
// Runtime behavior - truthy cases (has at least one non-undefined value)
|
|
922
1672
|
expect(isNonEmptyJSONObject({ a: 1 })).toBe(true);
|
|
1673
|
+
expect(isNonEmptyJSONObject({ a: null })).toBe(true);
|
|
1674
|
+
expect(isNonEmptyJSONObject({ a: false })).toBe(true);
|
|
1675
|
+
expect(isNonEmptyJSONObject({ a: 0 })).toBe(true);
|
|
1676
|
+
expect(isNonEmptyJSONObject({ a: "" })).toBe(true);
|
|
1677
|
+
expect(isNonEmptyJSONObject({ "": 1 })).toBe(true);
|
|
1678
|
+
expect(isNonEmptyJSONObject({ 0: "a" })).toBe(true);
|
|
1679
|
+
expect(isNonEmptyJSONObject({ a: 1, b: 2, c: 3 })).toBe(true);
|
|
1680
|
+
expect(isNonEmptyJSONObject({ nested: { deep: { value: 1 } } })).toBe(true);
|
|
1681
|
+
expect(isNonEmptyJSONObject(Object.create(null, { a: { value: 1, enumerable: true } }))).toBe(true);
|
|
1682
|
+
// Mixed undefined and defined values - should be true
|
|
1683
|
+
expect(isNonEmptyJSONObject({ a: undefined, b: 1 })).toBe(true);
|
|
1684
|
+
expect(isNonEmptyJSONObject({ a: 1, b: undefined })).toBe(true);
|
|
1685
|
+
expect(isNonEmptyJSONObject({ a: undefined, b: undefined, c: null })).toBe(true);
|
|
1686
|
+
|
|
1687
|
+
// Runtime behavior - falsy cases
|
|
923
1688
|
expect(isNonEmptyJSONObject({})).toBe(false);
|
|
924
|
-
expect(isNonEmptyJSONObject([])).toBe(false);
|
|
925
1689
|
expect(isNonEmptyJSONObject({ a: undefined })).toBe(false);
|
|
1690
|
+
expect(isNonEmptyJSONObject({ a: undefined, b: undefined })).toBe(false);
|
|
1691
|
+
expect(isNonEmptyJSONObject([])).toBe(false);
|
|
1692
|
+
expect(isNonEmptyJSONObject([1, 2, 3])).toBe(false);
|
|
1693
|
+
expect(isNonEmptyJSONObject("hello")).toBe(false);
|
|
1694
|
+
expect(isNonEmptyJSONObject("")).toBe(false);
|
|
1695
|
+
expect(isNonEmptyJSONObject(1)).toBe(false);
|
|
1696
|
+
expect(isNonEmptyJSONObject(0)).toBe(false);
|
|
1697
|
+
expect(isNonEmptyJSONObject(NaN)).toBe(false);
|
|
1698
|
+
expect(isNonEmptyJSONObject(null)).toBe(false);
|
|
1699
|
+
expect(isNonEmptyJSONObject(undefined)).toBe(false);
|
|
1700
|
+
expect(isNonEmptyJSONObject(false)).toBe(false);
|
|
1701
|
+
expect(isNonEmptyJSONObject(true)).toBe(false);
|
|
1702
|
+
expect(isNonEmptyJSONObject(Symbol())).toBe(false);
|
|
1703
|
+
expect(isNonEmptyJSONObject(() => {})).toBe(false);
|
|
1704
|
+
expect(isNonEmptyJSONObject(function () {})).toBe(false);
|
|
1705
|
+
expect(isNonEmptyJSONObject(new Date())).toBe(false);
|
|
1706
|
+
expect(isNonEmptyJSONObject(/regex/)).toBe(false);
|
|
1707
|
+
expect(isNonEmptyJSONObject(new Map([["a", 1]]))).toBe(false);
|
|
1708
|
+
expect(isNonEmptyJSONObject(new Set([1, 2]))).toBe(false);
|
|
1709
|
+
|
|
1710
|
+
// Objects with only inherited properties (no own properties)
|
|
1711
|
+
const proto = { inherited: 1 };
|
|
1712
|
+
const objWithInherited = Object.create(proto);
|
|
1713
|
+
expect(isNonEmptyJSONObject(objWithInherited)).toBe(false);
|
|
1714
|
+
|
|
1715
|
+
// Objects with inherited and own undefined property
|
|
1716
|
+
const objWithOwnUndefined = Object.create(proto);
|
|
1717
|
+
objWithOwnUndefined.own = undefined;
|
|
1718
|
+
expect(isNonEmptyJSONObject(objWithOwnUndefined)).toBe(false);
|
|
1719
|
+
|
|
1720
|
+
// Objects with own non-undefined property
|
|
1721
|
+
const objWithOwn = Object.create(proto);
|
|
1722
|
+
objWithOwn.own = 2;
|
|
1723
|
+
expect(isNonEmptyJSONObject(objWithOwn)).toBe(true);
|
|
1724
|
+
|
|
1725
|
+
// Objects with symbol keys (symbols are not enumerated by for...in)
|
|
1726
|
+
const sym = Symbol("key");
|
|
1727
|
+
const objWithSymbol = { [sym]: 1 };
|
|
1728
|
+
expect(isNonEmptyJSONObject(objWithSymbol)).toBe(false);
|
|
1729
|
+
|
|
1730
|
+
// Objects with both string and symbol keys
|
|
1731
|
+
const objWithBoth = { a: 1, [sym]: 2 };
|
|
1732
|
+
expect(isNonEmptyJSONObject(objWithBoth)).toBe(true);
|
|
1733
|
+
|
|
1734
|
+
// Objects with symbol key and only undefined string key
|
|
1735
|
+
const objWithSymbolAndUndefined = { a: undefined, [sym]: 2 };
|
|
1736
|
+
expect(isNonEmptyJSONObject(objWithSymbolAndUndefined)).toBe(false);
|
|
1737
|
+
|
|
1738
|
+
// Objects with non-enumerable properties
|
|
1739
|
+
const objWithNonEnumerable = {};
|
|
1740
|
+
Object.defineProperty(objWithNonEnumerable, "hidden", { value: 1, enumerable: false });
|
|
1741
|
+
expect(isNonEmptyJSONObject(objWithNonEnumerable)).toBe(false);
|
|
1742
|
+
|
|
1743
|
+
// Object.create(null) with non-undefined property
|
|
1744
|
+
const nullProtoObj = Object.create(null);
|
|
1745
|
+
nullProtoObj.a = 1;
|
|
1746
|
+
expect(isNonEmptyJSONObject(nullProtoObj)).toBe(true);
|
|
1747
|
+
|
|
1748
|
+
// Object.create(null) with only undefined property
|
|
1749
|
+
const nullProtoObjUndefined = Object.create(null);
|
|
1750
|
+
nullProtoObjUndefined.a = undefined;
|
|
1751
|
+
expect(isNonEmptyJSONObject(nullProtoObjUndefined)).toBe(false);
|
|
1752
|
+
|
|
1753
|
+
// Empty Object.create(null)
|
|
1754
|
+
expect(isNonEmptyJSONObject(Object.create(null))).toBe(false);
|
|
1755
|
+
|
|
1756
|
+
// Class instances with non-undefined property
|
|
1757
|
+
class MyClass {
|
|
1758
|
+
prop = 1;
|
|
1759
|
+
}
|
|
1760
|
+
expect(isNonEmptyJSONObject(new MyClass())).toBe(true);
|
|
1761
|
+
|
|
1762
|
+
// Class instances with only undefined property
|
|
1763
|
+
class MyClassUndefined {
|
|
1764
|
+
prop = undefined;
|
|
1765
|
+
}
|
|
1766
|
+
expect(isNonEmptyJSONObject(new MyClassUndefined())).toBe(false);
|
|
1767
|
+
|
|
1768
|
+
// Empty class instance
|
|
1769
|
+
class EmptyClass {}
|
|
1770
|
+
expect(isNonEmptyJSONObject(new EmptyClass())).toBe(false);
|
|
926
1771
|
|
|
927
1772
|
{
|
|
928
1773
|
// Type narrowing - unknown input narrows to PlainObject
|
|
@@ -930,6 +1775,8 @@ describe("primitive.ts", () => {
|
|
|
930
1775
|
if (isNonEmptyJSONObject(obj)) {
|
|
931
1776
|
const check: PlainObject = obj;
|
|
932
1777
|
expect(check).toEqual({ a: 1 });
|
|
1778
|
+
} else {
|
|
1779
|
+
throw new Error("Unreachable");
|
|
933
1780
|
}
|
|
934
1781
|
}
|
|
935
1782
|
|
|
@@ -937,8 +1784,8 @@ describe("primitive.ts", () => {
|
|
|
937
1784
|
// Type narrowing - object with only undefined values returns false
|
|
938
1785
|
const obj = castType<{ a?: number }>({ a: undefined });
|
|
939
1786
|
if (isNonEmptyJSONObject(obj)) {
|
|
940
|
-
const
|
|
941
|
-
|
|
1787
|
+
const _check: { a?: number } = obj;
|
|
1788
|
+
throw new Error("Unreachable");
|
|
942
1789
|
} else {
|
|
943
1790
|
expect(obj).toEqual({ a: undefined });
|
|
944
1791
|
}
|
|
@@ -947,16 +1794,182 @@ describe("primitive.ts", () => {
|
|
|
947
1794
|
{
|
|
948
1795
|
// Type narrowing - excludes array
|
|
949
1796
|
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
1797
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
1798
|
+
const check: { a: number } = obj;
|
|
1799
|
+
expect(check).toEqual({ a: 1 });
|
|
1800
|
+
} else {
|
|
1801
|
+
const _check: number[] = obj;
|
|
1802
|
+
throw new Error("Unreachable");
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
{
|
|
1807
|
+
// Type narrowing - array returns false
|
|
1808
|
+
const arr = castType<{ a: number } | number[]>([1, 2, 3]);
|
|
1809
|
+
if (isNonEmptyJSONObject(arr)) {
|
|
1810
|
+
const _check: { a: number } = arr;
|
|
1811
|
+
throw new Error("Unreachable");
|
|
1812
|
+
} else {
|
|
1813
|
+
const check: number[] = arr;
|
|
1814
|
+
expect(check).toEqual([1, 2, 3]);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
{
|
|
1819
|
+
// Type narrowing - null | object union extracts PlainObject
|
|
1820
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
1821
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
1822
|
+
const check: { x: number } = obj;
|
|
1823
|
+
expect(check).toEqual({ x: 42 });
|
|
1824
|
+
} else {
|
|
1825
|
+
const _check: null = obj;
|
|
1826
|
+
throw new Error("Unreachable");
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
{
|
|
1831
|
+
// Type narrowing - null returns false
|
|
1832
|
+
const obj = castType<null | { x: number }>(null);
|
|
1833
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
1834
|
+
const _check: { x: number } = obj;
|
|
1835
|
+
throw new Error("Unreachable");
|
|
1836
|
+
} else {
|
|
1837
|
+
const check: null = obj;
|
|
1838
|
+
expect(check).toBe(null);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
{
|
|
1843
|
+
// Type narrowing - preserves object type
|
|
1844
|
+
const obj = castType<{ a: string; b: number }>({ a: "hello", b: 42 });
|
|
1845
|
+
if (isNonEmptyJSONObject(obj)) {
|
|
1846
|
+
const check: { a: string; b: number } = obj;
|
|
1847
|
+
expect(check).toEqual({ a: "hello", b: 42 });
|
|
1848
|
+
} else {
|
|
1849
|
+
throw new Error("Unreachable");
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
{
|
|
1854
|
+
// Type narrowing - any input
|
|
1855
|
+
const obj = castType<any>({ a: 1 });
|
|
950
1856
|
if (isNonEmptyJSONObject(obj)) {
|
|
951
1857
|
const check: PlainObject = obj;
|
|
952
1858
|
expect(check).toEqual({ a: 1 });
|
|
953
1859
|
}
|
|
954
1860
|
}
|
|
1861
|
+
|
|
1862
|
+
{
|
|
1863
|
+
// Type narrowing - generic function
|
|
1864
|
+
const _fn = <T>(x: T): void => {
|
|
1865
|
+
if (isNonEmptyJSONObject(x)) {
|
|
1866
|
+
const y: object = x;
|
|
1867
|
+
expect(typeof y).toBe("object");
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
955
1871
|
});
|
|
956
1872
|
|
|
957
1873
|
it("toNonEmptyJSONObject", () => {
|
|
1874
|
+
// Runtime behavior - returns value for objects with at least one non-undefined value
|
|
958
1875
|
expect(toNonEmptyJSONObject({ a: 1 })).toEqual({ a: 1 });
|
|
1876
|
+
expect(toNonEmptyJSONObject({ a: null })).toEqual({ a: null });
|
|
1877
|
+
expect(toNonEmptyJSONObject({ a: false })).toEqual({ a: false });
|
|
1878
|
+
expect(toNonEmptyJSONObject({ a: 0 })).toEqual({ a: 0 });
|
|
1879
|
+
expect(toNonEmptyJSONObject({ a: "" })).toEqual({ a: "" });
|
|
1880
|
+
expect(toNonEmptyJSONObject({ "": 1 })).toEqual({ "": 1 });
|
|
1881
|
+
expect(toNonEmptyJSONObject({ 0: "a" })).toEqual({ 0: "a" });
|
|
1882
|
+
expect(toNonEmptyJSONObject({ a: 1, b: 2, c: 3 })).toEqual({ a: 1, b: 2, c: 3 });
|
|
1883
|
+
expect(toNonEmptyJSONObject({ nested: { deep: { value: 1 } } })).toEqual({ nested: { deep: { value: 1 } } });
|
|
1884
|
+
// Mixed undefined and defined values - should return the object
|
|
1885
|
+
expect(toNonEmptyJSONObject({ a: undefined, b: 1 })).toEqual({ a: undefined, b: 1 });
|
|
1886
|
+
expect(toNonEmptyJSONObject({ a: 1, b: undefined })).toEqual({ a: 1, b: undefined });
|
|
1887
|
+
|
|
1888
|
+
// Runtime behavior - returns undefined for empty or non-objects or all-undefined
|
|
959
1889
|
expect(toNonEmptyJSONObject({})).toBe(undefined);
|
|
1890
|
+
expect(toNonEmptyJSONObject({ a: undefined })).toBe(undefined);
|
|
1891
|
+
expect(toNonEmptyJSONObject({ a: undefined, b: undefined })).toBe(undefined);
|
|
1892
|
+
expect(toNonEmptyJSONObject([])).toBe(undefined);
|
|
1893
|
+
expect(toNonEmptyJSONObject([1, 2, 3])).toBe(undefined);
|
|
1894
|
+
expect(toNonEmptyJSONObject("hello")).toBe(undefined);
|
|
1895
|
+
expect(toNonEmptyJSONObject("")).toBe(undefined);
|
|
1896
|
+
expect(toNonEmptyJSONObject(1)).toBe(undefined);
|
|
1897
|
+
expect(toNonEmptyJSONObject(0)).toBe(undefined);
|
|
1898
|
+
expect(toNonEmptyJSONObject(NaN)).toBe(undefined);
|
|
1899
|
+
expect(toNonEmptyJSONObject(null)).toBe(undefined);
|
|
1900
|
+
expect(toNonEmptyJSONObject(undefined)).toBe(undefined);
|
|
1901
|
+
expect(toNonEmptyJSONObject(false)).toBe(undefined);
|
|
1902
|
+
expect(toNonEmptyJSONObject(true)).toBe(undefined);
|
|
1903
|
+
expect(toNonEmptyJSONObject(Symbol())).toBe(undefined);
|
|
1904
|
+
expect(toNonEmptyJSONObject(() => {})).toBe(undefined);
|
|
1905
|
+
expect(toNonEmptyJSONObject(function () {})).toBe(undefined);
|
|
1906
|
+
expect(toNonEmptyJSONObject(new Date())).toBe(undefined);
|
|
1907
|
+
expect(toNonEmptyJSONObject(/regex/)).toBe(undefined);
|
|
1908
|
+
expect(toNonEmptyJSONObject(new Map([["a", 1]]))).toBe(undefined);
|
|
1909
|
+
expect(toNonEmptyJSONObject(new Set([1, 2]))).toBe(undefined);
|
|
1910
|
+
|
|
1911
|
+
// Objects with only inherited properties (no own properties)
|
|
1912
|
+
const proto = { inherited: 1 };
|
|
1913
|
+
const objWithInherited = Object.create(proto);
|
|
1914
|
+
expect(toNonEmptyJSONObject(objWithInherited)).toBe(undefined);
|
|
1915
|
+
|
|
1916
|
+
// Objects with inherited and own undefined property
|
|
1917
|
+
const objWithOwnUndefined = Object.create(proto);
|
|
1918
|
+
objWithOwnUndefined.own = undefined;
|
|
1919
|
+
expect(toNonEmptyJSONObject(objWithOwnUndefined)).toBe(undefined);
|
|
1920
|
+
|
|
1921
|
+
// Objects with own non-undefined property
|
|
1922
|
+
const objWithOwn = Object.create(proto);
|
|
1923
|
+
objWithOwn.own = 2;
|
|
1924
|
+
expect(toNonEmptyJSONObject(objWithOwn)).toBe(objWithOwn);
|
|
1925
|
+
|
|
1926
|
+
// Objects with symbol keys (symbols are not enumerated by for...in)
|
|
1927
|
+
const sym = Symbol("key");
|
|
1928
|
+
const objWithSymbol = { [sym]: 1 };
|
|
1929
|
+
expect(toNonEmptyJSONObject(objWithSymbol)).toBe(undefined);
|
|
1930
|
+
|
|
1931
|
+
// Objects with both string and symbol keys
|
|
1932
|
+
const objWithBoth = { a: 1, [sym]: 2 };
|
|
1933
|
+
expect(toNonEmptyJSONObject(objWithBoth)).toBe(objWithBoth);
|
|
1934
|
+
|
|
1935
|
+
// Objects with symbol key and only undefined string key
|
|
1936
|
+
const objWithSymbolAndUndefined = { a: undefined, [sym]: 2 };
|
|
1937
|
+
expect(toNonEmptyJSONObject(objWithSymbolAndUndefined)).toBe(undefined);
|
|
1938
|
+
|
|
1939
|
+
// Objects with non-enumerable properties
|
|
1940
|
+
const objWithNonEnumerable = {};
|
|
1941
|
+
Object.defineProperty(objWithNonEnumerable, "hidden", { value: 1, enumerable: false });
|
|
1942
|
+
expect(toNonEmptyJSONObject(objWithNonEnumerable)).toBe(undefined);
|
|
1943
|
+
|
|
1944
|
+
// Object.create(null) with non-undefined property
|
|
1945
|
+
const nullProtoObj = Object.create(null);
|
|
1946
|
+
nullProtoObj.a = 1;
|
|
1947
|
+
expect(toNonEmptyJSONObject(nullProtoObj)).toBe(nullProtoObj);
|
|
1948
|
+
|
|
1949
|
+
// Object.create(null) with only undefined property
|
|
1950
|
+
const nullProtoObjUndefined = Object.create(null);
|
|
1951
|
+
nullProtoObjUndefined.a = undefined;
|
|
1952
|
+
expect(toNonEmptyJSONObject(nullProtoObjUndefined)).toBe(undefined);
|
|
1953
|
+
|
|
1954
|
+
// Empty Object.create(null)
|
|
1955
|
+
expect(toNonEmptyJSONObject(Object.create(null))).toBe(undefined);
|
|
1956
|
+
|
|
1957
|
+
// Class instances with non-undefined property
|
|
1958
|
+
class MyClass {
|
|
1959
|
+
prop = 1;
|
|
1960
|
+
}
|
|
1961
|
+
const instance = new MyClass();
|
|
1962
|
+
expect(toNonEmptyJSONObject(instance)).toBe(instance);
|
|
1963
|
+
|
|
1964
|
+
// Class instances with only undefined property
|
|
1965
|
+
class MyClassUndefined {
|
|
1966
|
+
prop = undefined;
|
|
1967
|
+
}
|
|
1968
|
+
expect(toNonEmptyJSONObject(new MyClassUndefined())).toBe(undefined);
|
|
1969
|
+
|
|
1970
|
+
// Empty class instance
|
|
1971
|
+
class EmptyClass {}
|
|
1972
|
+
expect(toNonEmptyJSONObject(new EmptyClass())).toBe(undefined);
|
|
960
1973
|
|
|
961
1974
|
{
|
|
962
1975
|
// Type narrowing - preserves object type
|
|
@@ -965,6 +1978,8 @@ describe("primitive.ts", () => {
|
|
|
965
1978
|
if (result) {
|
|
966
1979
|
const check: { a: number } = result;
|
|
967
1980
|
expect(check).toEqual({ a: 1 });
|
|
1981
|
+
} else {
|
|
1982
|
+
throw new Error("Unreachable");
|
|
968
1983
|
}
|
|
969
1984
|
}
|
|
970
1985
|
|
|
@@ -981,6 +1996,78 @@ describe("primitive.ts", () => {
|
|
|
981
1996
|
const result = toNonEmptyJSONObject(obj);
|
|
982
1997
|
expect(result).toBe(undefined);
|
|
983
1998
|
}
|
|
1999
|
+
|
|
2000
|
+
{
|
|
2001
|
+
// Type narrowing - non-object type returns PlainObject | undefined
|
|
2002
|
+
const obj = castType<number>(42);
|
|
2003
|
+
const result: PlainObject | undefined = toNonEmptyJSONObject(obj);
|
|
2004
|
+
expect(result).toBe(undefined);
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
{
|
|
2008
|
+
// Type narrowing - unknown input returns PlainObject | undefined
|
|
2009
|
+
const obj = castType<unknown>({ a: 1 });
|
|
2010
|
+
const result: PlainObject | undefined = toNonEmptyJSONObject(obj);
|
|
2011
|
+
expect(result).toEqual({ a: 1 });
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
{
|
|
2015
|
+
// Type narrowing - any input returns PlainObject | undefined
|
|
2016
|
+
const obj = castType<any>({ a: 1 });
|
|
2017
|
+
const result: PlainObject | undefined = toNonEmptyJSONObject(obj);
|
|
2018
|
+
expect(result).toEqual({ a: 1 });
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
{
|
|
2022
|
+
// Type narrowing - extracts object from union with array
|
|
2023
|
+
const obj = castType<{ a: number } | number[]>({ a: 1 });
|
|
2024
|
+
const result = toNonEmptyJSONObject(obj);
|
|
2025
|
+
if (result) {
|
|
2026
|
+
const check: { a: number } = result;
|
|
2027
|
+
expect(check).toEqual({ a: 1 });
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
{
|
|
2032
|
+
// Type narrowing - array in union returns undefined
|
|
2033
|
+
const obj = castType<{ a: number } | number[]>([1, 2, 3]);
|
|
2034
|
+
const result = toNonEmptyJSONObject(obj);
|
|
2035
|
+
expect(result).toBe(undefined);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
{
|
|
2039
|
+
// Type narrowing - null | object union extracts PlainObject
|
|
2040
|
+
const obj = castType<null | { x: number }>({ x: 42 });
|
|
2041
|
+
const result = toNonEmptyJSONObject(obj);
|
|
2042
|
+
if (result) {
|
|
2043
|
+
const check: { x: number } = result;
|
|
2044
|
+
expect(check).toEqual({ x: 42 });
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
{
|
|
2049
|
+
// Type narrowing - null returns undefined
|
|
2050
|
+
const obj = castType<null | { x: number }>(null);
|
|
2051
|
+
const result = toNonEmptyJSONObject(obj);
|
|
2052
|
+
expect(result).toBe(undefined);
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
{
|
|
2056
|
+
// Type narrowing - preserves complex object type
|
|
2057
|
+
const obj = castType<{ a: string; b: number; c: boolean }>({ a: "hello", b: 42, c: true });
|
|
2058
|
+
const result = toNonEmptyJSONObject(obj);
|
|
2059
|
+
if (result) {
|
|
2060
|
+
const check: { a: string; b: number; c: boolean } = result;
|
|
2061
|
+
expect(check).toEqual({ a: "hello", b: 42, c: true });
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
{
|
|
2066
|
+
// Type narrowing - optional chaining works
|
|
2067
|
+
const obj = castType<unknown>({ a: 1 });
|
|
2068
|
+
const keys = Object.keys(toNonEmptyJSONObject(obj) ?? {});
|
|
2069
|
+
expect(keys).toEqual(["a"]);
|
|
2070
|
+
}
|
|
984
2071
|
});
|
|
985
2072
|
|
|
986
2073
|
it("toPlainObjectOf", () => {
|
package/src/is-to-as.ts
CHANGED
|
@@ -5,7 +5,7 @@ export type _ = undefined;
|
|
|
5
5
|
export type SetDefaultType<T, U> = [T, U][T extends any ? (0 extends 1 & T ? 1 : 0) : 1];
|
|
6
6
|
|
|
7
7
|
/** Returns `true` if `x` is not `undefined`. */
|
|
8
|
-
export const isDefined = <T>(x: T |
|
|
8
|
+
export const isDefined = <T>(x: T | _): x is Exclude<T, _> => x !== _;
|
|
9
9
|
|
|
10
10
|
export const isTrue = (x: unknown): x is true => x === true;
|
|
11
11
|
|