@@ -11,6 +11,24 @@ import {
1111}  from  "graphql" ; 
1212import  {  inlineNamedFragmentSpreads  }  from  "../utils/inline-named-fragment-spreads.js" ; 
1313
14+ function  checkForExtraFields ( 
15+   fixtureObjects : any [ ] , 
16+   expectedFields : Set < string > 
17+ ) : string [ ]  { 
18+   const  errors : string [ ]  =  [ ] ; 
19+   for  ( const  fixtureObject  of  fixtureObjects )  { 
20+     if  ( typeof  fixtureObject  ===  "object"  &&  fixtureObject  !==  null  &&  ! Array . isArray ( fixtureObject ) )  { 
21+       const  fixtureFields  =  Object . keys ( fixtureObject ) ; 
22+       for  ( const  fixtureField  of  fixtureFields )  { 
23+         if  ( ! expectedFields . has ( fixtureField ) )  { 
24+           errors . push ( `Extra field "${ fixtureField }  " found in fixture data` ) ; 
25+         } 
26+       } 
27+     } 
28+   } 
29+   return  errors ; 
30+ } 
31+ 
1432export  interface  ValidateFixtureInputResult  { 
1533  valid : boolean ; 
1634  errors : string [ ] ; 
@@ -24,6 +42,7 @@ export function validateFixtureInput(
2442  const  inlineFragmentSpreadsAst  =  inlineNamedFragmentSpreads ( queryAST ) ; 
2543  const  typeInfo  =  new  TypeInfo ( schema ) ; 
2644  const  valueStack : any [ ]  =  [ [ value ] ] ; 
45+   const  expectedFieldsStack : Set < string > [ ]  =  [ new  Set ( ) ] ;  // Initial set tracks root level fields 
2746  const  errors : string [ ]  =  [ ] ; 
2847  visit ( 
2948    inlineFragmentSpreadsAst , 
@@ -35,6 +54,9 @@ export function validateFixtureInput(
3554
3655          const  responseKey  =  node . alias ?. value  ||  node . name . value ; 
3756
57+           // Track this field as expected in the parent's set 
58+           expectedFieldsStack [ expectedFieldsStack . length  -  1 ] . add ( responseKey ) ; 
59+ 
3860          const  fieldDefinition  =  typeInfo . getFieldDef ( ) ; 
3961          const  fieldType  =  fieldDefinition ?. type ; 
4062
@@ -76,10 +98,21 @@ export function validateFixtureInput(
7698            } 
7799          } 
78100
101+           // If this field has nested selections, prepare to track expected child fields 
102+           if  ( node . selectionSet )  { 
103+             expectedFieldsStack . push ( new  Set < string > ( ) ) ; 
104+           } 
105+ 
79106          valueStack . push ( nestedValues ) ; 
80107        } , 
81-         leave ( )  { 
82-           valueStack . pop ( ) ; 
108+         leave ( node )  { 
109+           const  nestedValues  =  valueStack . pop ( ) ! ; 
110+ 
111+           // If this field had nested selections, check for extra fields 
112+           if  ( node . selectionSet )  { 
113+             const  expectedFields  =  expectedFieldsStack . pop ( ) ! ; 
114+             errors . push ( ...checkForExtraFields ( nestedValues ,  expectedFields ) ) ; 
115+           } 
83116        } , 
84117      } , 
85118      SelectionSet : { 
@@ -97,6 +130,7 @@ export function validateFixtureInput(
97130                selection . kind  ==  Kind . INLINE_FRAGMENT 
98131            ) . length ; 
99132
133+             // We only need to check for __typename if there are multiple fragment spreads 
100134            if  ( ! hasTypename  &&  fragmentSpreadCount  >  1 )  { 
101135              errors . push ( 
102136                `Missing __typename field for abstract type ${ getNamedType ( typeInfo . getType ( ) ) ?. name }  ` 
@@ -107,5 +141,10 @@ export function validateFixtureInput(
107141      } , 
108142    } ) 
109143  ) ; 
144+ 
145+   // The query's root SelectionSet has no parent Field node, so there's no Field.leave event to check it. 
146+   // We manually perform the same check here that would happen in Field.leave for nested objects. 
147+   errors . push ( ...checkForExtraFields ( valueStack [ 0 ] ,  expectedFieldsStack [ 0 ] ) ) ; 
148+ 
110149  return  {  valid : errors . length  ===  0 ,  errors } ; 
111150} 
0 commit comments