11import { describe , expect , expectTypeOf , it } from "vitest" ;
2+ import { z } from "zod" ;
23import { GroqBuilderBase , InferResultItem , InferResultType } from "../../index" ;
4+ import { executeBuilder } from "../../tests/mocks/executeQuery" ;
5+ import { mock } from "../../tests/mocks/nextjs-sanity-fe-mocks" ;
36import { q } from "../../tests/schemas/nextjs-sanity-fe" ;
47import { ExtractConditionalProjectionTypes } from "./conditional-types" ;
58import { Empty , Simplify } from "../../types/utils" ;
@@ -37,7 +40,7 @@ describe("conditional", () => {
3740 } ) ;
3841 } ) ;
3942
40- const qAll = qVariants . project ( ( qV ) => ( {
43+ const qConditional = qVariants . project ( ( qV ) => ( {
4144 name : true ,
4245 ...qV . conditional ( {
4346 "price == msrp" : {
@@ -50,19 +53,17 @@ describe("conditional", () => {
5053 } ,
5154 } ) ,
5255 } ) ) ;
53-
5456 it ( "should be able to extract the return type" , ( ) => {
55- expectTypeOf < InferResultType < typeof qAll > > ( ) . toEqualTypeOf <
57+ expectTypeOf < InferResultType < typeof qConditional > > ( ) . toEqualTypeOf <
5658 Array <
5759 | { name : string }
5860 | { name : string ; onSale : false }
5961 | { name : string ; onSale : true ; price : number ; msrp : number }
6062 >
6163 > ( ) ;
6264 } ) ;
63-
6465 it ( "the query should look correct" , ( ) => {
65- expect ( qAll . query ) . toMatchInlineSnapshot (
66+ expect ( qConditional . query ) . toMatchInlineSnapshot (
6667 `
6768 "*[_type == "variant"] {
6869 name,
@@ -238,47 +239,226 @@ describe("conditional", () => {
238239 } ) ;
239240 } ) ;
240241
242+ const data = mock . generateSeedData ( {
243+ variants : [
244+ //
245+ mock . variant ( {
246+ name : "Variant 1" ,
247+ price : 10 ,
248+ msrp : 10 ,
249+ } ) ,
250+ mock . variant ( { name : "Variant 2" , price : 8 , msrp : 9 } ) ,
251+ ] ,
252+ } ) ;
241253 describe ( "using query syntax" , ( ) => {
242- const qAll = qVariants . project ( ( q ) => ( {
254+ const qConditional = qVariants . project ( ( q ) => ( {
243255 name : true ,
244256 ...q . conditional ( {
245257 "price == msrp" : q . project ( {
246258 onSale : q . value ( false ) ,
259+ msrp : true ,
247260 } ) ,
248261 "price < msrp" : ( q ) =>
249262 q . project ( {
250263 onSale : q . value ( true ) ,
251264 price : true ,
252- msrp : true ,
253265 } ) ,
254266 } ) ,
255267 } ) ) ;
256268 it ( "should have the correct expected type" , ( ) => {
257- type Result = InferResultType < typeof qAll > ;
269+ type Result = InferResultType < typeof qConditional > ;
258270 type Expected = Array <
259271 | { name : string }
260- | { name : string ; onSale : false }
261- | { name : string ; onSale : true ; price : number ; msrp : number }
272+ | { name : string ; onSale : false ; msrp : number }
273+ | { name : string ; onSale : true ; price : number }
262274 > ;
263275 expectTypeOf < Result > ( ) . toEqualTypeOf < Expected > ( ) ;
264276 } ) ;
265277 it ( "should generate the correct query" , ( ) => {
266- expect ( qAll . query ) . toMatchInlineSnapshot ( `
278+ expect ( qConditional . query ) . toMatchInlineSnapshot ( `
267279 "*[_type == "variant"] {
268280 name,
269281 price == msrp => {
270- "onSale": false
282+ "onSale": false,
283+ msrp
271284 },
272285 price < msrp => {
273286 "onSale": true,
274- price,
287+ price
288+ }
289+ }"
290+ ` ) ;
291+ } ) ;
292+ it ( "should execute correctly" , async ( ) => {
293+ const results = await executeBuilder ( qConditional , data ) ;
294+ expect ( results ) . toMatchInlineSnapshot ( `
295+ [
296+ {
297+ "msrp": 10,
298+ "name": "Variant 1",
299+ "onSale": false,
300+ },
301+ {
302+ "name": "Variant 2",
303+ "onSale": true,
304+ "price": 8,
305+ },
306+ ]
307+ ` ) ;
308+ } ) ;
309+ } ) ;
310+
311+ describe ( "with validation" , ( ) => {
312+ const qConditional = qVariants . project ( ( q ) => ( {
313+ name : z . string ( ) ,
314+ ...q . conditional ( {
315+ "price == msrp" : {
316+ onSale : q . value ( false , z . literal ( false ) ) ,
317+ msrp : z . number ( ) ,
318+ } ,
319+ "price < msrp" : {
320+ onSale : q . value ( true , z . literal ( true ) ) ,
321+ price : z . number ( ) ,
322+ } ,
323+ } ) ,
324+ } ) ) ;
325+ it ( "should have the correct expected type" , ( ) => {
326+ type Result = InferResultType < typeof qConditional > ;
327+ type Expected = Array <
328+ | { name : string }
329+ | { name : string ; onSale : false ; msrp : number }
330+ | { name : string ; onSale : true ; price : number }
331+ > ;
332+ expectTypeOf < Result > ( ) . toEqualTypeOf < Expected > ( ) ;
333+ } ) ;
334+ it ( "should generate the correct query" , ( ) => {
335+ expect ( qConditional . query ) . toMatchInlineSnapshot ( `
336+ "*[_type == "variant"] {
337+ name,
338+ price == msrp => {
339+ "onSale": false,
275340 msrp
341+ },
342+ price < msrp => {
343+ "onSale": true,
344+ price
276345 }
277346 }"
278347 ` ) ;
279348 } ) ;
280- it ( "should execute correctly" , ( ) => {
281- // (we actually already test this exact query in a previous test)
349+ it ( "should execute correctly" , async ( ) => {
350+ const results = await executeBuilder ( qConditional , data ) ;
351+ expect ( results ) . toMatchInlineSnapshot ( `
352+ [
353+ {
354+ "msrp": 10,
355+ "name": "Variant 1",
356+ "onSale": false,
357+ },
358+ {
359+ "name": "Variant 2",
360+ "onSale": true,
361+ "price": 8,
362+ },
363+ ]
364+ ` ) ;
365+ } ) ;
366+
367+ const invalidData = mock . generateSeedData ( {
368+ variants : [
369+ mock . variant ( { name : "Variant 1 (valid)" , price : 10 , msrp : 10 } ) ,
370+ mock . variant ( { name : "Variant 2 (valid)" , price : 9 , msrp : 10 } ) ,
371+ mock . variant ( { name : "Variant 3 (invalid)" , price : 11 , msrp : 10 } ) ,
372+ // @ts -expect-error -- must be numbers
373+ mock . variant ( { name : "Variant 4 (invalid)" , price : "10" , msrp : "10" } ) ,
374+ // @ts -expect-error -- must be numbers
375+ mock . variant ( { name : "Variant 5 (invalid)" , price : "8" , msrp : "9" } ) ,
376+ ] ,
377+ } ) ;
378+ describe ( "when the data is invalid" , ( ) => {
379+ it ( "should strip invalid fields" , async ( ) => {
380+ const result = await executeBuilder ( qConditional , invalidData ) ;
381+ expect ( result ) . toMatchInlineSnapshot ( `
382+ [
383+ {
384+ "msrp": 10,
385+ "name": "Variant 1 (valid)",
386+ "onSale": false,
387+ },
388+ {
389+ "name": "Variant 2 (valid)",
390+ "onSale": true,
391+ "price": 9,
392+ },
393+ {
394+ "name": "Variant 3 (invalid)",
395+ },
396+ {
397+ "name": "Variant 4 (invalid)",
398+ },
399+ {
400+ "name": "Variant 5 (invalid)",
401+ },
402+ ]
403+ ` ) ;
404+ } ) ;
405+ } ) ;
406+ describe ( "when the isExhaustive flag is set" , ( ) => {
407+ const qConditional = qVariants . project ( ( q ) => ( {
408+ name : z . string ( ) ,
409+ ...q . conditional (
410+ {
411+ "price == msrp" : {
412+ onSale : q . value ( false , z . literal ( false ) ) ,
413+ msrp : z . number ( ) ,
414+ } ,
415+ "price < msrp" : {
416+ onSale : q . value ( true , z . literal ( true ) ) ,
417+ price : z . number ( ) ,
418+ } ,
419+ } ,
420+ { isExhaustive : true }
421+ ) ,
422+ } ) ) ;
423+ it ( "should throw an error" , async ( ) => {
424+ await expect ( async ( ) => {
425+ return await executeBuilder ( qConditional , invalidData ) ;
426+ } ) . rejects . toThrowErrorMatchingInlineSnapshot ( `
427+ [ValidationErrors: 3 Parsing Errors:
428+ result[2]: The data did not match any of the 2 conditional assertions
429+ result[3]: The data did not match any of the 2 conditional assertions
430+ result[4]: The data did not match any of the 2 conditional assertions]
431+ ` ) ;
432+ } ) ;
433+ } ) ;
434+ describe ( "when multiple conditions can be true" , ( ) => {
435+ const qConditional = qVariants . project ( ( q ) => ( {
436+ name : z . string ( ) ,
437+ ...q . conditional ( {
438+ "price < msrp" : {
439+ price : z . number ( ) ,
440+ } ,
441+ "price <= msrp" : {
442+ msrp : z . number ( ) ,
443+ } ,
444+ } ) ,
445+ } ) ) ;
446+ it ( "should include fields from both conditions" , async ( ) => {
447+ const results = await executeBuilder ( qConditional , data ) ;
448+ expect ( results ) . toMatchInlineSnapshot ( `
449+ [
450+ {
451+ "msrp": 10,
452+ "name": "Variant 1",
453+ },
454+ {
455+ "msrp": 9,
456+ "name": "Variant 2",
457+ "price": 8,
458+ },
459+ ]
460+ ` ) ;
461+ } ) ;
282462 } ) ;
283463 } ) ;
284464} ) ;
0 commit comments