@@ -9,7 +9,11 @@ import { parse } from '../../language/parser.js';
99
1010import { buildSchema } from '../../utilities/buildASTSchema.js' ;
1111
12- import { execute , experimentalExecuteIncrementally } from '../execute.js' ;
12+ import {
13+ execute ,
14+ experimentalExecuteIncrementally ,
15+ subscribe ,
16+ } from '../execute.js' ;
1317import type {
1418 InitialIncrementalExecutionResult ,
1519 SubsequentIncrementalExecutionResult ,
@@ -52,12 +56,17 @@ const schema = buildSchema(`
5256
5357 type Query {
5458 todo: Todo
59+ nonNullableTodo: Todo!
5560 }
5661
5762 type Mutation {
5863 foo: String
5964 bar: String
6065 }
66+
67+ type Subscription {
68+ foo: String
69+ }
6170` ) ;
6271
6372describe ( 'Execute: Cancellation' , ( ) => {
@@ -300,6 +309,97 @@ describe('Execute: Cancellation', () => {
300309 } ) ;
301310 } ) ;
302311
312+ it ( 'should stop the execution when aborted despite a hanging resolver' , async ( ) => {
313+ const abortController = new AbortController ( ) ;
314+ const document = parse ( `
315+ query {
316+ todo {
317+ id
318+ author {
319+ id
320+ }
321+ }
322+ }
323+ ` ) ;
324+
325+ const resultPromise = execute ( {
326+ document,
327+ schema,
328+ abortSignal : abortController . signal ,
329+ rootValue : {
330+ todo : ( ) =>
331+ new Promise ( ( ) => {
332+ /* will never resolve */
333+ } ) ,
334+ } ,
335+ } ) ;
336+
337+ abortController . abort ( ) ;
338+
339+ const result = await resultPromise ;
340+
341+ expect ( result . errors ?. [ 0 ] . originalError ?. name ) . to . equal ( 'AbortError' ) ;
342+
343+ expectJSON ( result ) . toDeepEqual ( {
344+ data : {
345+ todo : null ,
346+ } ,
347+ errors : [
348+ {
349+ message : 'This operation was aborted' ,
350+ path : [ 'todo' ] ,
351+ locations : [ { line : 3 , column : 9 } ] ,
352+ } ,
353+ ] ,
354+ } ) ;
355+ } ) ;
356+
357+ it ( 'should stop the execution when aborted with proper null bubbling' , async ( ) => {
358+ const abortController = new AbortController ( ) ;
359+ const document = parse ( `
360+ query {
361+ nonNullableTodo {
362+ id
363+ author {
364+ id
365+ }
366+ }
367+ }
368+ ` ) ;
369+
370+ const resultPromise = execute ( {
371+ document,
372+ schema,
373+ abortSignal : abortController . signal ,
374+ rootValue : {
375+ nonNullableTodo : async ( ) =>
376+ Promise . resolve ( {
377+ id : '1' ,
378+ text : 'Hello, World!' ,
379+ /* c8 ignore next */
380+ author : ( ) => expect . fail ( 'Should not be called' ) ,
381+ } ) ,
382+ } ,
383+ } ) ;
384+
385+ abortController . abort ( ) ;
386+
387+ const result = await resultPromise ;
388+
389+ expect ( result . errors ?. [ 0 ] . originalError ?. name ) . to . equal ( 'AbortError' ) ;
390+
391+ expectJSON ( result ) . toDeepEqual ( {
392+ data : null ,
393+ errors : [
394+ {
395+ message : 'This operation was aborted' ,
396+ path : [ 'nonNullableTodo' ] ,
397+ locations : [ { line : 3 , column : 9 } ] ,
398+ } ,
399+ ] ,
400+ } ) ;
401+ } ) ;
402+
303403 it ( 'should stop deferred execution when aborted' , async ( ) => {
304404 const abortController = new AbortController ( ) ;
305405 const document = parse ( `
@@ -353,14 +453,12 @@ describe('Execute: Cancellation', () => {
353453 const abortController = new AbortController ( ) ;
354454 const document = parse ( `
355455 query {
356- todo {
357- id
358- ... on Todo @defer {
456+ ... on Query @defer {
457+ todo {
458+ id
359459 text
360460 author {
361- ... on Author @defer {
362- id
363- }
461+ id
364462 }
365463 }
366464 }
@@ -382,41 +480,27 @@ describe('Execute: Cancellation', () => {
382480 abortController . signal ,
383481 ) ;
384482
385- await resolveOnNextTick ( ) ;
386- await resolveOnNextTick ( ) ;
387- await resolveOnNextTick ( ) ;
388-
389483 abortController . abort ( ) ;
390484
391485 const result = await resultPromise ;
392486
393487 expectJSON ( result ) . toDeepEqual ( [
394488 {
395- data : {
396- todo : {
397- id : '1' ,
398- } ,
399- } ,
400- pending : [ { id : '0' , path : [ 'todo' ] } ] ,
489+ data : { } ,
490+ pending : [ { id : '0' , path : [ ] } ] ,
401491 hasNext : true ,
402492 } ,
403493 {
404494 incremental : [
405495 {
406496 data : {
407- text : 'hello world' ,
408- author : null ,
497+ todo : null ,
409498 } ,
410499 errors : [
411500 {
412- locations : [
413- {
414- column : 13 ,
415- line : 7 ,
416- } ,
417- ] ,
418501 message : 'This operation was aborted' ,
419- path : [ 'todo' , 'author' ] ,
502+ path : [ 'todo' ] ,
503+ locations : [ { line : 4 , column : 11 } ] ,
420504 } ,
421505 ] ,
422506 id : '0' ,
@@ -448,6 +532,10 @@ describe('Execute: Cancellation', () => {
448532 } ,
449533 } ) ;
450534
535+ await resolveOnNextTick ( ) ;
536+ await resolveOnNextTick ( ) ;
537+ await resolveOnNextTick ( ) ;
538+
451539 abortController . abort ( ) ;
452540
453541 const result = await resultPromise ;
@@ -498,4 +586,39 @@ describe('Execute: Cancellation', () => {
498586 ] ,
499587 } ) ;
500588 } ) ;
589+
590+ it ( 'should stop the execution when aborted during subscription' , async ( ) => {
591+ const abortController = new AbortController ( ) ;
592+ const document = parse ( `
593+ subscription {
594+ foo
595+ }
596+ ` ) ;
597+
598+ const resultPromise = subscribe ( {
599+ document,
600+ schema,
601+ abortSignal : abortController . signal ,
602+ rootValue : {
603+ foo : async ( ) =>
604+ new Promise ( ( ) => {
605+ /* will never resolve */
606+ } ) ,
607+ } ,
608+ } ) ;
609+
610+ abortController . abort ( ) ;
611+
612+ const result = await resultPromise ;
613+
614+ expectJSON ( result ) . toDeepEqual ( {
615+ errors : [
616+ {
617+ message : 'This operation was aborted' ,
618+ path : [ 'foo' ] ,
619+ locations : [ { line : 3 , column : 9 } ] ,
620+ } ,
621+ ] ,
622+ } ) ;
623+ } ) ;
501624} ) ;
0 commit comments