@scalar/validation 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,780 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ any,
5
+ array,
6
+ boolean,
7
+ evaluate,
8
+ lazy,
9
+ literal,
10
+ notDefined,
11
+ nullable,
12
+ number,
13
+ object,
14
+ optional,
15
+ record,
16
+ string,
17
+ union,
18
+ } from '@/schema'
19
+ import { validate } from '@/validate'
20
+
21
+ describe('any', () => {
22
+ const T = any()
23
+ it('Should pass string', () => {
24
+ const value = 'hello'
25
+ const result = validate(T, value)
26
+ expect(result).toBe(true)
27
+ })
28
+ it('Should pass number', () => {
29
+ const value = 1
30
+ const result = validate(T, value)
31
+ expect(result).toBe(true)
32
+ })
33
+ it('Should pass boolean', () => {
34
+ const value = true
35
+ const result = validate(T, value)
36
+ expect(result).toBe(true)
37
+ })
38
+ it('Should pass null', () => {
39
+ const value = null
40
+ const result = validate(T, value)
41
+ expect(result).toBe(true)
42
+ })
43
+ it('Should pass undefined', () => {
44
+ const value = undefined
45
+ const result = validate(T, value)
46
+ expect(result).toBe(true)
47
+ })
48
+ it('Should pass object', () => {
49
+ const value = { a: 1 }
50
+ const result = validate(T, value)
51
+ expect(result).toBe(true)
52
+ })
53
+ it('Should pass array', () => {
54
+ const value = [1, 2]
55
+ const result = validate(T, value)
56
+ expect(result).toBe(true)
57
+ })
58
+ it('Should pass Date', () => {
59
+ const value = new Date()
60
+ const result = validate(T, value)
61
+ expect(result).toBe(true)
62
+ })
63
+ })
64
+
65
+ describe('array', () => {
66
+ it('Should pass number array', () => {
67
+ const T = array(number())
68
+ const value = [1, 2, 3]
69
+ const result = validate(T, value)
70
+ expect(result).toBe(true)
71
+ })
72
+ it('Should fail number array', () => {
73
+ const T = array(number())
74
+ const value = ['a', 'b', 'c']
75
+ const result = validate(T, value)
76
+ expect(result).toBe(false)
77
+ })
78
+ it('Should pass object array', () => {
79
+ const T = array(object({ x: number() }))
80
+ const value = [{ x: 1 }, { x: 1 }, { x: 1 }]
81
+ const result = validate(T, value)
82
+ expect(result).toBe(true)
83
+ })
84
+ it('Should fail object array', () => {
85
+ const T = array(object({ x: number() }))
86
+ const value = [{ x: 1 }, { x: 1 }, 1]
87
+ const result = validate(T, value)
88
+ expect(result).toBe(false)
89
+ })
90
+ it('Should fail Date', () => {
91
+ const value = new Date()
92
+ const result = validate(array(any()), value)
93
+ expect(result).toBe(false)
94
+ })
95
+ })
96
+
97
+ describe('boolean', () => {
98
+ const T = boolean()
99
+ it('Should fail string', () => {
100
+ const value = 'hello'
101
+ const result = validate(T, value)
102
+ expect(result).toBe(false)
103
+ })
104
+ it('Should fail number', () => {
105
+ const value = 1
106
+ const result = validate(T, value)
107
+ expect(result).toBe(false)
108
+ })
109
+ it('Should pass boolean', () => {
110
+ const value = true
111
+ const result = validate(T, value)
112
+ expect(result).toBe(true)
113
+ })
114
+ it('Should fail null', () => {
115
+ const value = null
116
+ const result = validate(T, value)
117
+ expect(result).toBe(false)
118
+ })
119
+ it('Should fail undefined', () => {
120
+ const value = undefined
121
+ const result = validate(T, value)
122
+ expect(result).toBe(false)
123
+ })
124
+ it('Should fail object', () => {
125
+ const value = { a: 1 }
126
+ const result = validate(T, value)
127
+ expect(result).toBe(false)
128
+ })
129
+ it('Should fail array', () => {
130
+ const value = [1, 2]
131
+ const result = validate(T, value)
132
+ expect(result).toBe(false)
133
+ })
134
+ it('Should fail Date', () => {
135
+ const value = new Date()
136
+ const result = validate(T, value)
137
+ expect(result).toBe(false)
138
+ })
139
+ })
140
+
141
+ describe('literal', () => {
142
+ const T = literal('hello')
143
+ it('Should pass literal', () => {
144
+ const value = 'hello'
145
+ const result = validate(T, value)
146
+ expect(result).toBe(true)
147
+ })
148
+ it('Should fail literal', () => {
149
+ const value = 1
150
+ const result = validate(T, value)
151
+ expect(result).toBe(false)
152
+ })
153
+ it('Should fail literal with undefined', () => {
154
+ const value = undefined
155
+ const result = validate(T, value)
156
+ expect(result).toBe(false)
157
+ })
158
+ it('Should fail literal with null', () => {
159
+ const value = null
160
+ const result = validate(T, value)
161
+ expect(result).toBe(false)
162
+ })
163
+ })
164
+
165
+ describe('null', () => {
166
+ const T = nullable()
167
+ it('Should fail string', () => {
168
+ const value = 'hello'
169
+ const result = validate(T, value)
170
+ expect(result).toBe(false)
171
+ })
172
+ it('Should fail number', () => {
173
+ const value = 1
174
+ const result = validate(T, value)
175
+ expect(result).toBe(false)
176
+ })
177
+ it('Should fail boolean', () => {
178
+ const value = true
179
+ const result = validate(T, value)
180
+ expect(result).toBe(false)
181
+ })
182
+ it('Should pass null', () => {
183
+ const value = null
184
+ const result = validate(T, value)
185
+ expect(result).toBe(true)
186
+ })
187
+ it('Should fail undefined', () => {
188
+ const value = undefined
189
+ const result = validate(T, value)
190
+ expect(result).toBe(false)
191
+ })
192
+ it('Should fail object', () => {
193
+ const value = { a: 1 }
194
+ const result = validate(T, value)
195
+ expect(result).toBe(false)
196
+ })
197
+ it('Should fail array', () => {
198
+ const value = [1, 2]
199
+ const result = validate(T, value)
200
+ expect(result).toBe(false)
201
+ })
202
+ it('Should fail Date', () => {
203
+ const value = new Date()
204
+ const result = validate(T, value)
205
+ expect(result).toBe(false)
206
+ })
207
+ })
208
+
209
+ describe('number', () => {
210
+ const T = number()
211
+ it('Should not validate NaN', () => {
212
+ const result = validate(T, Number.NaN)
213
+ expect(result).toBe(false)
214
+ })
215
+ it('Should not validate +Infinity', () => {
216
+ const result = validate(T, Number.POSITIVE_INFINITY)
217
+ expect(result).toBe(false)
218
+ })
219
+ it('Should not validate -Infinity', () => {
220
+ const result = validate(T, Number.NEGATIVE_INFINITY)
221
+ expect(result).toBe(false)
222
+ })
223
+ it('Should fail string', () => {
224
+ const value = 'hello'
225
+ const result = validate(T, value)
226
+ expect(result).toBe(false)
227
+ })
228
+ it('Should pass number', () => {
229
+ const value = 1
230
+ const result = validate(T, value)
231
+ expect(result).toBe(true)
232
+ })
233
+ it('Should fail boolean', () => {
234
+ const value = true
235
+ const result = validate(T, value)
236
+ expect(result).toBe(false)
237
+ })
238
+ it('Should fail null', () => {
239
+ const value = null
240
+ const result = validate(T, value)
241
+ expect(result).toBe(false)
242
+ })
243
+ it('Should fail undefined', () => {
244
+ const value = undefined
245
+ const result = validate(T, value)
246
+ expect(result).toBe(false)
247
+ })
248
+ it('Should fail object', () => {
249
+ const value = { a: 1 }
250
+ const result = validate(T, value)
251
+ expect(result).toBe(false)
252
+ })
253
+ it('Should fail array', () => {
254
+ const value = [1, 2]
255
+ const result = validate(T, value)
256
+ expect(result).toBe(false)
257
+ })
258
+ it('Should fail Date', () => {
259
+ const value = new Date()
260
+ const result = validate(T, value)
261
+ expect(result).toBe(false)
262
+ })
263
+
264
+ it('Should fail NaN', () => {
265
+ const result = validate(T, Number.NaN)
266
+ expect(result).toBe(false)
267
+ })
268
+ })
269
+
270
+ describe('object', () => {
271
+ const T = object({
272
+ x: number(),
273
+ y: number(),
274
+ z: number(),
275
+ a: string(),
276
+ b: string(),
277
+ c: string(),
278
+ })
279
+ it('Should pass object', () => {
280
+ const value = {
281
+ x: 1,
282
+ y: 1,
283
+ z: 1,
284
+ a: '1',
285
+ b: '1',
286
+ c: '1',
287
+ }
288
+ const result = validate(T, value)
289
+ expect(result).toBe(true)
290
+ })
291
+
292
+ it('Should fail object with invalid property', () => {
293
+ const value = {
294
+ x: true,
295
+ y: 1,
296
+ z: 1,
297
+ a: '1',
298
+ b: '1',
299
+ c: '1',
300
+ }
301
+ const result = validate(T, value)
302
+ expect(result).toBe(false)
303
+ })
304
+ it('Should fail object with missing property', () => {
305
+ const value = {
306
+ y: 1,
307
+ z: 1,
308
+ a: '1',
309
+ b: '1',
310
+ c: '1',
311
+ }
312
+ const result = validate(T, value)
313
+ expect(result).toBe(false)
314
+ })
315
+ it('Should pass object with optional properties', () => {
316
+ const T = object({
317
+ x: optional(number()),
318
+ y: optional(number()),
319
+ z: optional(number()),
320
+ a: string(),
321
+ b: string(),
322
+ c: string(),
323
+ })
324
+ const value = {
325
+ a: '1',
326
+ b: '1',
327
+ c: '1',
328
+ }
329
+ const result = validate(T, value)
330
+ expect(result).toBe(true)
331
+ })
332
+ it('Should fail object with null', () => {
333
+ const value = null
334
+ const result = validate(T, value)
335
+ expect(result).toBe(false)
336
+ })
337
+ it('Should fail object with undefined', () => {
338
+ const value = undefined
339
+ const result = validate(T, value)
340
+ expect(result).toBe(false)
341
+ })
342
+
343
+ it('Should check for property key if property type is undefined', () => {
344
+ const T = object({ x: notDefined() })
345
+ expect(validate(T, { x: undefined })).toBe(true)
346
+ expect(validate(T, {})).toBe(true)
347
+ })
348
+ it('Should check for property key if property type extends undefined', () => {
349
+ const T = object({ x: union([number(), notDefined()]) })
350
+ expect(validate(T, { x: 1 })).toBe(true)
351
+ expect(validate(T, { x: undefined })).toBe(true)
352
+ expect(validate(T, {})).toBe(true)
353
+ })
354
+ it('Should not check for property key if property type is undefined and optional', () => {
355
+ const T = object({ x: optional(notDefined()) })
356
+ expect(validate(T, { x: undefined })).toBe(true)
357
+ expect(validate(T, {})).toBe(true)
358
+ })
359
+ it('Should not check for property key if property type extends undefined and optional', () => {
360
+ const T = object({ x: optional(union([number(), notDefined()])) })
361
+ expect(validate(T, { x: 1 })).toBe(true)
362
+ expect(validate(T, { x: undefined })).toBe(true)
363
+ expect(validate(T, {})).toBe(true)
364
+ })
365
+ it('Should check undefined for optional property of number', () => {
366
+ const T = object({ x: optional(number()) })
367
+ expect(validate(T, { x: 1 })).toBe(true)
368
+ expect(validate(T, { x: undefined })).toBe(true) // allowed by default
369
+ expect(validate(T, {})).toBe(true)
370
+ })
371
+ it('Should check undefined for optional property of undefined', () => {
372
+ const T = object({ x: optional(notDefined()) })
373
+ expect(validate(T, { x: 1 })).toBe(false)
374
+ expect(validate(T, {})).toBe(true)
375
+ expect(validate(T, { x: undefined })).toBe(true)
376
+ })
377
+ })
378
+
379
+ describe('record', () => {
380
+ it('Should pass record', () => {
381
+ const T = record(
382
+ string(),
383
+ object({
384
+ x: number(),
385
+ y: number(),
386
+ z: number(),
387
+ }),
388
+ )
389
+ const value = {
390
+ position: {
391
+ x: 1,
392
+ y: 2,
393
+ z: 3,
394
+ },
395
+ }
396
+ const result = validate(T, value)
397
+ expect(result).toBe(true)
398
+ })
399
+ it('Should fail record with Date', () => {
400
+ const T = record(string(), string())
401
+ const result = validate(T, new Date())
402
+ expect(result).toBe(false)
403
+ })
404
+ it('Should fail record with Uint8Array', () => {
405
+ const T = record(string(), string())
406
+ const result = validate(T, new Uint8Array())
407
+ expect(result).toBe(false)
408
+ })
409
+ it('Should fail record with missing property', () => {
410
+ const T = record(
411
+ string(),
412
+ object({
413
+ x: number(),
414
+ y: number(),
415
+ z: number(),
416
+ }),
417
+ )
418
+ const value = {
419
+ position: {
420
+ x: 1,
421
+ y: 2,
422
+ },
423
+ }
424
+ const result = validate(T, value)
425
+ expect(result).toBe(false)
426
+ })
427
+ it('Should fail record with invalid property', () => {
428
+ const T = record(
429
+ string(),
430
+ object({
431
+ x: number(),
432
+ y: number(),
433
+ z: number(),
434
+ }),
435
+ )
436
+ const value = {
437
+ position: {
438
+ x: 1,
439
+ y: 2,
440
+ z: '3',
441
+ },
442
+ }
443
+ const result = validate(T, value)
444
+ expect(result).toBe(false)
445
+ })
446
+ it('Should pass record with optional property', () => {
447
+ const T = record(
448
+ string(),
449
+ object({
450
+ x: number(),
451
+ y: number(),
452
+ z: optional(number()),
453
+ }),
454
+ )
455
+ const value = {
456
+ position: {
457
+ x: 1,
458
+ y: 2,
459
+ },
460
+ }
461
+ const result = validate(T, value)
462
+ expect(result).toBe(true)
463
+ })
464
+ it('Should pass record with optional property', () => {
465
+ const T = record(
466
+ string(),
467
+ object({
468
+ x: number(),
469
+ y: number(),
470
+ z: optional(number()),
471
+ }),
472
+ )
473
+ const value = {
474
+ position: {
475
+ x: 1,
476
+ y: 2,
477
+ },
478
+ }
479
+ const result = validate(T, value)
480
+ expect(result).toBe(true)
481
+ })
482
+
483
+ it('Should validate for any keys', () => {
484
+ const T = record(any(), nullable())
485
+ const R = validate(T, {
486
+ a: null,
487
+ b: null,
488
+ 0: null,
489
+ 1: null,
490
+ })
491
+ expect(R).toBe(true)
492
+ })
493
+
494
+ // TODO: implement this
495
+ it.skip('Should pass record with number key', () => {
496
+ // @ts-expect-error - number key is not supported yet
497
+ const T = record(number(), string())
498
+ const value = {
499
+ 0: 'a',
500
+ 1: 'a',
501
+ 2: 'a',
502
+ }
503
+ const result = validate(T, value)
504
+ expect(result).toBe(true)
505
+ })
506
+
507
+ it.skip('Should not pass record with invalid number key', () => {
508
+ // @ts-expect-error - number key is not supported yet
509
+ const T = record(number(), string())
510
+ const value = {
511
+ a: 'a',
512
+ 1: 'a',
513
+ 2: 'a',
514
+ }
515
+ const result = validate(T, value)
516
+ expect(result).toBe(false)
517
+ })
518
+
519
+ it.skip('Should validate for number keys', () => {
520
+ // @ts-expect-error - number key is not supported yet
521
+ const T = record(number(), nullable())
522
+ const R1 = validate(T, {
523
+ a: null,
524
+ b: null,
525
+ 0: null,
526
+ 1: null,
527
+ })
528
+ const R2 = validate(T, {
529
+ 0: null,
530
+ 1: null,
531
+ })
532
+ expect(R1).toBe(false)
533
+ expect(R2).toBe(true)
534
+ })
535
+ })
536
+
537
+ describe('string', () => {
538
+ const T = string()
539
+ it('Should pass string', () => {
540
+ const value = 'hello'
541
+ const result = validate(T, value)
542
+ expect(result).toBe(true)
543
+ })
544
+ it('Should fail number', () => {
545
+ const value = 1
546
+ const result = validate(T, value)
547
+ expect(result).toBe(false)
548
+ })
549
+ it('Should fail boolean', () => {
550
+ const value = true
551
+ const result = validate(T, value)
552
+ expect(result).toBe(false)
553
+ })
554
+ it('Should fail null', () => {
555
+ const value = null
556
+ const result = validate(T, value)
557
+ expect(result).toBe(false)
558
+ })
559
+ it('Should fail undefined', () => {
560
+ const value = undefined
561
+ const result = validate(T, value)
562
+ expect(result).toBe(false)
563
+ })
564
+ it('Should fail object', () => {
565
+ const value = { a: 1 }
566
+ const result = validate(T, value)
567
+ expect(result).toBe(false)
568
+ })
569
+ it('Should fail array', () => {
570
+ const value = [1, 2]
571
+ const result = validate(T, value)
572
+ expect(result).toBe(false)
573
+ })
574
+ it('Should fail Date', () => {
575
+ const value = new Date()
576
+ const result = validate(T, value)
577
+ expect(result).toBe(false)
578
+ })
579
+ })
580
+
581
+ describe('union', () => {
582
+ const A = object({
583
+ type: literal('A'),
584
+ x: number(),
585
+ y: number(),
586
+ })
587
+
588
+ const B = object({
589
+ type: literal('B'),
590
+ x: boolean(),
591
+ y: boolean(),
592
+ })
593
+
594
+ const T = union([A, B])
595
+
596
+ it('Should pass union A', () => {
597
+ const value = { type: 'A', x: 1, y: 1 }
598
+ const result = validate(T, value)
599
+ expect(result).toBe(true)
600
+ })
601
+
602
+ it('Should pass union B', () => {
603
+ const value = { type: 'B', x: true, y: false }
604
+ const result = validate(T, value)
605
+ expect(result).toBe(true)
606
+ })
607
+
608
+ it('Should fail union A', () => {
609
+ const value = { type: 'A', x: true, y: false }
610
+ const result = validate(T, value)
611
+ expect(result).toBe(false)
612
+ })
613
+
614
+ it('Should fail union B', () => {
615
+ const value = { type: 'B', x: 1, y: 1 }
616
+ const result = validate(T, value)
617
+ expect(result).toBe(false)
618
+ })
619
+
620
+ it('Should pass union A with optional properties', () => {
621
+ const A = object({
622
+ type: literal('A'),
623
+ x: optional(number()),
624
+ y: optional(number()),
625
+ })
626
+ const B = object({
627
+ type: literal('B'),
628
+ x: boolean(),
629
+ y: boolean(),
630
+ })
631
+ const T = union([A, B])
632
+ const value = { type: 'A' }
633
+ const result = validate(T, value)
634
+ expect(result).toBe(true)
635
+ })
636
+
637
+ it('Should fail union A with invalid optional properties', () => {
638
+ const A = object({
639
+ type: literal('A'),
640
+ x: optional(number()),
641
+ y: optional(number()),
642
+ })
643
+ const B = object({
644
+ type: literal('B'),
645
+ x: boolean(),
646
+ y: boolean(),
647
+ })
648
+ const T = union([A, B])
649
+ const value = { type: 'A', x: true, y: false }
650
+ const result = validate(T, value)
651
+ expect(result).toBe(false)
652
+ })
653
+ })
654
+
655
+ describe('notDefined', () => {
656
+ const T = notDefined()
657
+ it('Should fail string', () => {
658
+ const value = 'hello'
659
+ const result = validate(T, value)
660
+ expect(result).toBe(false)
661
+ })
662
+ it('Should fail number', () => {
663
+ const value = 1
664
+ const result = validate(T, value)
665
+ expect(result).toBe(false)
666
+ })
667
+ it('Should fail boolean', () => {
668
+ const value = true
669
+ const result = validate(T, value)
670
+ expect(result).toBe(false)
671
+ })
672
+ it('Should fail null', () => {
673
+ const value = null
674
+ const result = validate(T, value)
675
+ expect(result).toBe(false)
676
+ })
677
+ it('Should pass undefined', () => {
678
+ const value = undefined
679
+ const result = validate(T, value)
680
+ expect(result).toBe(true)
681
+ })
682
+ it('Should fail object', () => {
683
+ const value = { a: 1 }
684
+ const result = validate(T, value)
685
+ expect(result).toBe(false)
686
+ })
687
+ it('Should fail array', () => {
688
+ const value = [1, 2]
689
+ const result = validate(T, value)
690
+ expect(result).toBe(false)
691
+ })
692
+ it('Should fail Date', () => {
693
+ const value = new Date()
694
+ const result = validate(T, value)
695
+ expect(result).toBe(false)
696
+ })
697
+ })
698
+
699
+ describe('evaluate', () => {
700
+ const T = evaluate((value) => value, number())
701
+ it('Should pass number', () => {
702
+ const value = 1
703
+ const result = validate(T, value)
704
+ expect(result).toBe(true)
705
+ })
706
+ it('Should fail string', () => {
707
+ const value = 'hello'
708
+ const result = validate(T, value)
709
+ expect(result).toBe(false)
710
+ })
711
+ it('Should fail boolean', () => {
712
+ const value = true
713
+ const result = validate(T, value)
714
+ expect(result).toBe(false)
715
+ })
716
+ it('Should fail null', () => {
717
+ const value = null
718
+ const result = validate(T, value)
719
+ expect(result).toBe(false)
720
+ })
721
+ it('Should fail undefined', () => {
722
+ const value = undefined
723
+ const result = validate(T, value)
724
+ expect(result).toBe(false)
725
+ })
726
+ it('Should fail object', () => {
727
+ const value = { a: 1 }
728
+ const result = validate(T, value)
729
+ expect(result).toBe(false)
730
+ })
731
+ it('Should fail array', () => {
732
+ const value = [1, 2]
733
+ const result = validate(T, value)
734
+ expect(result).toBe(false)
735
+ })
736
+ it('Should fail Date', () => {
737
+ const value = new Date()
738
+ const result = validate(T, value)
739
+ expect(result).toBe(false)
740
+ })
741
+ })
742
+
743
+ describe('lazy', () => {
744
+ const T = lazy(() => object({ x: number() }))
745
+ it('Should pass object', () => {
746
+ const value = { x: 1 }
747
+ const result = validate(T, value)
748
+ expect(result).toBe(true)
749
+ })
750
+ it('Should fail string', () => {
751
+ const value = 'hello'
752
+ const result = validate(T, value)
753
+ expect(result).toBe(false)
754
+ })
755
+ it('Should fail boolean', () => {
756
+ const value = true
757
+ const result = validate(T, value)
758
+ expect(result).toBe(false)
759
+ })
760
+ it('Should fail null', () => {
761
+ const value = null
762
+ const result = validate(T, value)
763
+ expect(result).toBe(false)
764
+ })
765
+ it('Should fail undefined', () => {
766
+ const value = undefined
767
+ const result = validate(T, value)
768
+ expect(result).toBe(false)
769
+ })
770
+ it('Should fail array', () => {
771
+ const value = [1, 2]
772
+ const result = validate(T, value)
773
+ expect(result).toBe(false)
774
+ })
775
+ it('Should fail Date', () => {
776
+ const value = new Date()
777
+ const result = validate(T, value)
778
+ expect(result).toBe(false)
779
+ })
780
+ })