From 79fee3cada20d683d250aad5aa5fef9d6ed9f4d2 Mon Sep 17 00:00:00 2001 From: Eddy Nguyen Date: Tue, 2 Jul 2024 18:11:25 +1000 Subject: [PATCH] Fix abstract types not being handled when nested in object types (#10014) --- .changeset/grumpy-clocks-retire.md | 6 + dev-test/modules/types.ts | 13 +- .../src/base-resolvers-visitor.ts | 99 ++++++- .../__snapshots__/ts-resolvers.spec.ts.snap | 18 +- ...onfig.resolversNonOptionalTypename.spec.ts | 237 +++++++++++++++++ .../tests/ts-resolvers.interface.spec.ts | 248 +++++++++++++++++- .../tests/ts-resolvers.mapping.spec.ts | 24 +- .../resolvers/tests/ts-resolvers.spec.ts | 238 ----------------- 8 files changed, 615 insertions(+), 268 deletions(-) create mode 100644 .changeset/grumpy-clocks-retire.md create mode 100644 packages/plugins/typescript/resolvers/tests/ts-resolvers.config.resolversNonOptionalTypename.spec.ts diff --git a/.changeset/grumpy-clocks-retire.md b/.changeset/grumpy-clocks-retire.md new file mode 100644 index 00000000000..88e0342e148 --- /dev/null +++ b/.changeset/grumpy-clocks-retire.md @@ -0,0 +1,6 @@ +--- +'@graphql-codegen/visitor-plugin-common': patch +'@graphql-codegen/typescript-resolvers': patch +--- + +Fix object types with fields being abstract types not pointing to resolver types correctly diff --git a/dev-test/modules/types.ts b/dev-test/modules/types.ts index af996e8e275..4a4bf594d23 100644 --- a/dev-test/modules/types.ts +++ b/dev-test/modules/types.ts @@ -171,10 +171,12 @@ export type ResolversUnionTypes<_RefType extends Record> = { /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = { - Article: ResolverTypeWrapper
; + Article: ResolverTypeWrapper & { author: ResolversTypes['User'] }>; Boolean: ResolverTypeWrapper; CreditCard: ResolverTypeWrapper; - Donation: ResolverTypeWrapper; + Donation: ResolverTypeWrapper< + Omit & { recipient: ResolversTypes['User']; sender: ResolversTypes['User'] } + >; DonationInput: DonationInput; Float: ResolverTypeWrapper; ID: ResolverTypeWrapper; @@ -191,10 +193,13 @@ export type ResolversTypes = { /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = { - Article: Article; + Article: Omit & { author: ResolversParentTypes['User'] }; Boolean: Scalars['Boolean']['output']; CreditCard: CreditCard; - Donation: Donation; + Donation: Omit & { + recipient: ResolversParentTypes['User']; + sender: ResolversParentTypes['User']; + }; DonationInput: DonationInput; Float: Scalars['Float']['output']; ID: Scalars['ID']['output']; diff --git a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts index 89b8a65d1b1..a08472afa8a 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts @@ -652,6 +652,7 @@ export class BaseResolversVisitor< protected _hasFederation = false; protected _fieldContextTypeMap: FieldContextTypeMap; protected _directiveContextTypesMap: FieldContextTypeMap; + protected _checkedTypesWithNestedAbstractTypes: Record = {}; private _directiveResolverMappings: Record; private _shouldMapType: { [typeName: string]: boolean } = {}; @@ -1788,8 +1789,23 @@ export class BaseResolversVisitor< const baseType = getBaseType(field.type); const isUnion = isUnionType(baseType); const isInterface = isInterfaceType(baseType); + const isObject = isObjectType(baseType); + let isObjectWithAbstractType = false; - if (!this.config.mappers[baseType.name] && !isUnion && !isInterface && !this._shouldMapType[baseType.name]) { + if (isObject) { + isObjectWithAbstractType = checkIfObjectTypeHasAbstractTypesRecursively(baseType, { + isObjectWithAbstractType, + checkedTypesWithNestedAbstractTypes: this._checkedTypesWithNestedAbstractTypes, + }); + } + + if ( + !this.config.mappers[baseType.name] && + !isUnion && + !isInterface && + !this._shouldMapType[baseType.name] && + !isObjectWithAbstractType + ) { return null; } @@ -1835,3 +1851,84 @@ function normalizeResolversNonOptionalTypename( ...input, }; } + +function checkIfObjectTypeHasAbstractTypesRecursively( + baseType: GraphQLObjectType, + result: { + isObjectWithAbstractType: boolean; + checkedTypesWithNestedAbstractTypes: Record; + } +): boolean { + if ( + result.checkedTypesWithNestedAbstractTypes[baseType.name] && + (result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'yes' || + result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'no') + ) { + return result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'yes'; + } + + result.checkedTypesWithNestedAbstractTypes[baseType.name] ||= { checkStatus: 'checking' }; + + let atLeastOneFieldWithAbstractType = false; + + const fields = baseType.getFields(); + for (const field of Object.values(fields)) { + const fieldBaseType = getBaseType(field.type); + + // If the field is self-referencing, skip it. Otherwise, it's an infinite loop + if (baseType.name === fieldBaseType.name) { + continue; + } + + // If the current field has been checked, and it has nested abstract types, + // mark the parent type as having nested abstract types + if (result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name]) { + if (result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus === 'yes') { + atLeastOneFieldWithAbstractType = true; + result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes'; + } + continue; + } else { + result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name] = { checkStatus: 'checking' }; + } + + // If the field is an abstract type, then both the field type and parent type are abstract types + if (isInterfaceType(fieldBaseType) || isUnionType(fieldBaseType)) { + atLeastOneFieldWithAbstractType = true; + result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'yes'; + result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes'; + continue; + } + + // If the field is an object, check it recursively to see if it has abstract types + // If it does, both field type and parent type have abstract types + if (isObjectType(fieldBaseType)) { + // IMPORTANT: we are pointing the parent type to the field type here + // to make sure when the field type is updated to either 'yes' or 'no', it becomes the parent's type as well + if (result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus === 'checking') { + result.checkedTypesWithNestedAbstractTypes[baseType.name] = + result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name]; + } + + const foundAbstractType = checkIfObjectTypeHasAbstractTypesRecursively(fieldBaseType, result); + if (foundAbstractType) { + atLeastOneFieldWithAbstractType = true; + result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'yes'; + result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'yes'; + } + continue; + } + + // Otherwise, the current field type is not abstract type + // This includes scalar types, enums, input types and objects without abstract types + result.checkedTypesWithNestedAbstractTypes[fieldBaseType.name].checkStatus = 'no'; + } + + if (atLeastOneFieldWithAbstractType) { + result.isObjectWithAbstractType = true; + } else { + result.checkedTypesWithNestedAbstractTypes[baseType.name].checkStatus = 'no'; + } + + return atLeastOneFieldWithAbstractType; +} diff --git a/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap b/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap index 2f7da265854..8783aac2cb1 100644 --- a/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap +++ b/packages/plugins/typescript/resolvers/tests/__snapshots__/ts-resolvers.spec.ts.snap @@ -168,7 +168,7 @@ export type DirectiveResolverFn> = ResolversObject<{ - ChildUnion: ( Child ) | ( MyOtherType ); + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType ); MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } ) | ( MyOtherType ); }>; @@ -184,7 +184,7 @@ export type ResolversInterfaceTypes<_RefType extends Record> = export type ResolversTypes = ResolversObject<{ MyType: ResolverTypeWrapper & { unionChild?: Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper<{}>; @@ -207,7 +207,7 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ MyType: Omit & { unionChild?: Maybe }; String: Scalars['String']['output']; - Child: Child; + Child: Omit & { parent?: Maybe }; MyOtherType: MyOtherType; ChildUnion: ResolversUnionTypes['ChildUnion']; Query: {}; @@ -427,7 +427,7 @@ export type DirectiveResolverFn> = ResolversObject<{ - ChildUnion: ( Types.Child ) | ( Types.MyOtherType ); + ChildUnion: ( Omit & { parent?: Types.Maybe<_RefType['MyType']> } ) | ( Types.MyOtherType ); MyUnion: ( Omit & { unionChild?: Types.Maybe<_RefType['ChildUnion']> } ) | ( Types.MyOtherType ); }>; @@ -443,7 +443,7 @@ export type ResolversInterfaceTypes<_RefType extends Record> = export type ResolversTypes = ResolversObject<{ MyType: ResolverTypeWrapper & { unionChild?: Types.Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Types.Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper<{}>; @@ -466,7 +466,7 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ MyType: Omit & { unionChild?: Types.Maybe }; String: Types.Scalars['String']['output']; - Child: Types.Child; + Child: Omit & { parent?: Types.Maybe }; MyOtherType: Types.MyOtherType; ChildUnion: ResolversUnionTypes['ChildUnion']; Query: {}; @@ -772,7 +772,7 @@ export type DirectiveResolverFn> = ResolversObject<{ - ChildUnion: ( Child ) | ( MyOtherType ); + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType ); MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } ) | ( MyOtherType ); }>; @@ -788,7 +788,7 @@ export type ResolversInterfaceTypes<_RefType extends Record> = export type ResolversTypes = ResolversObject<{ MyType: ResolverTypeWrapper & { unionChild?: Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper<{}>; @@ -811,7 +811,7 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ MyType: Omit & { unionChild?: Maybe }; String: Scalars['String']['output']; - Child: Child; + Child: Omit & { parent?: Maybe }; MyOtherType: MyOtherType; ChildUnion: ResolversUnionTypes['ChildUnion']; Query: {}; diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.config.resolversNonOptionalTypename.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.config.resolversNonOptionalTypename.spec.ts new file mode 100644 index 00000000000..19a23cc3b23 --- /dev/null +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.config.resolversNonOptionalTypename.spec.ts @@ -0,0 +1,237 @@ +import { resolversTestingSchema } from '@graphql-codegen/testing'; +import { plugin } from '../src/index.js'; + +describe('TypeScript Resolvers Plugin - config.resolversNonOptionalTypename', () => { + it('excludes types', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { + unionMember: true, + interfaceImplementingType: true, + excludeTypes: ['ChildUnion', 'AnotherNode', 'Node'], + }, + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversUnionTypes<_RefType extends Record> = { + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType ); + MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + Node: ( SomeNode ); + AnotherNode: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } ); + WithChild: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + }; + `); + }); + + it('adds non-optional typenames to implemented types', async () => { + const result = await plugin(resolversTestingSchema, [], { resolversNonOptionalTypename: true }, { outputFile: '' }); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversUnionTypes<_RefType extends Record> = { + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + }; + `); + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + Node: ( SomeNode & { __typename: 'SomeNode' } ); + AnotherNode: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChild: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversUnionTypes', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { resolversNonOptionalTypename: { unionMember: true } }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversUnionTypes<_RefType extends Record> = { + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversUnionTypes for mappers with no placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { unionMember: true }, + mappers: { Child: 'ChildMapper', MyType: 'MyTypeMapper' }, + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversUnionTypes<_RefType extends Record> = { + ChildUnion: ( ChildMapper & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + MyUnion: ( MyTypeMapper & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversUnionTypes for mappers with placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { unionMember: true }, + mappers: { Child: 'Wrapper<{T}>', MyType: 'MyWrapper<{T}>' }, + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversUnionTypes<_RefType extends Record> = { + ChildUnion: ( Wrapper & { parent?: Maybe<_RefType['MyType']> }> & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + MyUnion: ( MyWrapper & { unionChild?: Maybe<_RefType['ChildUnion']> }> & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversUnionTypes for default mappers with placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { unionMember: true }, + defaultMapper: 'Partial<{T}>', + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversUnionTypes<_RefType extends Record> = { + ChildUnion: ( Partial & { parent?: Maybe<_RefType['MyType']> }> & { __typename: 'Child' } ) | ( Partial & { __typename: 'MyOtherType' } ); + MyUnion: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']> }> & { __typename: 'MyType' } ) | ( Partial & { __typename: 'MyOtherType' } ); + }; + `); + }); + + it('does not create ResolversUnionTypes for default mappers with no placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { unionMember: true }, + defaultMapper: '{}', + }, + { outputFile: '' } + ); + + expect(result.content).not.toBeSimilarStringTo('export type ResolversUnionTypes'); + expect(result.content).not.toBeSimilarStringTo('export type ResolversUnionParentTypes'); + }); + + it('adds non-optional typenames to ResolversInterfaceTypes', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { resolversNonOptionalTypename: { interfaceImplementingType: true } }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + Node: ( SomeNode & { __typename: 'SomeNode' } ); + AnotherNode: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChild: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversInterfaceTypes for mappers with no placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { interfaceImplementingType: true }, + mappers: { AnotherNodeWithChild: 'AnotherNodeWithChildMapper' }, + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + Node: ( SomeNode & { __typename: 'SomeNode' } ); + AnotherNode: ( AnotherNodeWithChildMapper & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChild: ( AnotherNodeWithChildMapper & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversInterfaceTypes for mappers with placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { interfaceImplementingType: true }, + mappers: { AnotherNodeWithChild: 'Wrapper<{T}>' }, + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + Node: ( SomeNode & { __typename: 'SomeNode' } ); + AnotherNode: ( Wrapper & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChild: ( Wrapper & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); + }; + `); + }); + + it('adds non-optional typenames to ResolversInterfaceTypes for default mappers with placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { interfaceImplementingType: true }, + defaultMapper: 'Partial<{T}>', + }, + { outputFile: '' } + ); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + Node: ( Partial & { __typename: 'SomeNode' } ); + AnotherNode: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> }> & { __typename: 'AnotherNodeWithAll' } ); + WithChild: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> }> & { __typename: 'AnotherNodeWithAll' } ); + WithChildren: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> }> & { __typename: 'AnotherNodeWithAll' } ); + }; + `); + }); + + it('does not create ResolversInterfaceTypes for default mappers with no placeholder', async () => { + const result = await plugin( + resolversTestingSchema, + [], + { + resolversNonOptionalTypename: { interfaceImplementingType: true }, + defaultMapper: 'unknown', + }, + { outputFile: '' } + ); + + expect(result.content).not.toBeSimilarStringTo('export type ResolversInterfaceTypes'); + }); +}); diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.interface.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.interface.spec.ts index af71b4bc6ca..2066de7574c 100644 --- a/packages/plugins/typescript/resolvers/tests/ts-resolvers.interface.spec.ts +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.interface.spec.ts @@ -19,7 +19,7 @@ describe('TypeScript Resolvers Plugin - Interfaces', () => { export type ResolversTypes = { MyType: ResolverTypeWrapper & { unionChild?: Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper<{}>; @@ -43,7 +43,7 @@ describe('TypeScript Resolvers Plugin - Interfaces', () => { export type ResolversParentTypes = { MyType: Omit & { unionChild?: Maybe }; String: Scalars['String']['output']; - Child: Child; + Child: Omit & { parent?: Maybe }; MyOtherType: MyOtherType; ChildUnion: ResolversUnionTypes['ChildUnion']; Query: {}; @@ -85,7 +85,7 @@ describe('TypeScript Resolvers Plugin - Interfaces', () => { export type I_ResolversTypes_Types = { MyType: ResolverTypeWrapper & { unionChild?: Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper<{}>; @@ -109,7 +109,7 @@ describe('TypeScript Resolvers Plugin - Interfaces', () => { export type I_ResolversParentTypes_Types = { MyType: Omit & { unionChild?: Maybe }; String: Scalars['String']['output']; - Child: I_Child_Types; + Child: Omit & { parent?: Maybe }; MyOtherType: I_MyOtherType_Types; ChildUnion: I_ResolversUnionTypes_Types['ChildUnion']; Query: {}; @@ -166,4 +166,244 @@ describe('TypeScript Resolvers Plugin - Interfaces', () => { }; `); }); + + it('generates overridden interface types for interfaces wrapped in object types', async () => { + const schema = buildSchema(/* GraphQL */ ` + interface I_Node { + id: ID! + } + + interface I_WithChild { + node: I_Node! + } + + interface I_WithChildren { + nodes: [I_Node!]! + } + + type T_NodeWithChild implements I_Node & I_WithChild { + id: ID! + node: I_Node + } + + type T_NodeWithChildren implements I_Node & I_WithChildren { + id: ID! + nodes: [I_Node!]! + } + + type T_Level1 { + boolean: Boolean! + string: String! + i_node: I_Node! + i_withChild: I_WithChild! + i_withChildren: I_WithChildren! + t_nodeWithChild: T_NodeWithChild! + t_nodeWithChildren: T_NodeWithChildren! + t_self: T_Level1! + t_level2A: T_Level2A! + t_level2B: T_Level2B! + t_nodeWithNoAbstractFieldLevel1: T_WithNoAbstractFieldLevel1! + } + + type T_Level2A { + t_level1: T_Level1! + t_level1Array: [T_Level1!]! + } + + type T_Level2B { + t_level3: T_Level3 + } + + type T_Level3 { + t_level4Array: [T_Level4!]! + } + + type T_Level4 { + node: I_Node! + } + + type T_WithNoAbstractFieldLevel1 { + id: ID! + t_self: [T_WithNoAbstractFieldLevel1!]! + t_withNoAbstractFieldLevel2: T_WithNoAbstractFieldLevel2! + } + + type T_WithNoAbstractFieldLevel2 { + id: ID! + t_withNoAbstractFieldLevel3: T_WithNoAbstractFieldLevel3 + } + + type T_WithNoAbstractFieldLevel3 { + id: ID! + } + + type Query { + level1: T_Level1! + } + `); + + const result = await plugin(schema, [], {}, { outputFile: '' }); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + I_Node: ( Omit & { node?: Maybe<_RefType['I_Node']> } ) | ( Omit & { nodes: Array<_RefType['I_Node']> } ); + I_WithChild: ( Omit & { node?: Maybe<_RefType['I_Node']> } ); + I_WithChildren: ( Omit & { nodes: Array<_RefType['I_Node']> } ); + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversTypes = { + I_Node: ResolverTypeWrapper['I_Node']>; + ID: ResolverTypeWrapper; + I_WithChild: ResolverTypeWrapper['I_WithChild']>; + I_WithChildren: ResolverTypeWrapper['I_WithChildren']>; + T_NodeWithChild: ResolverTypeWrapper & { node?: Maybe }>; + T_NodeWithChildren: ResolverTypeWrapper & { nodes: Array }>; + T_Level1: ResolverTypeWrapper & { i_node: ResolversTypes['I_Node'], i_withChild: ResolversTypes['I_WithChild'], i_withChildren: ResolversTypes['I_WithChildren'], t_nodeWithChild: ResolversTypes['T_NodeWithChild'], t_nodeWithChildren: ResolversTypes['T_NodeWithChildren'], t_self: ResolversTypes['T_Level1'], t_level2A: ResolversTypes['T_Level2A'], t_level2B: ResolversTypes['T_Level2B'] }>; + Boolean: ResolverTypeWrapper; + String: ResolverTypeWrapper; + T_Level2A: ResolverTypeWrapper & { t_level1: ResolversTypes['T_Level1'], t_level1Array: Array }>; + T_Level2B: ResolverTypeWrapper & { t_level3?: Maybe }>; + T_Level3: ResolverTypeWrapper & { t_level4Array: Array }>; + T_Level4: ResolverTypeWrapper & { node: ResolversTypes['I_Node'] }>; + T_WithNoAbstractFieldLevel1: ResolverTypeWrapper; + T_WithNoAbstractFieldLevel2: ResolverTypeWrapper; + T_WithNoAbstractFieldLevel3: ResolverTypeWrapper; + Query: ResolverTypeWrapper<{}>; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversParentTypes = { + I_Node: ResolversInterfaceTypes['I_Node']; + ID: Scalars['ID']['output']; + I_WithChild: ResolversInterfaceTypes['I_WithChild']; + I_WithChildren: ResolversInterfaceTypes['I_WithChildren']; + T_NodeWithChild: Omit & { node?: Maybe }; + T_NodeWithChildren: Omit & { nodes: Array }; + T_Level1: Omit & { i_node: ResolversParentTypes['I_Node'], i_withChild: ResolversParentTypes['I_WithChild'], i_withChildren: ResolversParentTypes['I_WithChildren'], t_nodeWithChild: ResolversParentTypes['T_NodeWithChild'], t_nodeWithChildren: ResolversParentTypes['T_NodeWithChildren'], t_self: ResolversParentTypes['T_Level1'], t_level2A: ResolversParentTypes['T_Level2A'], t_level2B: ResolversParentTypes['T_Level2B'] }; + Boolean: Scalars['Boolean']['output']; + String: Scalars['String']['output']; + T_Level2A: Omit & { t_level1: ResolversParentTypes['T_Level1'], t_level1Array: Array }; + T_Level2B: Omit & { t_level3?: Maybe }; + T_Level3: Omit & { t_level4Array: Array }; + T_Level4: Omit & { node: ResolversParentTypes['I_Node'] }; + T_WithNoAbstractFieldLevel1: T_WithNoAbstractFieldLevel1; + T_WithNoAbstractFieldLevel2: T_WithNoAbstractFieldLevel2; + T_WithNoAbstractFieldLevel3: T_WithNoAbstractFieldLevel3; + Query: {}; + }; + `); + }); + + it('correctly handles circular reference - variant 1', async () => { + const schema = buildSchema(/* GraphQL */ ` + interface I_Node { + id: ID! + } + + type T_WithNode { + node: I_Node! + } + + type T_Type1 { + id: ID! + type2: T_Type2! + withNode: T_WithNode! # abstract type is in T_Type1 + } + + type T_Type2 { + id: ID! + type1: T_Type1! + } + `); + + const result = await plugin(schema, [], {}, { outputFile: '' }); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + I_Node: never; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversTypes = { + I_Node: ResolverTypeWrapper['I_Node']>; + ID: ResolverTypeWrapper; + T_WithNode: ResolverTypeWrapper & { node: ResolversTypes['I_Node'] }>; + T_Type1: ResolverTypeWrapper & { type2: ResolversTypes['T_Type2'], withNode: ResolversTypes['T_WithNode'] }>; + T_Type2: ResolverTypeWrapper & { type1: ResolversTypes['T_Type1'] }>; + Boolean: ResolverTypeWrapper; + String: ResolverTypeWrapper; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversParentTypes = { + I_Node: ResolversInterfaceTypes['I_Node']; + ID: Scalars['ID']['output']; + T_WithNode: Omit & { node: ResolversParentTypes['I_Node'] }; + T_Type1: Omit & { type2: ResolversParentTypes['T_Type2'], withNode: ResolversParentTypes['T_WithNode'] }; + T_Type2: Omit & { type1: ResolversParentTypes['T_Type1'] }; + Boolean: Scalars['Boolean']['output']; + String: Scalars['String']['output']; + }; + `); + }); + + it('correctly handles circular reference - variant 2', async () => { + const schema = buildSchema(/* GraphQL */ ` + interface I_Node { + id: ID! + } + + type T_WithNode { + node: I_Node! + } + + type T_Type1 { + id: ID! + type2: T_Type2! + } + + type T_Type2 { + id: ID! + type1: T_Type1! + withNode: T_WithNode! # abstract type is in T_Type2 + } + `); + + const result = await plugin(schema, [], {}, { outputFile: '' }); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversInterfaceTypes<_RefType extends Record> = { + I_Node: never; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversTypes = { + I_Node: ResolverTypeWrapper['I_Node']>; + ID: ResolverTypeWrapper; + T_WithNode: ResolverTypeWrapper & { node: ResolversTypes['I_Node'] }>; + T_Type1: ResolverTypeWrapper & { type2: ResolversTypes['T_Type2'] }>; + T_Type2: ResolverTypeWrapper & { type1: ResolversTypes['T_Type1'], withNode: ResolversTypes['T_WithNode'] }>; + Boolean: ResolverTypeWrapper; + String: ResolverTypeWrapper; + }; + `); + + expect(result.content).toBeSimilarStringTo(` + export type ResolversParentTypes = { + I_Node: ResolversInterfaceTypes['I_Node']; + ID: Scalars['ID']['output']; + T_WithNode: Omit & { node: ResolversParentTypes['I_Node'] }; + T_Type1: Omit & { type2: ResolversParentTypes['T_Type2'] }; + T_Type2: Omit & { type1: ResolversParentTypes['T_Type1'], withNode: ResolversParentTypes['T_WithNode'] }; + Boolean: Scalars['Boolean']['output']; + String: Scalars['String']['output']; + }; + `); + }); }); diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.mapping.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.mapping.spec.ts index 0a7ef291458..c595ebe7041 100644 --- a/packages/plugins/typescript/resolvers/tests/ts-resolvers.mapping.spec.ts +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.mapping.spec.ts @@ -9,7 +9,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { expect(result.content).toBeSimilarStringTo(` export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Child ) | ( MyOtherType ); + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType ); MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } ) | ( MyOtherType ); }; `); @@ -25,7 +25,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversTypes = { MyType: ResolverTypeWrapper & { unionChild?: Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper<{}>; @@ -48,7 +48,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversParentTypes = { MyType: Omit & { unionChild?: Maybe }; String: Scalars['String']['output']; - Child: Child; + Child: Omit & { parent?: Maybe }; MyOtherType: MyOtherType; ChildUnion: ResolversUnionTypes['ChildUnion']; Query: {}; @@ -392,7 +392,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { expect(result.content).toBeSimilarStringTo(` export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Partial ) | ( Partial ); + ChildUnion: ( Partial & { parent?: Maybe<_RefType['MyType']> }> ) | ( Partial ); MyUnion: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']> }> ) | ( Partial ); }; `); @@ -408,7 +408,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversTypes = { MyType: ResolverTypeWrapper & { unionChild?: Maybe }>>; String: ResolverTypeWrapper>; - Child: ResolverTypeWrapper>; + Child: ResolverTypeWrapper & { parent?: Maybe }>>; MyOtherType: ResolverTypeWrapper>; ChildUnion: Partial['ChildUnion']>>; Query: ResolverTypeWrapper<{}>; @@ -430,7 +430,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversParentTypes = { MyType: Partial & { unionChild?: Maybe }>; String: Partial; - Child: Partial; + Child: Partial & { parent?: Maybe }>; MyOtherType: Partial; ChildUnion: Partial['ChildUnion']>; Query: {}; @@ -465,7 +465,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { expect(result.prepend).toContain(`import { CustomPartial } from './my-wrapper';`); expect(result.content).toBeSimilarStringTo(` export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( CustomPartial ) | ( CustomPartial ); + ChildUnion: ( CustomPartial & { parent?: Maybe<_RefType['MyType']> }> ) | ( CustomPartial ); MyUnion: ( CustomPartial & { unionChild?: Maybe<_RefType['ChildUnion']> }> ) | ( CustomPartial ); } `); @@ -481,7 +481,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversTypes = { MyType: ResolverTypeWrapper & { unionChild?: Maybe }>>; String: ResolverTypeWrapper>; - Child: ResolverTypeWrapper>; + Child: ResolverTypeWrapper & { parent?: Maybe }>>; MyOtherType: ResolverTypeWrapper>; ChildUnion: CustomPartial['ChildUnion']>>; Query: ResolverTypeWrapper<{}>; @@ -503,7 +503,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversParentTypes = { MyType: CustomPartial & { unionChild?: Maybe }>; String: CustomPartial; - Child: CustomPartial; + Child: CustomPartial & { parent?: Maybe }>; MyOtherType: CustomPartial; ChildUnion: CustomPartial['ChildUnion']>; Query: {}; @@ -1919,7 +1919,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { expect(result.prepend).toContain(`import { MyNamespace } from './my-file';`); expect(result.content).toBeSimilarStringTo(` export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Child ) | ( MyOtherType ); + ChildUnion: ( Omit & { parent?: Maybe<_RefType['MyType']> } ) | ( MyOtherType ); MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } ) | ( MyOtherType ); }; `); @@ -1935,7 +1935,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversTypes = { MyType: ResolverTypeWrapper & { unionChild?: Maybe }>; String: ResolverTypeWrapper; - Child: ResolverTypeWrapper; + Child: ResolverTypeWrapper & { parent?: Maybe }>; MyOtherType: ResolverTypeWrapper; ChildUnion: ResolverTypeWrapper['ChildUnion']>; Query: ResolverTypeWrapper; @@ -1959,7 +1959,7 @@ describe('TypeScript Resolvers Plugin - Mapping', () => { export type ResolversParentTypes = { MyType: Omit & { unionChild?: Maybe }; String: Scalars['String']['output']; - Child: Child; + Child: Omit & { parent?: Maybe }; MyOtherType: MyOtherType; ChildUnion: ResolversUnionTypes['ChildUnion']; Query: MyNamespace.MyRootType; diff --git a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts index 156a8984d48..f67889c55f6 100644 --- a/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts +++ b/packages/plugins/typescript/resolvers/tests/ts-resolvers.spec.ts @@ -224,244 +224,6 @@ export type MyTypeResolvers { - const result = await plugin( - resolversTestingSchema, - [], - { resolversNonOptionalTypename: true }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Child & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - }; - `); - expect(result.content).toBeSimilarStringTo(` - export type ResolversInterfaceTypes<_RefType extends Record> = { - Node: ( SomeNode & { __typename: 'SomeNode' } ); - AnotherNode: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChild: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversUnionTypes', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { resolversNonOptionalTypename: { unionMember: true } }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Child & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversUnionTypes for mappers with no placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { unionMember: true }, - mappers: { Child: 'ChildMapper', MyType: 'MyTypeMapper' }, - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( ChildMapper & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - MyUnion: ( MyTypeMapper & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversUnionTypes for mappers with placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { unionMember: true }, - mappers: { Child: 'Wrapper<{T}>', MyType: 'MyWrapper<{T}>' }, - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Wrapper & { parent?: Maybe<_RefType['MyType']> }> & { __typename: 'Child' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - MyUnion: ( MyWrapper & { unionChild?: Maybe<_RefType['ChildUnion']> }> & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversUnionTypes for default mappers with placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { unionMember: true }, - defaultMapper: 'Partial<{T}>', - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Partial & { __typename: 'Child' } ) | ( Partial & { __typename: 'MyOtherType' } ); - MyUnion: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']> }> & { __typename: 'MyType' } ) | ( Partial & { __typename: 'MyOtherType' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - does not create ResolversUnionTypes for default mappers with no placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { unionMember: true }, - defaultMapper: '{}', - }, - { outputFile: '' } - ); - - expect(result.content).not.toBeSimilarStringTo('export type ResolversUnionTypes'); - expect(result.content).not.toBeSimilarStringTo('export type ResolversUnionParentTypes'); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversInterfaceTypes', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { resolversNonOptionalTypename: { interfaceImplementingType: true } }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversInterfaceTypes<_RefType extends Record> = { - Node: ( SomeNode & { __typename: 'SomeNode' } ); - AnotherNode: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChild: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversInterfaceTypes for mappers with no placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { interfaceImplementingType: true }, - mappers: { AnotherNodeWithChild: 'AnotherNodeWithChildMapper' }, - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversInterfaceTypes<_RefType extends Record> = { - Node: ( SomeNode & { __typename: 'SomeNode' } ); - AnotherNode: ( AnotherNodeWithChildMapper & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChild: ( AnotherNodeWithChildMapper & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversInterfaceTypes for mappers with placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { interfaceImplementingType: true }, - mappers: { AnotherNodeWithChild: 'Wrapper<{T}>' }, - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversInterfaceTypes<_RefType extends Record> = { - Node: ( SomeNode & { __typename: 'SomeNode' } ); - AnotherNode: ( Wrapper & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChild: ( Wrapper & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - adds non-optional typenames to ResolversInterfaceTypes for default mappers with placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { interfaceImplementingType: true }, - defaultMapper: 'Partial<{T}>', - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversInterfaceTypes<_RefType extends Record> = { - Node: ( Partial & { __typename: 'SomeNode' } ); - AnotherNode: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> }> & { __typename: 'AnotherNodeWithAll' } ); - WithChild: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> }> & { __typename: 'AnotherNodeWithChild' } ) | ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> }> & { __typename: 'AnotherNodeWithAll' } ); - WithChildren: ( Partial & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> }> & { __typename: 'AnotherNodeWithAll' } ); - }; - `); - }); - - it('resolversNonOptionalTypename - does not create ResolversInterfaceTypes for default mappers with no placeholder', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { interfaceImplementingType: true }, - defaultMapper: 'unknown', - }, - { outputFile: '' } - ); - - expect(result.content).not.toBeSimilarStringTo('export type ResolversInterfaceTypes'); - }); - - it('resolversNonOptionalTypename - excludes types', async () => { - const result = await plugin( - resolversTestingSchema, - [], - { - resolversNonOptionalTypename: { - unionMember: true, - interfaceImplementingType: true, - excludeTypes: ['ChildUnion', 'AnotherNode', 'Node'], - }, - }, - { outputFile: '' } - ); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversUnionTypes<_RefType extends Record> = { - ChildUnion: ( Child ) | ( MyOtherType ); - MyUnion: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']> } & { __typename: 'MyType' } ) | ( MyOtherType & { __typename: 'MyOtherType' } ); - }; - `); - - expect(result.content).toBeSimilarStringTo(` - export type ResolversInterfaceTypes<_RefType extends Record> = { - Node: ( SomeNode ); - AnotherNode: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } ); - WithChild: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']> } & { __typename: 'AnotherNodeWithChild' } ) | ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - WithChildren: ( Omit & { unionChild?: Maybe<_RefType['ChildUnion']>, unionChildren: Array<_RefType['ChildUnion']>, interfaceChild?: Maybe<_RefType['Node']>, interfaceChildren: Array<_RefType['Node']> } & { __typename: 'AnotherNodeWithAll' } ); - }; - `); - }); }); it('directiveResolverMappings - should generate correct types (import definition)', async () => {