@@ -2,8 +2,11 @@ import { expect } from 'chai';
22import { describe , it } from 'mocha' ;
33
44import { expectJSON } from '../../__testUtils__/expectJSON.js' ;
5+ import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js' ;
56
7+ import { invariant } from '../../jsutils/invariant.js' ;
68import type { PromiseOrValue } from '../../jsutils/PromiseOrValue.js' ;
9+ import { promiseWithResolvers } from '../../jsutils/promiseWithResolvers.js' ;
710
811import { parse } from '../../language/parser.js' ;
912
@@ -526,6 +529,186 @@ describe('Execute: handles non-nullable types', () => {
526529 } ) ;
527530 } ) ;
528531
532+ describe ( 'Handles multiple errors for a single response position' , ( ) => {
533+ it ( 'nullable and non-nullable root fields throw nested errors' , async ( ) => {
534+ const query = `
535+ {
536+ promiseNonNullNest {
537+ syncNonNull
538+ }
539+ promiseNest {
540+ syncNonNull
541+ }
542+ }
543+ ` ;
544+ const result = await executeQuery ( query , throwingData ) ;
545+
546+ expectJSON ( result ) . toDeepEqual ( {
547+ data : null ,
548+ errors : [
549+ {
550+ message : syncNonNullError . message ,
551+ path : [ 'promiseNest' , 'syncNonNull' ] ,
552+ locations : [ { line : 7 , column : 13 } ] ,
553+ } ,
554+ {
555+ message : syncNonNullError . message ,
556+ path : [ 'promiseNonNullNest' , 'syncNonNull' ] ,
557+ locations : [ { line : 4 , column : 13 } ] ,
558+ } ,
559+ ] ,
560+ } ) ;
561+ } ) ;
562+
563+ it ( 'a nullable root field throws a slower nested error after a non-nullable root field throws a nested error' , async ( ) => {
564+ const query = `
565+ {
566+ promiseNonNullNest {
567+ syncNonNull
568+ }
569+ promiseNest {
570+ promiseNonNull
571+ }
572+ }
573+ ` ;
574+ const result = await executeQuery ( query , throwingData ) ;
575+
576+ expectJSON ( result ) . toDeepEqual ( {
577+ data : null ,
578+ errors : [
579+ {
580+ message : syncNonNullError . message ,
581+ path : [ 'promiseNonNullNest' , 'syncNonNull' ] ,
582+ locations : [ { line : 4 , column : 13 } ] ,
583+ } ,
584+ ] ,
585+ } ) ;
586+
587+ // allow time for slower error to reject
588+ invariant ( result . errors !== undefined ) ;
589+ const initialErrors = [ ...result . errors ] ;
590+ for ( let i = 0 ; i < 5 ; i ++ ) {
591+ // eslint-disable-next-line no-await-in-loop
592+ await resolveOnNextTick ( ) ;
593+ }
594+ expectJSON ( initialErrors ) . toDeepEqual ( result . errors ) ;
595+ } ) ;
596+
597+ it ( 'nullable and non-nullable nested fields throw nested errors' , async ( ) => {
598+ const query = `
599+ {
600+ syncNest {
601+ promiseNonNullNest {
602+ syncNonNull
603+ }
604+ promiseNest {
605+ syncNonNull
606+ }
607+ }
608+ }
609+ ` ;
610+ const result = await executeQuery ( query , throwingData ) ;
611+
612+ expectJSON ( result ) . toDeepEqual ( {
613+ data : { syncNest : null } ,
614+ errors : [
615+ {
616+ message : syncNonNullError . message ,
617+ path : [ 'syncNest' , 'promiseNest' , 'syncNonNull' ] ,
618+ locations : [ { line : 8 , column : 15 } ] ,
619+ } ,
620+ {
621+ message : syncNonNullError . message ,
622+ path : [ 'syncNest' , 'promiseNonNullNest' , 'syncNonNull' ] ,
623+ locations : [ { line : 5 , column : 15 } ] ,
624+ } ,
625+ ] ,
626+ } ) ;
627+ } ) ;
628+
629+ it ( 'a nullable nested field throws a slower nested error after a non-nullable nested field throws a nested error' , async ( ) => {
630+ const query = `
631+ {
632+ syncNest {
633+ promiseNonNullNest {
634+ syncNonNull
635+ }
636+ promiseNest {
637+ promiseNest {
638+ promiseNest {
639+ promiseNonNull
640+ }
641+ }
642+ }
643+ }
644+ }
645+ ` ;
646+ const result = await executeQuery ( query , throwingData ) ;
647+
648+ expectJSON ( result ) . toDeepEqual ( {
649+ data : { syncNest : null } ,
650+ errors : [
651+ {
652+ message : syncNonNullError . message ,
653+ path : [ 'syncNest' , 'promiseNonNullNest' , 'syncNonNull' ] ,
654+ locations : [ { line : 5 , column : 15 } ] ,
655+ } ,
656+ ] ,
657+ } ) ;
658+
659+ invariant ( result . errors !== undefined ) ;
660+ const initialErrors = [ ...result . errors ] ;
661+ for ( let i = 0 ; i < 20 ; i ++ ) {
662+ // eslint-disable-next-line no-await-in-loop
663+ await resolveOnNextTick ( ) ;
664+ }
665+ expectJSON ( initialErrors ) . toDeepEqual ( result . errors ) ;
666+ } ) ;
667+
668+ it ( 'suppresses a later error after a parent has been nulled' , async ( ) => {
669+ const query = `
670+ {
671+ syncNest {
672+ syncNonNull
673+ promise
674+ }
675+ }
676+ ` ;
677+
678+ const nonNullDeferred = promiseWithResolvers < unknown > ( ) ;
679+ const promiseDeferred = promiseWithResolvers < unknown > ( ) ;
680+
681+ const resultPromise = executeQuery ( query , {
682+ syncNest : {
683+ syncNonNull : ( ) => nonNullDeferred . promise ,
684+ promise : ( ) => promiseDeferred . promise ,
685+ } ,
686+ } ) ;
687+
688+ nonNullDeferred . reject ( syncNonNullError ) ;
689+
690+ // Give the first error a chance to null out the parent position.
691+ await resolveOnNextTick ( ) ;
692+ await resolveOnNextTick ( ) ;
693+ await resolveOnNextTick ( ) ;
694+
695+ promiseDeferred . reject ( promiseError ) ;
696+
697+ const result = await resultPromise ;
698+
699+ expectJSON ( result ) . toDeepEqual ( {
700+ data : { syncNest : null } ,
701+ errors : [
702+ {
703+ message : syncNonNullError . message ,
704+ path : [ 'syncNest' , 'syncNonNull' ] ,
705+ locations : [ { line : 4 , column : 13 } ] ,
706+ } ,
707+ ] ,
708+ } ) ;
709+ } ) ;
710+ } ) ;
711+
529712 describe ( 'Handles non-null argument' , ( ) => {
530713 const schemaWithNonNullArg = new GraphQLSchema ( {
531714 query : new GraphQLObjectType ( {
0 commit comments