@@ -77,123 +77,115 @@ describe('Abort Signal', () => {
77
77
expect ( stopped ) . toBe ( true ) ;
78
78
expect ( results ) . toEqual ( [ 0 , 1 , 2 , 3 , 4 ] ) ;
79
79
} ) ;
80
- it ( 'should stop the serial mutation execution ' , async ( ) => {
80
+ it ( 'pending subscription execution is canceled ' , async ( ) => {
81
81
const controller = new AbortController ( ) ;
82
- const firstFn = jest . fn ( ( ) => true ) ;
83
- const secondFn = jest . fn ( ( ) => {
84
- controller . abort ( ) ;
85
- return true ;
86
- } ) ;
87
- const thirdFn = jest . fn ( ( ) => true ) ;
82
+ const rootResolverGotInvokedD = createDeferred ( ) ;
83
+ const requestGotCancelledD = createDeferred ( ) ;
84
+ let aResolverGotInvoked = false ;
85
+
88
86
const schema = makeExecutableSchema ( {
89
87
typeDefs : /* GraphQL */ `
90
88
type Query {
91
89
_: Boolean
92
90
}
93
- type Mutation {
94
- first: Boolean
95
- second: Boolean
96
- third: Boolean
91
+ type Subscription {
92
+ a: A!
93
+ }
94
+
95
+ type A {
96
+ a: String!
97
97
}
98
98
` ,
99
99
resolvers : {
100
- Mutation : {
101
- first : firstFn ,
102
- second : secondFn ,
103
- third : thirdFn ,
100
+ Subscription : {
101
+ a : {
102
+ async * subscribe ( ) {
103
+ yield 1 ;
104
+ } ,
105
+ async resolve ( ) {
106
+ rootResolverGotInvokedD . resolve ( ) ;
107
+ await requestGotCancelledD . promise ;
108
+ return { a : 'a' } ;
109
+ } ,
110
+ } ,
111
+ } ,
112
+ A : {
113
+ a ( ) {
114
+ aResolverGotInvoked = true ;
115
+ return 'a' ;
116
+ } ,
104
117
} ,
105
118
} ,
106
119
} ) ;
107
120
const result = await normalizedExecutor ( {
108
121
schema,
109
122
document : parse ( /* GraphQL */ `
110
- mutation {
111
- first
112
- second
113
- third
123
+ subscription {
124
+ a {
125
+ a
126
+ }
114
127
}
115
128
` ) ,
116
129
signal : controller . signal ,
117
130
} ) ;
118
- expect ( firstFn ) . toHaveBeenCalledTimes ( 1 ) ;
119
- expect ( secondFn ) . toHaveBeenCalledTimes ( 1 ) ;
120
- expect ( thirdFn ) . toHaveBeenCalledTimes ( 0 ) ;
121
- expect ( result ) . toMatchObject ( {
122
- data : {
123
- first : true ,
124
- second : true ,
125
- third : null ,
126
- } ,
127
- errors : [
128
- {
129
- message : 'Execution aborted' ,
130
- path : [ 'second' ] ,
131
- locations : [
132
- {
133
- line : 4 ,
134
- column : 11 ,
135
- } ,
136
- ] ,
137
- } ,
138
- ] ,
139
- } ) ;
131
+ assertAsyncIterable ( result ) ;
132
+ const iterator = result ! [ Symbol . asyncIterator ] ( ) ;
133
+ const $next = iterator . next ( ) ;
134
+ await rootResolverGotInvokedD . promise ;
135
+ controller . abort ( ) ;
136
+ await expect ( $next ) . rejects . toMatchInlineSnapshot ( `DOMException {}` ) ;
137
+ expect ( aResolverGotInvoked ) . toEqual ( false ) ;
140
138
} ) ;
141
- it ( 'should stop the parallel query execution' , async ( ) => {
142
- let resolve$ : ( value : any ) => void = ( ) => { } ;
139
+ it ( 'should stop the serial mutation execution' , async ( ) => {
143
140
const controller = new AbortController ( ) ;
141
+
142
+ let didInvokeFirstFn = false ;
143
+ let didInvokeSecondFn = false ;
144
+ let didInvokeThirdFn = false ;
144
145
const schema = makeExecutableSchema ( {
145
146
typeDefs : /* GraphQL */ `
146
147
type Query {
148
+ _: Boolean
149
+ }
150
+ type Mutation {
147
151
first: Boolean
148
152
second: Boolean
149
153
third: Boolean
150
154
}
151
155
` ,
152
156
resolvers : {
153
- Query : {
154
- first : async ( ) => true ,
155
- second : async ( ) => {
157
+ Mutation : {
158
+ first ( ) {
159
+ didInvokeFirstFn = true ;
160
+ return true ;
161
+ } ,
162
+ second ( ) {
163
+ didInvokeSecondFn = true ;
156
164
controller . abort ( ) ;
157
165
return true ;
158
166
} ,
159
- third : ( ) =>
160
- new Promise ( resolve => {
161
- resolve$ = resolve ;
162
- } ) ,
167
+ third ( ) {
168
+ didInvokeThirdFn = true ;
169
+ return true ;
170
+ } ,
163
171
} ,
164
172
} ,
165
173
} ) ;
166
- const result = await normalizedExecutor ( {
174
+ const result$ = normalizedExecutor ( {
167
175
schema,
168
176
document : parse ( /* GraphQL */ `
169
- query {
177
+ mutation {
170
178
first
171
179
second
172
180
third
173
181
}
174
182
` ) ,
175
183
signal : controller . signal ,
176
184
} ) ;
177
- resolve$ ?.( true ) ;
178
- expect ( result ) . toMatchObject ( {
179
- data : {
180
- first : true ,
181
- second : true ,
182
- third : null ,
183
- } ,
184
- errors : [
185
- {
186
- message : 'Execution aborted' ,
187
- path : [ 'second' ] ,
188
- locations : [
189
- {
190
- line : 4 ,
191
- column : 11 ,
192
- } ,
193
- ] ,
194
- } ,
195
- ] ,
196
- } ) ;
185
+ expect ( result$ ) . rejects . toMatchInlineSnapshot ( `DOMException {}` ) ;
186
+ expect ( didInvokeFirstFn ) . toBe ( true ) ;
187
+ expect ( didInvokeSecondFn ) . toBe ( true ) ;
188
+ expect ( didInvokeThirdFn ) . toBe ( false ) ;
197
189
} ) ;
198
190
it ( 'should stop stream execution' , async ( ) => {
199
191
const controller = new AbortController ( ) ;
@@ -244,7 +236,7 @@ describe('Abort Signal', () => {
244
236
await expect ( result$ ) . rejects . toMatchInlineSnapshot ( `DOMException {}` ) ;
245
237
expect ( isAborted ) . toEqual ( true ) ;
246
238
} ) ;
247
- it ( 'stops pending stream execution for incremental delivery' , async ( ) => {
239
+ it ( 'stops pending stream execution for incremental delivery (@stream) ' , async ( ) => {
248
240
const controller = new AbortController ( ) ;
249
241
const d = createDeferred ( ) ;
250
242
let isReturnInvoked = false ;
@@ -306,6 +298,164 @@ describe('Abort Signal', () => {
306
298
await expect ( next$ ) . rejects . toMatchInlineSnapshot ( `DOMException {}` ) ;
307
299
expect ( isReturnInvoked ) . toEqual ( true ) ;
308
300
} ) ;
301
+ it ( 'stops pending stream execution for parallel sources incremental delivery (@stream)' , async ( ) => {
302
+ const controller = new AbortController ( ) ;
303
+ const d1 = createDeferred ( ) ;
304
+ const d2 = createDeferred ( ) ;
305
+
306
+ let isReturn1Invoked = false ;
307
+ let isReturn2Invoked = false ;
308
+
309
+ const schema = makeExecutableSchema ( {
310
+ typeDefs : /* GraphQL */ `
311
+ type Query {
312
+ counter1: [Int!]!
313
+ counter2: [Int!]!
314
+ }
315
+ ` ,
316
+ resolvers : {
317
+ Query : {
318
+ counter1 : ( ) => ( {
319
+ [ Symbol . asyncIterator ] ( ) {
320
+ return this ;
321
+ } ,
322
+ next ( ) {
323
+ return d1 . promise . then ( ( ) => ( { done : true } ) ) ;
324
+ } ,
325
+ return ( ) {
326
+ isReturn1Invoked = true ;
327
+ d1 . resolve ( ) ;
328
+ return Promise . resolve ( { done : true } ) ;
329
+ } ,
330
+ } ) ,
331
+ counter2 : ( ) => ( {
332
+ [ Symbol . asyncIterator ] ( ) {
333
+ return this ;
334
+ } ,
335
+ next ( ) {
336
+ return d2 . promise . then ( ( ) => ( { done : true } ) ) ;
337
+ } ,
338
+ return ( ) {
339
+ isReturn2Invoked = true ;
340
+ d2 . resolve ( ) ;
341
+ return Promise . resolve ( { done : true } ) ;
342
+ } ,
343
+ } ) ,
344
+ } ,
345
+ } ,
346
+ } ) ;
347
+
348
+ const result = await normalizedExecutor ( {
349
+ schema,
350
+ document : parse ( /* GraphQL */ `
351
+ query {
352
+ counter1 @stream
353
+ counter2 @stream
354
+ }
355
+ ` ) ,
356
+ signal : controller . signal ,
357
+ } ) ;
358
+
359
+ if ( ! isAsyncIterable ( result ) ) {
360
+ throw new Error ( 'Result is not an async iterable' ) ;
361
+ }
362
+
363
+ const iter = result [ Symbol . asyncIterator ] ( ) ;
364
+
365
+ const next = await iter . next ( ) ;
366
+ expect ( next ) . toEqual ( {
367
+ done : false ,
368
+ value : {
369
+ data : {
370
+ counter1 : [ ] ,
371
+ counter2 : [ ] ,
372
+ } ,
373
+ hasNext : true ,
374
+ } ,
375
+ } ) ;
376
+
377
+ const next$ = iter . next ( ) ;
378
+ controller . abort ( ) ;
379
+ await expect ( next$ ) . rejects . toMatchInlineSnapshot ( `DOMException {}` ) ;
380
+ expect ( isReturn1Invoked ) . toEqual ( true ) ;
381
+ expect ( isReturn2Invoked ) . toEqual ( true ) ;
382
+ } ) ;
383
+ it ( 'stops pending stream execution for incremental delivery (@defer)' , async ( ) => {
384
+ const aResolverGotInvokedD = createDeferred ( ) ;
385
+ const requestGotCancelledD = createDeferred ( ) ;
386
+ let bResolverGotInvoked = false ;
387
+
388
+ const schema = makeExecutableSchema ( {
389
+ typeDefs : /* GraphQL */ `
390
+ type Query {
391
+ root: A!
392
+ }
393
+ type A {
394
+ a: B!
395
+ }
396
+ type B {
397
+ b: String
398
+ }
399
+ ` ,
400
+ resolvers : {
401
+ Query : {
402
+ async root ( ) {
403
+ return { a : 'a' } ;
404
+ } ,
405
+ } ,
406
+ A : {
407
+ async a ( ) {
408
+ aResolverGotInvokedD . resolve ( ) ;
409
+ await requestGotCancelledD . promise ;
410
+ return { b : 'b' } ;
411
+ } ,
412
+ } ,
413
+ B : {
414
+ b : obj => {
415
+ bResolverGotInvoked = true ;
416
+ return obj . b ;
417
+ } ,
418
+ } ,
419
+ } ,
420
+ } ) ;
421
+ const controller = new AbortController ( ) ;
422
+ const result = await normalizedExecutor ( {
423
+ schema,
424
+ document : parse ( /* GraphQL */ `
425
+ query {
426
+ root {
427
+ ... @defer {
428
+ a {
429
+ b
430
+ }
431
+ }
432
+ }
433
+ }
434
+ ` ) ,
435
+ signal : controller . signal ,
436
+ } ) ;
437
+
438
+ if ( ! isAsyncIterable ( result ) ) {
439
+ throw new Error ( 'Result is not an async iterable' ) ;
440
+ }
441
+
442
+ const iterator = result [ Symbol . asyncIterator ] ( ) ;
443
+ const next = await iterator . next ( ) ;
444
+ expect ( next . value ) . toMatchInlineSnapshot ( `
445
+ {
446
+ "data": {
447
+ "root": {},
448
+ },
449
+ "hasNext": true,
450
+ }
451
+ ` ) ;
452
+ const next$ = iterator . next ( ) ;
453
+ await aResolverGotInvokedD . promise ;
454
+ controller . abort ( ) ;
455
+ requestGotCancelledD . resolve ( ) ;
456
+ await expect ( next$ ) . rejects . toThrow ( 'This operation was aborted' ) ;
457
+ expect ( bResolverGotInvoked ) . toBe ( false ) ;
458
+ } ) ;
309
459
it ( 'stops promise execution' , async ( ) => {
310
460
const controller = new AbortController ( ) ;
311
461
const d = createDeferred ( ) ;
0 commit comments