@@ -38,6 +38,7 @@ export function validateFixtureInput(
3838  const  inlineFragmentSpreadsAst  =  inlineNamedFragmentSpreads ( queryAST ) ; 
3939  const  typeInfo  =  new  TypeInfo ( schema ) ; 
4040  const  valueStack : any [ ] [ ]  =  [ [ value ] ] ; 
41+   const  expectedFieldsStack : Set < string > [ ]  =  [ new  Set ( ) ] ;  // Initial set tracks root level fields 
4142  const  errors : string [ ]  =  [ ] ; 
4243  visit ( 
4344    inlineFragmentSpreadsAst , 
@@ -49,6 +50,9 @@ export function validateFixtureInput(
4950
5051          const  responseKey  =  node . alias ?. value  ||  node . name . value ; 
5152
53+           // Track this field as expected in the parent's set 
54+           expectedFieldsStack [ expectedFieldsStack . length  -  1 ] . add ( responseKey ) ; 
55+ 
5256          const  fieldDefinition  =  typeInfo . getFieldDef ( ) ; 
5357          const  fieldType  =  fieldDefinition ?. type ; 
5458
@@ -129,10 +133,21 @@ export function validateFixtureInput(
129133            } 
130134          } 
131135
136+           // If this field has nested selections, prepare to track expected child fields 
137+           if  ( node . selectionSet )  { 
138+             expectedFieldsStack . push ( new  Set < string > ( ) ) ; 
139+           } 
140+ 
132141          valueStack . push ( nestedValues ) ; 
133142        } , 
134-         leave ( )  { 
135-           valueStack . pop ( ) ; 
143+         leave ( node )  { 
144+           const  nestedValues  =  valueStack . pop ( ) ! ; 
145+ 
146+           // If this field had nested selections, check for extra fields 
147+           if  ( node . selectionSet )  { 
148+             const  expectedFields  =  expectedFieldsStack . pop ( ) ! ; 
149+             errors . push ( ...checkForExtraFields ( nestedValues ,  expectedFields ) ) ; 
150+           } 
136151        } , 
137152      } , 
138153      SelectionSet : { 
@@ -160,6 +175,11 @@ export function validateFixtureInput(
160175      } , 
161176    } ) 
162177  ) ; 
178+ 
179+   // The query's root SelectionSet has no parent Field node, so there's no Field.leave event to check it. 
180+   // We manually perform the same check here that would happen in Field.leave for nested objects. 
181+   errors . push ( ...checkForExtraFields ( valueStack [ 0 ] ,  expectedFieldsStack [ 0 ] ) ) ; 
182+ 
163183  return  {  errors } ; 
164184} 
165185
@@ -259,3 +279,31 @@ function isValueExpectedForType(
259279  // Only expect the value for this type if its __typename matches the parent type 
260280  return  valueTypename  ===  parentType . name ; 
261281} 
282+ 
283+ /** 
284+  * Checks fixture objects for fields that are not present in the GraphQL query. 
285+  * 
286+  * @param  fixtureObjects - Array of fixture objects to validate 
287+  * @param  expectedFields - Set of field names that are expected based on the query 
288+  * @returns  Array of error messages for any extra fields found (empty if valid) 
289+  * 
290+  * @remarks  
291+  * Only validates object types - skips null values and arrays. 
292+  */ 
293+ function  checkForExtraFields ( 
294+   fixtureObjects : any [ ] , 
295+   expectedFields : Set < string > 
296+ ) : string [ ]  { 
297+   const  errors : string [ ]  =  [ ] ; 
298+   for  ( const  fixtureObject  of  fixtureObjects )  { 
299+     if  ( typeof  fixtureObject  ===  "object"  &&  fixtureObject  !==  null  &&  ! Array . isArray ( fixtureObject ) )  { 
300+       const  fixtureFields  =  Object . keys ( fixtureObject ) ; 
301+       for  ( const  fixtureField  of  fixtureFields )  { 
302+         if  ( ! expectedFields . has ( fixtureField ) )  { 
303+           errors . push ( `Extra field "${ fixtureField }  " found in fixture data not in query` ) ; 
304+         } 
305+       } 
306+     } 
307+   } 
308+   return  errors ; 
309+ } 
0 commit comments