@@ -9,7 +9,11 @@ import { parse } from '../../language/parser.js';
9
9
10
10
import { buildSchema } from '../../utilities/buildASTSchema.js' ;
11
11
12
- import { execute , experimentalExecuteIncrementally } from '../execute.js' ;
12
+ import {
13
+ execute ,
14
+ experimentalExecuteIncrementally ,
15
+ subscribe ,
16
+ } from '../execute.js' ;
13
17
import type {
14
18
InitialIncrementalExecutionResult ,
15
19
SubsequentIncrementalExecutionResult ,
@@ -52,12 +56,17 @@ const schema = buildSchema(`
52
56
53
57
type Query {
54
58
todo: Todo
59
+ nonNullableTodo: Todo!
55
60
}
56
61
57
62
type Mutation {
58
63
foo: String
59
64
bar: String
60
65
}
66
+
67
+ type Subscription {
68
+ foo: String
69
+ }
61
70
` ) ;
62
71
63
72
describe ( 'Execute: Cancellation' , ( ) => {
@@ -300,6 +309,97 @@ describe('Execute: Cancellation', () => {
300
309
} ) ;
301
310
} ) ;
302
311
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
+
303
403
it ( 'should stop deferred execution when aborted' , async ( ) => {
304
404
const abortController = new AbortController ( ) ;
305
405
const document = parse ( `
@@ -353,14 +453,12 @@ describe('Execute: Cancellation', () => {
353
453
const abortController = new AbortController ( ) ;
354
454
const document = parse ( `
355
455
query {
356
- todo {
357
- id
358
- ... on Todo @defer {
456
+ ... on Query @defer {
457
+ todo {
458
+ id
359
459
text
360
460
author {
361
- ... on Author @defer {
362
- id
363
- }
461
+ id
364
462
}
365
463
}
366
464
}
@@ -382,41 +480,27 @@ describe('Execute: Cancellation', () => {
382
480
abortController . signal ,
383
481
) ;
384
482
385
- await resolveOnNextTick ( ) ;
386
- await resolveOnNextTick ( ) ;
387
- await resolveOnNextTick ( ) ;
388
-
389
483
abortController . abort ( ) ;
390
484
391
485
const result = await resultPromise ;
392
486
393
487
expectJSON ( result ) . toDeepEqual ( [
394
488
{
395
- data : {
396
- todo : {
397
- id : '1' ,
398
- } ,
399
- } ,
400
- pending : [ { id : '0' , path : [ 'todo' ] } ] ,
489
+ data : { } ,
490
+ pending : [ { id : '0' , path : [ ] } ] ,
401
491
hasNext : true ,
402
492
} ,
403
493
{
404
494
incremental : [
405
495
{
406
496
data : {
407
- text : 'hello world' ,
408
- author : null ,
497
+ todo : null ,
409
498
} ,
410
499
errors : [
411
500
{
412
- locations : [
413
- {
414
- column : 13 ,
415
- line : 7 ,
416
- } ,
417
- ] ,
418
501
message : 'This operation was aborted' ,
419
- path : [ 'todo' , 'author' ] ,
502
+ path : [ 'todo' ] ,
503
+ locations : [ { line : 4 , column : 11 } ] ,
420
504
} ,
421
505
] ,
422
506
id : '0' ,
@@ -448,6 +532,10 @@ describe('Execute: Cancellation', () => {
448
532
} ,
449
533
} ) ;
450
534
535
+ await resolveOnNextTick ( ) ;
536
+ await resolveOnNextTick ( ) ;
537
+ await resolveOnNextTick ( ) ;
538
+
451
539
abortController . abort ( ) ;
452
540
453
541
const result = await resultPromise ;
@@ -498,4 +586,39 @@ describe('Execute: Cancellation', () => {
498
586
] ,
499
587
} ) ;
500
588
} ) ;
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
+ } ) ;
501
624
} ) ;
0 commit comments