Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-masked-union-fragment-members.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-codegen/visitor-plugin-common': patch
---

Emit a fragment member type for every union or interface member when fragment spreads are masked or combined, including members that select no fields. Nested spreads were referring to those names, and TypeScript reported them as missing.
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,18 @@ export class SelectionSetToObject<
: this.buildFragmentTypeName(fragmentName, fragmentSuffix, typeName);

if (possibleFields.length === 0) {
return [];
const namesEveryMember =
hasMultipleTypes &&
(this._config.inlineFragmentTypes === 'mask' ||
this._config.inlineFragmentTypes === 'combine');
if (!namesEveryMember) {
return [];
}
return {
name: declarationName,
content: this.getEmptyObjectType(),
isUnionType: false,
};
}

const flatFields = possibleFields.map(selectionObject => {
Expand Down
63 changes: 63 additions & 0 deletions packages/plugins/typescript/operations/tests/ts-documents.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5326,6 +5326,69 @@ function test(q: GetEntityBrandDataQuery): void {
`);
});

it('#10920 - keeps masked fragment member types that a nested spread references', async () => {
const testSchema = buildSchema(/* GraphQL */ `
type Human {
id: String!
name: String!
}
type Robot {
id: String!
registeredAt: String!
}
union User = Human | Robot
type Task {
id: String!
assignee: User!
}
type Query {
task(id: String!): Task!
}
`);

const fragment = parse(/* GraphQL */ `
fragment HumanName_User on User {
... on Human {
name
}
}

fragment TaskUser_User on User {
... on Human {
id
}
... on Robot {
id
}
...HumanName_User
}

fragment TaskDetail_Task on Task {
id
assignee {
...TaskUser_User
}
}

query GetTask($id: String!) {
task(id: $id) {
...TaskDetail_Task
}
}
`);

const { content } = await plugin(
testSchema,
[{ location: '', document: fragment }],
{ inlineFragmentTypes: 'mask' },
{ outputFile: 'graphql.ts' },
);

expect(content).toContain('type HumanName_User_Human_Fragment =');
expect(content).toContain('type HumanName_User_Robot_Fragment =');
await validate(content);
});

it('#6874 - generates types when parent type differs from spread fragment member types', async () => {
const testSchema = buildSchema(/* GraphQL */ `
interface Animal {
Expand Down