1- import { describe , it , expect , expectTypeOf } from "vitest" ;
1+ import { describe , it , expect , expectTypeOf , assert } from "vitest" ;
22import * as v from "../src" ;
33
44describe ( "union()" , ( ) => {
@@ -8,12 +8,14 @@ describe("union()", () => {
88 expect ( t . parse ( 1 ) ) . to . equal ( 1 ) ;
99 expect ( ( ) => t . parse ( { } ) ) . to . throw ( v . ValitaError ) ;
1010 } ) ;
11+
1112 it ( "ignores never()" , ( ) => {
1213 const t = v . union ( v . string ( ) , v . never ( ) ) ;
1314 expect ( t . parse ( "test" ) ) . to . equal ( "test" ) ;
1415 expect ( ( ) => t . parse ( 1 ) ) . to . throw ( v . ValitaError ) ;
1516 expectTypeOf < v . Infer < typeof t > > ( ) . toEqualTypeOf < string > ( ) ;
1617 } ) ;
18+
1719 it ( "picks the first successful parse" , ( ) => {
1820 const t = v . union (
1921 v
@@ -24,6 +26,7 @@ describe("union()", () => {
2426 ) ;
2527 expect ( t . parse ( "test" ) ) . to . equal ( 2 ) ;
2628 } ) ;
29+
2730 it ( "respects the order of overlapping parsers" , ( ) => {
2831 const a = v . literal ( 1 ) . map ( ( ) => "literal" ) ;
2932 const b = v . number ( ) . map ( ( ) => "number" ) ;
@@ -35,18 +38,21 @@ describe("union()", () => {
3538 expect ( v . union ( c , b , a ) . parse ( 1 ) ) . to . equal ( "unknown" ) ;
3639 expect ( v . union ( c , a , b ) . parse ( 1 ) ) . to . equal ( "unknown" ) ;
3740 } ) ;
41+
3842 it ( "deduplicates strictly equal parsers" , ( ) => {
3943 const a = v . unknown ( ) . assert ( ( ) => false , "test" ) ;
4044 expect ( ( ) => v . union ( a , a ) . parse ( 1 ) )
4145 . to . throw ( v . ValitaError )
4246 . with . property ( "issues" )
4347 . with . lengthOf ( 1 ) ;
4448 } ) ;
49+
4550 it ( "keeps the matching order when deduplicating" , ( ) => {
4651 const a = v . unknown ( ) . map ( ( ) => "a" ) ;
4752 const b = v . unknown ( ) . map ( ( ) => "b" ) ;
4853 expect ( v . union ( a , b , a ) . parse ( 1 ) ) . to . equal ( "a" ) ;
4954 } ) ;
55+
5056 it ( "accepts more than two subvalidators" , ( ) => {
5157 const t = v . union (
5258 v . string ( ) ,
@@ -62,6 +68,7 @@ describe("union()", () => {
6268 expect ( t . parse ( true ) ) . to . equal ( true ) ;
6369 expect ( ( ) => t . parse ( { } ) ) . to . throw ( v . ValitaError ) ;
6470 } ) ;
71+
6572 it ( "accepts optional input if it maps to non-optional output" , ( ) => {
6673 const t = v . object ( {
6774 a : v . union (
@@ -74,6 +81,7 @@ describe("union()", () => {
7481 } ) ;
7582 expect ( t . parse ( { } ) ) . toEqual ( { a : 1 } ) ;
7683 } ) ;
84+
7785 it ( "reports the expected type even for literals when the base type doesn't match" , ( ) => {
7886 const t = v . union ( v . literal ( 1 ) , v . literal ( "test" ) ) ;
7987 expect ( ( ) => t . parse ( true ) )
@@ -84,6 +92,7 @@ describe("union()", () => {
8492 expected : [ 1 , "test" ] ,
8593 } ) ;
8694 } ) ;
95+
8796 it ( "reports the expected literals when the base type matches" , ( ) => {
8897 const t = v . union ( v . literal ( 1 ) , v . literal ( "test" ) ) ;
8998 expect ( ( ) => t . parse ( 2 ) )
@@ -94,6 +103,7 @@ describe("union()", () => {
94103 expected : [ 1 , "test" ] ,
95104 } ) ;
96105 } ) ;
106+
97107 it ( "reports the errors from a branch that doesn't overlap with any other branch" , ( ) => {
98108 const t = v . union ( v . literal ( 1 ) , v . number ( ) , v . object ( { a : v . number ( ) } ) ) ;
99109 expect ( ( ) => t . parse ( { a : "test" } ) )
@@ -105,6 +115,7 @@ describe("union()", () => {
105115 expected : [ "number" ] ,
106116 } ) ;
107117 } ) ;
118+
108119 it ( "reports expected types in the order they were first listed" , ( ) => {
109120 const t1 = v . union ( v . literal ( 2 ) , v . string ( ) , v . literal ( 2 ) ) ;
110121 expect ( ( ) => t1 . parse ( true ) )
@@ -126,6 +137,7 @@ describe("union()", () => {
126137 expected : [ "string" , "number" ] ,
127138 } ) ;
128139 } ) ;
140+
129141 it ( "reports expected literals in the order they were first listed" , ( ) => {
130142 const t1 = v . union ( v . literal ( 2 ) , v . literal ( 1 ) , v . literal ( 2 ) ) ;
131143 expect ( ( ) => t1 . parse ( 3 ) )
@@ -147,6 +159,7 @@ describe("union()", () => {
147159 expected : [ 1 , 2 ] ,
148160 } ) ;
149161 } ) ;
162+
150163 it ( "matches unknowns if nothing else matches" , ( ) => {
151164 const t = v . union (
152165 v . literal ( 1 ) ,
@@ -161,6 +174,7 @@ describe("union()", () => {
161174 error : "test" ,
162175 } ) ;
163176 } ) ;
177+
164178 it ( "considers never() to not overlap with anything" , ( ) => {
165179 const t = v . union (
166180 v . never ( ) ,
@@ -174,6 +188,7 @@ describe("union()", () => {
174188 error : "unknown" ,
175189 } ) ;
176190 } ) ;
191+
177192 it ( "considers unknown() to overlap with everything except never()" , ( ) => {
178193 const t = v . union (
179194 v . literal ( 1 ) ,
@@ -187,6 +202,7 @@ describe("union()", () => {
187202 code : "invalid_union" ,
188203 } ) ;
189204 } ) ;
205+
190206 it ( "considers unknown() to overlap with objects" , ( ) => {
191207 const t = v . union (
192208 v . unknown ( ) ,
@@ -195,6 +211,7 @@ describe("union()", () => {
195211 ) ;
196212 expect ( t . parse ( { type : "c" } ) ) . to . deep . equal ( { type : "c" } ) ;
197213 } ) ;
214+
198215 it ( "considers array() and tuple() to overlap" , ( ) => {
199216 const t = v . union ( v . array ( v . number ( ) ) , v . tuple ( [ v . string ( ) ] ) ) ;
200217 expect ( ( ) => t . parse ( 2 ) )
@@ -205,13 +222,15 @@ describe("union()", () => {
205222 expected : [ "array" ] ,
206223 } ) ;
207224 } ) ;
225+
208226 it ( "keeps transformed values" , ( ) => {
209227 const t = v . union (
210228 v . literal ( "test1" ) . map ( ( ) => 1 ) ,
211229 v . literal ( "test2" ) . map ( ( ) => 2 ) ,
212230 ) ;
213231 expect ( t . parse ( "test1" ) ) . to . deep . equal ( 1 ) ;
214232 } ) ;
233+
215234 describe ( "of objects" , ( ) => {
216235 it ( "discriminates based on base types" , ( ) => {
217236 const t = v . union (
@@ -227,6 +246,7 @@ describe("union()", () => {
227246 expected : [ "number" , "string" ] ,
228247 } ) ;
229248 } ) ;
249+
230250 it ( "discriminates based on literal values" , ( ) => {
231251 const t = v . union (
232252 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -241,6 +261,7 @@ describe("union()", () => {
241261 expected : [ 1 , 2 ] ,
242262 } ) ;
243263 } ) ;
264+
244265 it ( "discriminates based on mixture of base types and literal values" , ( ) => {
245266 const t = v . union (
246267 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -255,6 +276,7 @@ describe("union()", () => {
255276 expected : [ "number" , "string" ] ,
256277 } ) ;
257278 } ) ;
279+
258280 it ( "considers unknown() to overlap with everything except never()" , ( ) => {
259281 const t = v . union (
260282 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -265,6 +287,7 @@ describe("union()", () => {
265287 . with . nested . property ( "issues[0]" )
266288 . that . deep . includes ( { code : "invalid_union" } ) ;
267289 } ) ;
290+
268291 it ( "considers literals to overlap with their base types" , ( ) => {
269292 const t = v . union (
270293 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -275,6 +298,7 @@ describe("union()", () => {
275298 . with . nested . property ( "issues[0]" )
276299 . that . deep . includes ( { code : "invalid_union" } ) ;
277300 } ) ;
301+
278302 it ( "considers optional() its own type" , ( ) => {
279303 const t = v . union (
280304 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -288,10 +312,12 @@ describe("union()", () => {
288312 expected : [ "number" , "undefined" ] ,
289313 } ) ;
290314 } ) ;
315+
291316 it ( "matches missing values to optional()" , ( ) => {
292317 const t = v . union ( v . object ( { a : v . unknown ( ) . optional ( ) } ) ) ;
293318 expect ( t . parse ( { } ) ) . to . deep . equal ( { } ) ;
294319 } ) ;
320+
295321 it ( "considers equal literals to overlap" , ( ) => {
296322 const t = v . union (
297323 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -302,6 +328,7 @@ describe("union()", () => {
302328 . with . nested . property ( "issues[0]" )
303329 . that . deep . includes ( { code : "invalid_union" } ) ;
304330 } ) ;
331+
305332 it ( "allows mixing literals and non-literals as long as they don't overlap" , ( ) => {
306333 const t = v . union (
307334 v . object ( { type : v . literal ( 1 ) } ) ,
@@ -312,6 +339,7 @@ describe("union()", () => {
312339 expect ( t . parse ( { type : 2 } ) ) . toEqual ( { type : 2 } ) ;
313340 expect ( t . parse ( { type : "test" } ) ) . toEqual ( { type : "test" } ) ;
314341 } ) ;
342+
315343 it ( "folds multiple overlapping types together in same branch" , ( ) => {
316344 const t = v . union (
317345 v . object ( {
@@ -331,6 +359,7 @@ describe("union()", () => {
331359 expected : [ "test" ] ,
332360 } ) ;
333361 } ) ;
362+
334363 it ( "considers two optionals to overlap" , ( ) => {
335364 const t = v . union (
336365 v . object ( { type : v . literal ( 1 ) . optional ( ) } ) ,
@@ -340,6 +369,7 @@ describe("union()", () => {
340369 . to . throw ( v . ValitaError )
341370 . with . nested . property ( "issues[0].code" , "invalid_union" ) ;
342371 } ) ;
372+
343373 it ( "considers two optionals and undefineds to overlap" , ( ) => {
344374 const t = v . union (
345375 v . object ( { type : v . undefined ( ) } ) ,
@@ -349,6 +379,7 @@ describe("union()", () => {
349379 . to . throw ( v . ValitaError )
350380 . with . nested . property ( "issues[0].code" , "invalid_union" ) ;
351381 } ) ;
382+
352383 it ( "considers two unions with partially same types to overlap" , ( ) => {
353384 const t = v . union (
354385 v . object ( { type : v . union ( v . literal ( 1 ) , v . literal ( 2 ) ) } ) ,
@@ -358,12 +389,35 @@ describe("union()", () => {
358389 . to . throw ( v . ValitaError )
359390 . with . nested . property ( "issues[0].code" , "invalid_union" ) ;
360391 } ) ;
392+
361393 it ( "keeps transformed values" , ( ) => {
362394 const t = v . union (
363395 v . object ( { type : v . literal ( "test1" ) . map ( ( ) => 1 ) } ) ,
364396 v . object ( { type : v . literal ( "test2" ) . map ( ( ) => 2 ) } ) ,
365397 ) ;
366398 expect ( t . parse ( { type : "test1" } ) ) . to . deep . equal ( { type : 1 } ) ;
367399 } ) ;
400+
401+ it ( "includes an array of sub-issues in 'invalid_union' issues" , ( ) => {
402+ const t = v . union (
403+ v . object ( { type : v . literal ( 1 ) . optional ( ) } ) ,
404+ v . object ( { type : v . literal ( 2 ) . optional ( ) } ) ,
405+ ) ;
406+ const result = t . try ( { type : 3 } ) ;
407+ assert ( ! result . ok ) ;
408+ assert ( result . issues [ 0 ] . code === "invalid_union" ) ;
409+ expect ( result . issues [ 0 ] . issues ) . toEqual ( [
410+ {
411+ code : "invalid_literal" ,
412+ expected : [ 1 ] ,
413+ path : [ "type" ] ,
414+ } ,
415+ {
416+ code : "invalid_literal" ,
417+ expected : [ 2 ] ,
418+ path : [ "type" ] ,
419+ } ,
420+ ] ) ;
421+ } ) ;
368422 } ) ;
369423} ) ;
0 commit comments