@@ -6,6 +6,7 @@ import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js';
66
77import { invariant } from '../../jsutils/invariant.js' ;
88import type { PromiseOrValue } from '../../jsutils/PromiseOrValue.js' ;
9+ import { promiseWithResolvers } from '../../jsutils/promiseWithResolvers.js' ;
910
1011import { parse } from '../../language/parser.js' ;
1112
@@ -663,6 +664,111 @@ describe('Execute: handles non-nullable types', () => {
663664 }
664665 expectJSON ( initialErrors ) . toDeepEqual ( result . errors ) ;
665666 } ) ;
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 first error a chance to propagate
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+
712+ describe ( 'cancellation with null bubbling' , ( ) => {
713+ function nestedPromise ( n : number ) : string {
714+ return n > 0 ? `promiseNest { ${ nestedPromise ( n - 1 ) } }` : 'promise' ;
715+ }
716+
717+ it ( 'returns an single error without cancellation' , async ( ) => {
718+ const query = `
719+ {
720+ promiseNonNull,
721+ ${ nestedPromise ( 4 ) }
722+ }
723+ ` ;
724+
725+ const result = await executeQuery ( query , throwingData ) ;
726+ expectJSON ( result ) . toDeepEqual ( {
727+ data : null ,
728+ errors : [
729+ // does not include syncNullError because result returns prior to it being added
730+ {
731+ message : 'promiseNonNull' ,
732+ path : [ 'promiseNonNull' ] ,
733+ locations : [ { line : 3 , column : 11 } ] ,
734+ } ,
735+ ] ,
736+ } ) ;
737+ } ) ;
738+
739+ it ( 'stops running despite error' , async ( ) => {
740+ const query = `
741+ {
742+ promiseNonNull,
743+ ${ nestedPromise ( 10 ) }
744+ }
745+ ` ;
746+
747+ let counter = 0 ;
748+ const rootValue = {
749+ ...throwingData ,
750+ promiseNest ( ) {
751+ return new Promise ( ( resolve ) => {
752+ counter ++ ;
753+ resolve ( rootValue ) ;
754+ } ) ;
755+ } ,
756+ } ;
757+ const result = await executeQuery ( query , rootValue ) ;
758+ expectJSON ( result ) . toDeepEqual ( {
759+ data : null ,
760+ errors : [
761+ {
762+ message : 'promiseNonNull' ,
763+ path : [ 'promiseNonNull' ] ,
764+ locations : [ { line : 3 , column : 11 } ] ,
765+ } ,
766+ ] ,
767+ } ) ;
768+ const counterAtExecutionEnd = counter ;
769+ await resolveOnNextTick ( ) ;
770+ expect ( counter ) . to . equal ( counterAtExecutionEnd ) ;
771+ } ) ;
666772 } ) ;
667773
668774 describe ( 'Handles non-null argument' , ( ) => {
0 commit comments