diff --git a/.changeset/warm-trainers-shout.md b/.changeset/warm-trainers-shout.md new file mode 100644 index 00000000000..5e7b9795b63 --- /dev/null +++ b/.changeset/warm-trainers-shout.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/visitor-plugin-common': patch +'@graphql-codegen/typescript-operations': patch +--- + +Fix issue where selection set flattening uses the wrong parent type diff --git a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts index 74e3d588080..95141f6e198 100644 --- a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts +++ b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts @@ -248,7 +248,8 @@ export class SelectionSetToObject + selections: ReadonlyArray, + parentSchemaType?: GraphQLObjectType ): Map> { const selectionNodesByTypeName = new Map>(); const inlineFragmentSelections: InlineFragmentNode[] = []; @@ -270,10 +271,16 @@ export class SelectionSetToObject objectType.name === parentSchemaType.name)) ) { // also process fields from fragment that apply for this parentType - const flatten = this.flattenSelectionSet(selectionNode.selectionNodes); + const flatten = this.flattenSelectionSet(selectionNode.selectionNodes, parentSchemaType); const typeNodes = flatten.get(parentSchemaType.name) ?? []; selectionNodes.push(...typeNodes); for (const iinterface of parentSchemaType.getInterfaces()) { diff --git a/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap b/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap index f6d1c1dbf7c..440ac880f0f 100644 --- a/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap +++ b/packages/plugins/typescript/operations/tests/__snapshots__/ts-documents.spec.ts.snap @@ -133,6 +133,20 @@ function test(q: QQuery) { }" `; +exports[`TypeScript Operations Plugin Issues #6874 - generates types when parent type differs from spread fragment member types and preResolveTypes=true 1`] = ` +"export type SnakeQueryQueryVariables = Exact<{ [key: string]: never; }>; + + +export type SnakeQueryQuery = { __typename?: 'Query', snake: { __typename?: 'Snake', name: string, features: { __typename?: 'SnakeFeatures', color: string, length: number } } | { __typename?: 'Error' } }; + +type AnimalFragment_Bat_Fragment = { __typename?: 'Bat', features: { __typename?: 'BatFeatures', color: string, wingspan: number } }; + +type AnimalFragment_Snake_Fragment = { __typename?: 'Snake', features: { __typename?: 'SnakeFeatures', color: string, length: number } }; + +export type AnimalFragmentFragment = AnimalFragment_Bat_Fragment | AnimalFragment_Snake_Fragment; +" +`; + exports[`TypeScript Operations Plugin Selection Set Should generate the correct __typename when using both inline fragment and spread over type 1`] = ` "export type UserQueryQueryVariables = Exact<{ [key: string]: never; }>; diff --git a/packages/plugins/typescript/operations/tests/ts-documents.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.spec.ts index 3a5ab87b87a..17982fb9f5f 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.spec.ts @@ -5881,6 +5881,70 @@ function test(q: GetEntityBrandDataQuery): void { " `); }); + + it('#6874 - generates types when parent type differs from spread fragment member types and preResolveTypes=true', async () => { + const testSchema = buildSchema(/* GraphQL */ ` + interface Animal { + name: String! + } + type Bat implements Animal { + name: String! + features: BatFeatures! + } + type BatFeatures { + color: String! + wingspan: Int! + } + type Snake implements Animal { + name: String! + features: SnakeFeatures! + } + type SnakeFeatures { + color: String! + length: Int! + } + type Error { + message: String! + } + union SnakeResult = Snake | Error + type Query { + snake: SnakeResult! + } + `); + + const query = parse(/* GraphQL */ ` + query SnakeQuery { + snake { + ... on Snake { + name + ...AnimalFragment + } + } + } + fragment AnimalFragment on Animal { + ... on Bat { + features { + color + wingspan + } + } + ... on Snake { + features { + color + length + } + } + } + `); + + const config = { preResolveTypes: true }; + + const { content } = await plugin(testSchema, [{ location: '', document: query }], config, { + outputFile: 'graphql.ts', + }); + + expect(content).toMatchSnapshot(); + }); }); describe('conditional directives handling', () => {