diff --git a/packages/plugins/typescript/operations/src/index.ts b/packages/plugins/typescript/operations/src/index.ts index 8dd2c1b3439..3e1497bc8f2 100644 --- a/packages/plugins/typescript/operations/src/index.ts +++ b/packages/plugins/typescript/operations/src/index.ts @@ -63,6 +63,7 @@ export const plugin: PluginFunction = { [K in keyof T]: T[K] };', ], diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index 3e4ae45692b..b8bc1f324f7 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -167,6 +167,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< ) ); this._declarationBlockConfig = { + enumNameValueSeparator: ' =', ignoreExport: this.config.noExport, }; } @@ -245,4 +246,58 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< return usedInputTypes; } + + // TODO: share with base-types-visitor + public getEnumsImports(): string[] { + return Object.keys(this.config.enumValues) + .flatMap(enumName => { + const mappedValue = this.config.enumValues[enumName]; + + if (mappedValue.sourceFile) { + if (mappedValue.isDefault) { + return [this._buildTypeImport(mappedValue.typeIdentifier, mappedValue.sourceFile, true)]; + } + + return this.handleEnumValueMapper( + mappedValue.typeIdentifier, + mappedValue.importIdentifier, + mappedValue.sourceIdentifier, + mappedValue.sourceFile + ); + } + + return []; + }) + .filter(Boolean); + } + protected _buildTypeImport(identifier: string, source: string, asDefault = false): string { + const { useTypeImports } = this.config; + if (asDefault) { + if (useTypeImports) { + return `import type { default as ${identifier} } from '${source}';`; + } + return `import ${identifier} from '${source}';`; + } + return `import${useTypeImports ? ' type' : ''} { ${identifier} } from '${source}';`; + } + + protected handleEnumValueMapper( + typeIdentifier: string, + importIdentifier: string | null, + sourceIdentifier: string | null, + sourceFile: string | null + ): string[] { + if (importIdentifier !== sourceIdentifier) { + // use namespace import to dereference nested enum + // { enumValues: { MyEnum: './my-file#NS.NestedEnum' } } + return [ + this._buildTypeImport(importIdentifier || sourceIdentifier, sourceFile), + `import ${typeIdentifier} = ${sourceIdentifier};`, + ]; + } + if (sourceIdentifier !== typeIdentifier) { + return [this._buildTypeImport(`${sourceIdentifier} as ${typeIdentifier}`, sourceFile)]; + } + return [this._buildTypeImport(importIdentifier || sourceIdentifier, sourceFile)]; + } } diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts new file mode 100644 index 00000000000..992d3b85e1d --- /dev/null +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.enum.spec.ts @@ -0,0 +1,1246 @@ +import { mergeOutputs } from '@graphql-codegen/plugin-helpers'; +import { validateTs } from '@graphql-codegen/testing'; +import { buildSchema, parse } from 'graphql'; +import { plugin } from '../src/index.js'; + +describe('TypeScript Operations Plugin - Enum', () => { + it('does not generate enums if not used in variables and result', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], {})]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type MeQueryVariables = Exact<{ [key: string]: never; }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `native-numeric` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-numeric' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + Admin = 0, + Customer = 1 + } + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `const` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + A_B_C + X_Y_Z + _TEST + My_Value + _123 + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'const' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export const UserRole = { + ABC: 'A_B_C', + XYZ: 'X_Y_Z', + Test: '_TEST', + MyValue: 'My_Value', + '123': '_123' + } as const; + + export type UserRole = typeof UserRole[keyof typeof UserRole]; + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `native-const` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + """ + Multiline comment test + """ + enum UserRole { + ADMIN + CUSTOMER @deprecated(reason: "Enum value CUSTOMER has been deprecated.") + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native-const' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + /** Multiline comment test */ + export const enum UserRole { + Admin = 'ADMIN', + /** @deprecated Enum value CUSTOMER has been deprecated. */ + Customer = 'CUSTOMER' + }; + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `native` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + Admin = 'ADMIN', + Customer = 'CUSTOMER' + } + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` with `string-literal` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + A_B_C + X_Y_Z + _TEST + My_Value + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumType: 'string-literal', + enumValues: { + UserRole: { + A_B_C: 0, + X_Y_Z: 'Foo', + _TEST: 'Bar', + My_Value: 1, + }, + }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserRole = + | 0 + | 'Foo' + | 'Bar' + | 1; + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` with `const` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + A_B_C + X_Y_Z + _TEST + My_Value + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumType: 'const', + enumValues: { + UserRole: { + A_B_C: 0, + X_Y_Z: 'Foo', + _TEST: 'Bar', + My_Value: 1, + }, + }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export const UserRole = { + ABC: 0, + XYZ: 'Foo', + Test: 'Bar', + MyValue: 1 + } as const; + + export type UserRole = typeof UserRole[keyof typeof UserRole]; + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` with `native` enum', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumType: 'native', + enumValues: { + UserRole: { + ADMIN: 0, + CUSTOMER: 'test', + }, + }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + Admin = 0, + Customer = 'test' + } + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` as file import', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumValues: { + UserRole: './my-file#MyEnum', + }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "import { MyEnum as UserRole } from './my-file'; + type Exact = { [K in keyof T]: T[K] }; + export { UserRole }; + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` with custom imported enum from namespace with different name', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumValues: { + UserRole: './my-file#NS.ETest', + }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "import { NS } from './my-file'; + import UserRole = NS.ETest; + type Exact = { [K in keyof T]: T[K] }; + export { UserRole }; + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` with custom imported enum from namespace with the same name', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumValues: { + UserRole: './my-file#NS.UserRole', + }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "import { NS } from './my-file'; + import UserRole = NS.UserRole; + type Exact = { [K in keyof T]: T[K] }; + export { UserRole }; + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` from a single file', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + enum UserStatus { + ACTIVE + PENDING + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!, $status: UserStatus!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumType: 'native', + enumValues: './my-file', + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "import { UserRole } from './my-file'; + import { UserStatus } from './my-file'; + type Exact = { [K in keyof T]: T[K] }; + export { UserRole }; + + export { UserStatus }; + + export type MeQueryVariables = Exact<{ + role: UserRole; + status: UserStatus; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('handles `enumValues` from a single file when specified as string', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + enum UserStatus { + ACTIVE + PENDING + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!, $status: UserStatus!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumType: 'native', + enumValues: { UserRole: './my-file#UserRole', UserStatus: './my-file#UserStatus2X' }, + }), + ]); + + expect(result).toMatchInlineSnapshot(` + "import { UserRole } from './my-file'; + import { UserStatus2X as UserStatus } from './my-file'; + type Exact = { [K in keyof T]: T[K] }; + export { UserRole }; + + export { UserStatus }; + + export type MeQueryVariables = Exact<{ + role: UserRole; + status: UserStatus; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('removes underscore from enum values', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + A_B_C + X_Y_Z + _TEST + My_Value + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + ABC = 'A_B_C', + XYZ = 'X_Y_Z', + Test = '_TEST', + MyValue = 'My_Value' + } + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('keeps underscores in enum values when the value is only underscores', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + _ + __ + _TEST + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { enumType: 'native' })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + _ = '_', + __ = '__', + Test = '_TEST' + } + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('adds typesPrefix to enum when enumPrefix is true', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { typesPrefix: 'I', enumPrefix: true })]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type IUserRole = + | 'ADMIN' + | 'CUSTOMER'; + + export type IMeQueryVariables = Exact<{ + role: IUserRole; + }>; + + + export type IMeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('does not add typesPrefix to enum when enumPrefix is false', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { typesPrefix: 'I', enumPrefix: false })]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserRole = + | 'ADMIN' + | 'CUSTOMER'; + + export type IMeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type IMeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('adds typesSuffix to enum when enumSuffix is true', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { typesSuffix: 'Z', enumSuffix: true })]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserRoleZ = + | 'ADMIN' + | 'CUSTOMER'; + + export type MeQueryVariablesZ = Exact<{ + role: UserRoleZ; + }>; + + + export type MeQueryZ = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('does not add typesSuffix to enum when enumSuffix is false', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { typesSuffix: 'Z', enumSuffix: false })]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserRole = + | 'ADMIN' + | 'CUSTOMER'; + + export type MeQueryVariablesZ = Exact<{ + role: UserRole; + }>; + + + export type MeQueryZ = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('keeps enum value naming convention when namingConvention.enumValues is `keep`', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + namingConvention: { + typeNames: 'change-case-all#lowerCase', + enumValues: 'keep', + }, + }), + ]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type userrole = + | 'ADMIN' + | 'CUSTOMER'; + + export type mequeryvariables = Exact<{ + role: userrole; + }>; + + + export type mequery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('uses custom enum naming convention when namingConvention.enumValues is provided and enumType is native', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + enumType: 'native', + namingConvention: { + typeNames: 'keep', + enumValues: 'change-case-all#lowerCase', + }, + }), + ]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export enum UserRole { + admin = 'ADMIN', + customer = 'CUSTOMER' + } + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); + + it('does not contain "export" when noExport is set to true', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([ + await plugin(schema, [{ document }], { + noExport: true, + }), + ]); + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + type UserRole = + | 'ADMIN' + | 'CUSTOMER'; + + type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); +}); + +describe('TypeScript Operations Plugin - Enum `%future added value`', () => { + it('adds `%future added value` to the type when enumType is `string-literal` and futureProofEnums is true', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + me: User + } + + type User { + id: ID! + name: String! + role: UserRole! + createdAt: DateTime! + } + + enum UserRole { + ADMIN + CUSTOMER + } + + scalar DateTime + `); + const document = parse(/* GraphQL */ ` + query Me($role: UserRole!) { + me { + id + } + } + `); + + const result = mergeOutputs([await plugin(schema, [{ document }], { futureProofEnums: true })]); + + expect(result).toMatchInlineSnapshot(` + "type Exact = { [K in keyof T]: T[K] }; + export type UserRole = + | 'ADMIN' + | 'CUSTOMER' + | '%future added value'; + + export type MeQueryVariables = Exact<{ + role: UserRole; + }>; + + + export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null }; + " + `); + + validateTs(result, undefined, undefined, undefined, undefined, true); + }); +}); diff --git a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts index 06dd1752443..f3106cf72a1 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.standalone.spec.ts @@ -29,13 +29,19 @@ describe('TypeScript Operations Plugin - Standalone', () => { createdAt: DateTime! } + "UserRole Description" enum UserRole { + "UserRole ADMIN" ADMIN + "UserRole CUSTOMER" CUSTOMER } + "UsersInput Description" input UsersInput { + "UsersInput from" from: DateTime + "UsersInput to" to: DateTime role: UserRole } @@ -88,8 +94,11 @@ describe('TypeScript Operations Plugin - Standalone', () => { expect(result).toMatchInlineSnapshot(` "type Exact = { [K in keyof T]: T[K] }; + /** UserRole Description */ export type UserRole = + /** UserRole ADMIN */ | 'ADMIN' + /** UserRole CUSTOMER */ | 'CUSTOMER'; export type UserQueryVariables = Exact<{ @@ -163,13 +172,3 @@ describe('TypeScript Operations Plugin - Standalone', () => { `); }); }); - -describe('TypeScript Operations Plugin - Enum', () => { - it.todo('does not generate unused enum in variables and result'); - it.todo('handles native numeric enum correctly'); - it.todo('handles const enum correctly'); - it.todo('handles native const enum correctly'); - it.todo('handles native enum correctly'); - it.todo('handles EnumValues correctly'); - // Bring over tests from https://github.com/dotansimha/graphql-code-generator/blob/accdab69106605241933e9d66d64dc7077656f30/packages/plugins/typescript/typescript/tests/typescript.spec.ts -});