@@ -78,6 +78,123 @@ describe('Execute: Accepts any iterable as list value', () => {
7878 } ) ;
7979} ) ;
8080
81+ describe ( 'Execute: Handles abrupt completion in synchronous iterables' , ( ) => {
82+ function complete ( rootValue : unknown , as : string = '[String]' ) {
83+ return execute ( {
84+ schema : buildSchema ( `type Query { listField: ${ as } }` ) ,
85+ document : parse ( '{ listField }' ) ,
86+ rootValue,
87+ } ) ;
88+ }
89+
90+ it ( 'closes the iterator when `next` throws' , async ( ) => {
91+ let returned = false ;
92+ let nextCalls = 0 ;
93+
94+ const listField : IterableIterator < string > = {
95+ [ Symbol . iterator ] ( ) : IterableIterator < string > {
96+ return this ;
97+ } ,
98+ next ( ) : IteratorResult < string > {
99+ nextCalls ++ ;
100+ if ( nextCalls === 1 ) {
101+ return { done : false , value : 'ok' } ;
102+ }
103+ throw new Error ( 'bad' ) ;
104+ } ,
105+ return ( ) : IteratorResult < string > {
106+ returned = true ;
107+ return { done : true , value : undefined } ;
108+ } ,
109+ } ;
110+
111+ expectJSON ( await complete ( { listField } ) ) . toDeepEqual ( {
112+ data : { listField : null } ,
113+ errors : [
114+ {
115+ message : 'bad' ,
116+ locations : [ { line : 1 , column : 3 } ] ,
117+ path : [ 'listField' ] ,
118+ } ,
119+ ] ,
120+ } ) ;
121+ expect ( nextCalls ) . to . equal ( 2 ) ;
122+ expect ( returned ) . to . equal ( true ) ;
123+ } ) ;
124+
125+ it ( 'closes the iterator when a null bubbles up from a non-null item' , async ( ) => {
126+ const values = [ 1 , null , 2 ] ;
127+ let index = 0 ;
128+ let returned = false ;
129+
130+ const listField : IterableIterator < number | null > = {
131+ [ Symbol . iterator ] ( ) : IterableIterator < number | null > {
132+ return this ;
133+ } ,
134+ next ( ) : IteratorResult < number | null > {
135+ const value = values [ index ++ ] ;
136+ if ( value === undefined ) {
137+ return { done : true , value : undefined } ;
138+ }
139+ return { done : false , value } ;
140+ } ,
141+ return ( ) : IteratorResult < number | null > {
142+ returned = true ;
143+ return { done : true , value : undefined } ;
144+ } ,
145+ } ;
146+
147+ expectJSON ( await complete ( { listField } , '[Int!]' ) ) . toDeepEqual ( {
148+ data : { listField : null } ,
149+ errors : [
150+ {
151+ message : 'Cannot return null for non-nullable field Query.listField.' ,
152+ locations : [ { line : 1 , column : 3 } ] ,
153+ path : [ 'listField' , 1 ] ,
154+ } ,
155+ ] ,
156+ } ) ;
157+ expect ( index ) . to . equal ( 2 ) ;
158+ expect ( returned ) . to . equal ( true ) ;
159+ } ) ;
160+
161+ it ( 'ignores errors thrown by the iterator `return` method' , async ( ) => {
162+ const values = [ 1 , null , 2 ] ;
163+ let index = 0 ;
164+ let returned = false ;
165+
166+ const listField : IterableIterator < number | null > = {
167+ [ Symbol . iterator ] ( ) : IterableIterator < number | null > {
168+ return this ;
169+ } ,
170+ next ( ) : IteratorResult < number | null > {
171+ const value = values [ index ++ ] ;
172+ if ( value === undefined ) {
173+ return { done : true , value : undefined } ;
174+ }
175+ return { done : false , value } ;
176+ } ,
177+ return ( ) : IteratorResult < number | null > {
178+ returned = true ;
179+ throw new Error ( 'ignored return error' ) ;
180+ } ,
181+ } ;
182+
183+ expectJSON ( await complete ( { listField } , '[Int!]' ) ) . toDeepEqual ( {
184+ data : { listField : null } ,
185+ errors : [
186+ {
187+ message : 'Cannot return null for non-nullable field Query.listField.' ,
188+ locations : [ { line : 1 , column : 3 } ] ,
189+ path : [ 'listField' , 1 ] ,
190+ } ,
191+ ] ,
192+ } ) ;
193+ expect ( index ) . to . equal ( 2 ) ;
194+ expect ( returned ) . to . equal ( true ) ;
195+ } ) ;
196+ } ) ;
197+
81198describe ( 'Execute: Accepts async iterables as list value' , ( ) => {
82199 function complete ( rootValue : unknown , as : string = '[String]' ) {
83200 return execute ( {
0 commit comments