From 69fd16cd30a89b31f0e2f7a5abb173d646ff8dab Mon Sep 17 00:00:00 2001 From: "s.vanriessen" Date: Mon, 16 Sep 2024 11:11:18 +0200 Subject: [PATCH 1/2] feat: rename types in config --- src/index.ts | 24 ++++++++++++++++--- .../useTypeImports/__snapshots__/spec.ts.snap | 8 ++++++- tests/useTypeImports/schema.ts | 4 ++++ tests/useTypeImports/spec.ts | 15 +++++++++++- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index a986971..12d025b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -443,10 +443,12 @@ const getMockString = ( prefix, typesPrefix = '', transformUnderscore: boolean, + newTypeNames?: Record, ) => { const typeNameConverter = createNameConverter(typeNamesConvention, transformUnderscore); + const NewTypeName = newTypeNames[typeName] || typeName; const casedName = typeNameConverter(typeName); - const casedNameWithPrefix = typeNameConverter(typeName, typesPrefix); + const casedNameWithPrefix = typeNameConverter(NewTypeName, typesPrefix); const typename = addTypename ? `\n __typename: '${typeName}',` : ''; const typenameReturnType = addTypename ? `{ __typename: '${typeName}' } & ` : ''; @@ -489,6 +491,7 @@ const getImportTypes = ({ transformUnderscore, enumsAsTypes, useTypeImports, + newTypeNames, }: { typeNamesConvention: NamingConvention; definitions: any; @@ -499,6 +502,7 @@ const getImportTypes = ({ transformUnderscore: boolean; enumsAsTypes: boolean; useTypeImports: boolean; + newTypeNames?: Record; }) => { const typenameConverter = createNameConverter(typeNamesConvention, transformUnderscore); const typeImports = typesPrefix?.endsWith('.') @@ -506,12 +510,18 @@ const getImportTypes = ({ : definitions .filter(({ typeName }: { typeName: string }) => !!typeName) .map(({ typeName }: { typeName: string }) => typenameConverter(typeName, typesPrefix)); + const renamedTypeImports = typeImports.map((type) => { + if (newTypeNames[type]) { + return `${type} as ${newTypeNames[type]}`; + } + return type; + }); const enumTypes = enumsPrefix?.endsWith('.') ? [enumsPrefix.slice(0, -1)] : types.filter(({ type }) => type === 'enum').map(({ name }) => typenameConverter(name, enumsPrefix)); if (!enumsAsTypes || useTypeImports) { - typeImports.push(...enumTypes); + renamedTypeImports.push(...enumTypes); } function onlyUnique(value, index, self) { @@ -520,7 +530,9 @@ const getImportTypes = ({ const importPrefix = `import ${useTypeImports ? 'type ' : ''}`; - return typesFile ? `${importPrefix}{ ${typeImports.filter(onlyUnique).join(', ')} } from '${typesFile}';\n` : ''; + return typesFile + ? `${importPrefix}{ ${renamedTypeImports.filter(onlyUnique).join(', ')} } from '${typesFile}';\n` + : ''; }; type GeneratorName = keyof Casual.Casual | keyof Casual.functions | string; @@ -564,6 +576,7 @@ export interface TypescriptMocksPluginConfig { useImplementingTypes?: boolean; defaultNullableToNull?: boolean; useTypeImports?: boolean; + newTypeNames?: Record; } interface TypeItem { @@ -614,6 +627,7 @@ export const plugin: PluginFunction = (schema, docu const useImplementingTypes = config.useImplementingTypes ?? false; const defaultNullableToNull = config.defaultNullableToNull ?? false; const generatorLocale = config.locale || 'en'; + const newTypeNames = config.newTypeNames || {}; // List of types that are enums const types: TypeItem[] = []; @@ -747,6 +761,7 @@ export const plugin: PluginFunction = (schema, docu config.prefix, config.typesPrefix, transformUnderscore, + newTypeNames, ); }, }; @@ -770,6 +785,7 @@ export const plugin: PluginFunction = (schema, docu config.prefix, config.typesPrefix, transformUnderscore, + newTypeNames, ); }, }; @@ -791,6 +807,7 @@ export const plugin: PluginFunction = (schema, docu config.prefix, config.typesPrefix, transformUnderscore, + newTypeNames, ); }, }; @@ -813,6 +830,7 @@ export const plugin: PluginFunction = (schema, docu transformUnderscore: transformUnderscore, useTypeImports: config.useTypeImports, enumsAsTypes, + newTypeNames, }); // Function that will generate the mocks. // We generate it after having visited because we need to distinct types from enums diff --git a/tests/useTypeImports/__snapshots__/spec.ts.snap b/tests/useTypeImports/__snapshots__/spec.ts.snap index fed422e..645a7fe 100644 --- a/tests/useTypeImports/__snapshots__/spec.ts.snap +++ b/tests/useTypeImports/__snapshots__/spec.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should support useTypeImports 1`] = ` -"import type { Avatar, User, WithAvatar, CamelCaseThing, PrefixedResponse, AbcType, ListType, UpdateUserInput, Mutation, Query, AbcStatus, Status, PrefixedEnum } from './types/graphql'; +"import type { Avatar, User, Partial, WithAvatar, CamelCaseThing, PrefixedResponse, AbcType, ListType, UpdateUserInput, Mutation, Query, AbcStatus, Status, PrefixedEnum } from './types/graphql'; export const anAvatar = (overrides?: Partial): Avatar => { return { @@ -25,6 +25,12 @@ export const aUser = (overrides?: Partial): User => { }; }; +export const aPartial = (overrides?: Partial): Partial => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '262c8866-bf76-4ccf-b606-2a0b4742f81f', + }; +}; + export const aWithAvatar = (overrides?: Partial): WithAvatar => { return { id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '99f515e7-21e0-461d-b823-0d4c7f4dafc5', diff --git a/tests/useTypeImports/schema.ts b/tests/useTypeImports/schema.ts index 8e1ac4e..3484d14 100644 --- a/tests/useTypeImports/schema.ts +++ b/tests/useTypeImports/schema.ts @@ -22,6 +22,10 @@ export default buildSchema(/* GraphQL */ ` prefixedEnum: Prefixed_Enum } + type Partial { + id: ID! + } + interface WithAvatar { id: ID! avatar: Avatar diff --git a/tests/useTypeImports/spec.ts b/tests/useTypeImports/spec.ts index e98259c..d222204 100644 --- a/tests/useTypeImports/spec.ts +++ b/tests/useTypeImports/spec.ts @@ -6,7 +6,20 @@ it('should support useTypeImports', async () => { expect(result).toBeDefined(); expect(result).toContain( - "import type { Avatar, User, WithAvatar, CamelCaseThing, PrefixedResponse, AbcType, ListType, UpdateUserInput, Mutation, Query, AbcStatus, Status, PrefixedEnum } from './types/graphql';", + "import type { Avatar, User, Partial, WithAvatar, CamelCaseThing, PrefixedResponse, AbcType, ListType, UpdateUserInput, Mutation, Query, AbcStatus, Status, PrefixedEnum } from './types/graphql';", ); expect(result).toMatchSnapshot(); }); + +it('should support useTypeImports', async () => { + const result = await plugin(testSchema, [], { + typesFile: './types/graphql.ts', + useTypeImports: true, + newTypeNames: { Partial: 'RenamedPartial' }, + }); + + expect(result).toBeDefined(); + expect(result).toContain( + "import type { Avatar, User, Partial as RenamedPartial, WithAvatar, CamelCaseThing, PrefixedResponse, AbcType, ListType, UpdateUserInput, Mutation, Query, AbcStatus, Status, PrefixedEnum } from './types/graphql';", + ); +}); From b27e199fc16ee2feef63b229f901769f046a0ffd Mon Sep 17 00:00:00 2001 From: "s.vanriessen" Date: Fri, 20 Sep 2024 14:25:31 +0200 Subject: [PATCH 2/2] chore: add more tests and docs --- README.md | 20 +++++++++++++ src/index.ts | 51 +++++++++++++++++++++----------- tests/typeNamesMapping/schema.ts | 31 +++++++++++++++++++ tests/typeNamesMapping/spec.ts | 36 ++++++++++++++++++++++ tests/useTypeImports/spec.ts | 13 -------- 5 files changed, 120 insertions(+), 31 deletions(-) create mode 100644 tests/typeNamesMapping/schema.ts create mode 100644 tests/typeNamesMapping/spec.ts diff --git a/README.md b/README.md index 19ee1d6..e7045ed 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,26 @@ export const aUser = (overrides?: Partial): User => { } ``` +### typeNamesMapping (`{ [typeName: string]: string }`, defaultValue: `{}`) + +Allows you to define mappings to rename the types. This is useful when you want to override the generated type name. For example, if you have a type called `User` and you want to rename it to `RenamedUser` you can do the following: + +``` +plugins: + - typescript-mock-data: + typesFile: '../generated-types.ts' + typeNamesMapping: + User: RenamedUser +``` + +This will generate the following mock function: + +``` +export const aUser = (overrides?: Partial): RenamedUser => { +``` + +**Note:** It is not possible to rename your enums using this option. + ### transformUnderscore (`boolean`, defaultValue: `true`) When disabled, underscores will be retained for type names when the case is changed. It has no effect if `typeNames` is set to `keep`. diff --git a/src/index.ts b/src/index.ts index 12d025b..53f88fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,6 +39,7 @@ type Options = { useImplementingTypes: boolean; defaultNullableToNull: boolean; nonNull: boolean; + typeNamesMapping?: Record; }; const getTerminateCircularRelationshipsConfig = ({ terminateCircularRelationships }: TypescriptMocksPluginConfig) => @@ -64,6 +65,15 @@ const createNameConverter = return `${prefix}${convertName(value, resolveExternalModuleAndFn(convention), transformUnderscore)}`; }; +const renameImports = (list: string[], typeNamesMapping: Record) => { + return list.map((type) => { + if (typeNamesMapping && typeNamesMapping[type]) { + return `${type} as ${typeNamesMapping[type]}`; + } + return type; + }); +}; + const toMockName = (typedName: string, casedName: string, prefix?: string) => { if (prefix) { return `${prefix}${casedName}`; @@ -380,14 +390,20 @@ const getNamedType = (opts: Options): opts.typeNamesConvention, opts.transformUnderscore, ); - const casedNameWithPrefix = typeNameConverter(name, opts.typesPrefix); + const renamedType = renameImports([name], opts.typeNamesMapping)[0]; + const casedNameWithPrefix = typeNameConverter(renamedType, opts.typesPrefix); return `relationshipsToOmit.has('${casedName}') ? {} as ${casedNameWithPrefix} : ${toMockName( name, casedName, opts.prefix, )}({}, relationshipsToOmit)`; } else { - return `relationshipsToOmit.has('${casedName}') ? {} as ${casedName} : ${toMockName( + const renamedType = renameImports([name], opts.typeNamesMapping)[0]; + const renamedCasedName = createNameConverter( + opts.typeNamesConvention, + opts.transformUnderscore, + )(renamedType); + return `relationshipsToOmit.has('${casedName}') ? {} as ${renamedCasedName} : ${toMockName( name, casedName, opts.prefix, @@ -443,10 +459,10 @@ const getMockString = ( prefix, typesPrefix = '', transformUnderscore: boolean, - newTypeNames?: Record, + typeNamesMapping?: Record, ) => { const typeNameConverter = createNameConverter(typeNamesConvention, transformUnderscore); - const NewTypeName = newTypeNames[typeName] || typeName; + const NewTypeName = typeNamesMapping[typeName] || typeName; const casedName = typeNameConverter(typeName); const casedNameWithPrefix = typeNameConverter(NewTypeName, typesPrefix); const typename = addTypename ? `\n __typename: '${typeName}',` : ''; @@ -491,7 +507,7 @@ const getImportTypes = ({ transformUnderscore, enumsAsTypes, useTypeImports, - newTypeNames, + typeNamesMapping, }: { typeNamesConvention: NamingConvention; definitions: any; @@ -502,7 +518,7 @@ const getImportTypes = ({ transformUnderscore: boolean; enumsAsTypes: boolean; useTypeImports: boolean; - newTypeNames?: Record; + typeNamesMapping?: Record; }) => { const typenameConverter = createNameConverter(typeNamesConvention, transformUnderscore); const typeImports = typesPrefix?.endsWith('.') @@ -510,16 +526,13 @@ const getImportTypes = ({ : definitions .filter(({ typeName }: { typeName: string }) => !!typeName) .map(({ typeName }: { typeName: string }) => typenameConverter(typeName, typesPrefix)); - const renamedTypeImports = typeImports.map((type) => { - if (newTypeNames[type]) { - return `${type} as ${newTypeNames[type]}`; - } - return type; - }); + const enumTypes = enumsPrefix?.endsWith('.') ? [enumsPrefix.slice(0, -1)] : types.filter(({ type }) => type === 'enum').map(({ name }) => typenameConverter(name, enumsPrefix)); + const renamedTypeImports = renameImports(typeImports, typeNamesMapping); + if (!enumsAsTypes || useTypeImports) { renamedTypeImports.push(...enumTypes); } @@ -576,7 +589,7 @@ export interface TypescriptMocksPluginConfig { useImplementingTypes?: boolean; defaultNullableToNull?: boolean; useTypeImports?: boolean; - newTypeNames?: Record; + typeNamesMapping?: Record; } interface TypeItem { @@ -627,7 +640,7 @@ export const plugin: PluginFunction = (schema, docu const useImplementingTypes = config.useImplementingTypes ?? false; const defaultNullableToNull = config.defaultNullableToNull ?? false; const generatorLocale = config.locale || 'en'; - const newTypeNames = config.newTypeNames || {}; + const typeNamesMapping = config.typeNamesMapping || {}; // List of types that are enums const types: TypeItem[] = []; @@ -707,6 +720,7 @@ export const plugin: PluginFunction = (schema, docu useImplementingTypes, defaultNullableToNull, nonNull: false, + typeNamesMapping: config.typeNamesMapping, }); return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`; @@ -745,6 +759,7 @@ export const plugin: PluginFunction = (schema, docu useImplementingTypes, defaultNullableToNull, nonNull: false, + typeNamesMapping: config.typeNamesMapping, }); return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`; @@ -761,7 +776,7 @@ export const plugin: PluginFunction = (schema, docu config.prefix, config.typesPrefix, transformUnderscore, - newTypeNames, + typeNamesMapping, ); }, }; @@ -785,7 +800,7 @@ export const plugin: PluginFunction = (schema, docu config.prefix, config.typesPrefix, transformUnderscore, - newTypeNames, + typeNamesMapping, ); }, }; @@ -807,7 +822,7 @@ export const plugin: PluginFunction = (schema, docu config.prefix, config.typesPrefix, transformUnderscore, - newTypeNames, + typeNamesMapping, ); }, }; @@ -830,7 +845,7 @@ export const plugin: PluginFunction = (schema, docu transformUnderscore: transformUnderscore, useTypeImports: config.useTypeImports, enumsAsTypes, - newTypeNames, + typeNamesMapping, }); // Function that will generate the mocks. // We generate it after having visited because we need to distinct types from enums diff --git a/tests/typeNamesMapping/schema.ts b/tests/typeNamesMapping/schema.ts new file mode 100644 index 0000000..a7770e2 --- /dev/null +++ b/tests/typeNamesMapping/schema.ts @@ -0,0 +1,31 @@ +import { buildSchema } from 'graphql/index'; + +export default buildSchema(/* GraphQL */ ` + enum EnumExample { + LOREM + IPSUM + } + + type A { + id: ID! + str: String! + email: String! + } + + type B { + id: ID! + str: String! + email: String! + } + + type C { + id: ID! + str: String! + enum: EnumExample! + D: D! + } + + type D { + nested: C! + } +`); diff --git a/tests/typeNamesMapping/spec.ts b/tests/typeNamesMapping/spec.ts new file mode 100644 index 0000000..2363c37 --- /dev/null +++ b/tests/typeNamesMapping/spec.ts @@ -0,0 +1,36 @@ +import { plugin } from '../../src'; +import testSchema from './schema'; + +it('should support typeNamesMapping', async () => { + const result = await plugin(testSchema, [], { + typesFile: './types/graphql.ts', + typeNamesMapping: { A: 'RenamedAType' }, + }); + + expect(result).toBeDefined(); + expect(result).toContain("import { A as RenamedAType, B, C, D, EnumExample } from './types/graphql';"); +}); + +it('should support typeNamesMapping with circular relationships', async () => { + const result = await plugin(testSchema, [], { + typesFile: './types/graphql.ts', + typeNamesMapping: { D: 'RenamedDType' }, + terminateCircularRelationships: 'immediate', + }); + + expect(result).toBeDefined(); + expect(result).toContain("import { A, B, C, D as RenamedDType, EnumExample } from './types/graphql';"); + expect(result).toContain( + "D: overrides && overrides.hasOwnProperty('D') ? overrides.D! : relationshipsToOmit.has('D') ? {} as DAsRenamedDType : aD({}, relationshipsToOmit),", + ); +}); + +it('should not support typeNamesMapping when enum type is given', async () => { + const result = await plugin(testSchema, [], { + typesFile: './types/graphql.ts', + typeNamesMapping: { EnumExample: 'RenamedEnum' }, + }); + + expect(result).toBeDefined(); + expect(result).toContain("import { A, B, C, D, EnumExample } from './types/graphql';"); +}); diff --git a/tests/useTypeImports/spec.ts b/tests/useTypeImports/spec.ts index d222204..8888e64 100644 --- a/tests/useTypeImports/spec.ts +++ b/tests/useTypeImports/spec.ts @@ -10,16 +10,3 @@ it('should support useTypeImports', async () => { ); expect(result).toMatchSnapshot(); }); - -it('should support useTypeImports', async () => { - const result = await plugin(testSchema, [], { - typesFile: './types/graphql.ts', - useTypeImports: true, - newTypeNames: { Partial: 'RenamedPartial' }, - }); - - expect(result).toBeDefined(); - expect(result).toContain( - "import type { Avatar, User, Partial as RenamedPartial, WithAvatar, CamelCaseThing, PrefixedResponse, AbcType, ListType, UpdateUserInput, Mutation, Query, AbcStatus, Status, PrefixedEnum } from './types/graphql';", - ); -});