From 3c442db7d9d9d5b4da1e31edfdc3cb6481f673bd Mon Sep 17 00:00:00 2001 From: Aggelos Arvanitakis Date: Tue, 14 Jul 2020 23:19:29 +0300 Subject: [PATCH] feat: add `scalars` config option for custom GraphQL [scalar -> casual] mappings (#21) Some apps use custom scalar for date-related stuff. This caused issues with the way `graphql-codegen-typescript-mock-data` handles autogenerated data, since a "random string" (the current default), may not always be what you need. This PR allows you to define mapping from your custom scalars to a casual embedded generator, allowing you to specify exactly how your scalar's autogenerated value will be populated Closes #20 Co-authored-by: Corentin Ardeois --- README.md | 12 +++ src/index.ts | 34 ++++++++- .../typescript-mock-data.spec.ts.snap | 74 +++++++++++++++++++ tests/typescript-mock-data.spec.ts | 16 ++++ 4 files changed, 132 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 63d68c0..0abeb98 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ Changes the case of the enums. Accepts `upper-case#upperCase`, `pascal-case#pasc Changes the case of the enums. Accepts `upper-case#upperCase`, `pascal-case#pascalCase` or `keep` +### scalars (`{ [Scalar: string]: keyof Casual.Casual | Casual.functions }`, defaultValue: `undefined`) + +Allows you to define mappings for your custom scalars. Allows you to map any GraphQL Scalar to a + [casual](https://github.com/boo1ean/casual#embedded-generators) embedded generator (string or + function key) + ## Example of usage **codegen.yml** @@ -48,6 +54,8 @@ generates: typesFile: '../generated-types.ts' enumValues: upper-case#upperCase typenames: keep + scalars: + AWSTimestamp: unix_time # gets translated to casual.unix_time ``` ## Example or generated code @@ -55,6 +63,8 @@ generates: Given the following schema: ```graphql +scalar AWSTimestamp + type Avatar { id: ID! url: String! @@ -65,6 +75,7 @@ type User { login: String! avatar: Avatar status: Status! + updatedAt: AWSTimestamp } type Query { @@ -111,6 +122,7 @@ export const aUser = (overrides?: Partial): User => { login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero', avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(), status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online, + updatedAt: overrides && overrides.hasOwnProperty('updatedAt') ? overrides.updatedAt! : 1458071232, }; }; ``` diff --git a/src/index.ts b/src/index.ts index d1902b8..5dc9a0c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,6 +64,7 @@ const getNamedType = ( enumValuesConvention: NamingConvention, prefix?: string, namedType?: NamedTypeNode, + customScalars?: ScalarMap, ): string | number | boolean => { if (!namedType) { return ''; @@ -105,10 +106,26 @@ const getNamedType = ( prefix, foundType.types && foundType.types[0], ); - case 'scalar': - // it's a scalar, let's use a string as a value. - // This could be improved with a custom scalar definition in the config - return `'${casual.word}'`; + case 'scalar': { + // it's a scalar, let's use a string as a value if there is no custom + // mapping for this particular scalar + if (!customScalars || !customScalars[foundType.name]) { + return `'${casual.word}'`; + } + + // If there is a mapping to a `casual` type, then use it and make sure + // to call it if it's a function + const embeddedGenerator = casual[customScalars[foundType.name]]; + const value = typeof embeddedGenerator === 'function' ? embeddedGenerator() : embeddedGenerator; + + if (typeof value === 'string') { + return `'${value}'`; + } + if (typeof value === 'object') { + return `${JSON.stringify(value)}`; + } + return value; + } default: throw `foundType is unknown: ${foundType.name}: ${foundType.type}`; } @@ -126,6 +143,7 @@ const generateMockValue = ( enumValuesConvention: NamingConvention, prefix: string | undefined, currentType: TypeNode, + customScalars: ScalarMap, ): string | number | boolean => { switch (currentType.kind) { case 'NamedType': @@ -137,6 +155,7 @@ const generateMockValue = ( enumValuesConvention, prefix, currentType as NamedTypeNode, + customScalars, ); case 'NonNullType': return generateMockValue( @@ -147,6 +166,7 @@ const generateMockValue = ( enumValuesConvention, prefix, currentType.type, + customScalars, ); case 'ListType': { const value = generateMockValue( @@ -157,6 +177,7 @@ const generateMockValue = ( enumValuesConvention, prefix, currentType.type, + customScalars, ); return `[${value}]`; } @@ -180,12 +201,15 @@ ${fields} };`; }; +type ScalarMap = { [name: string]: keyof (Casual.Casual | Casual.functions) }; + export interface TypescriptMocksPluginConfig { typesFile?: string; enumValues?: NamingConvention; typenames?: NamingConvention; addTypename?: boolean; prefix?: string; + scalars?: ScalarMap; } interface TypeItem { @@ -243,6 +267,7 @@ export const plugin: PluginFunction = (schema, docu enumValuesConvention, config.prefix, node.type, + config.scalars, ); return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`; @@ -266,6 +291,7 @@ export const plugin: PluginFunction = (schema, docu enumValuesConvention, config.prefix, field.type, + config.scalars, ); return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`; diff --git a/tests/__snapshots__/typescript-mock-data.spec.ts.snap b/tests/__snapshots__/typescript-mock-data.spec.ts.snap index 908a141..696a39d 100644 --- a/tests/__snapshots__/typescript-mock-data.spec.ts.snap +++ b/tests/__snapshots__/typescript-mock-data.spec.ts.snap @@ -37,6 +37,80 @@ export const mockUser = (overrides?: Partial): User => { " `; +exports[`should correctly generate the \`casual\` data for a non-string scalar mapping 1`] = ` +" +export const anAbcType = (overrides?: Partial): AbcType => { + return { + abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit', + }; +}; + +export const anAvatar = (overrides?: Partial): Avatar => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '0550ff93-dd31-49b4-8c38-ff1cb68bdc38', + url: overrides && overrides.hasOwnProperty('url') ? overrides.url! : 'aliquid', + }; +}; + +export const aUpdateUserInput = (overrides?: Partial): UpdateUserInput => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '1d6a9360-c92b-4660-8e5f-04155047bddc', + login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'qui', + avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(), + }; +}; + +export const aUser = (overrides?: Partial): User => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'a5756f00-41a6-422a-8a7d-d13ee6a63750', + creationDate: overrides && overrides.hasOwnProperty('creationDate') ? overrides.creationDate! : '1970-01-09T16:33:21.532Z', + login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero', + avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(), + status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online, + customStatus: overrides && overrides.hasOwnProperty('customStatus') ? overrides.customStatus! : AbcStatus.HasXyzStatus, + scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : [41,98,185], + }; +}; +" +`; + +exports[`should correctly generate the \`casual\` data for a scalar mapping of type string 1`] = ` +" +export const anAbcType = (overrides?: Partial): AbcType => { + return { + abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit', + }; +}; + +export const anAvatar = (overrides?: Partial): Avatar => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '0550ff93-dd31-49b4-8c38-ff1cb68bdc38', + url: overrides && overrides.hasOwnProperty('url') ? overrides.url! : 'aliquid', + }; +}; + +export const aUpdateUserInput = (overrides?: Partial): UpdateUserInput => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '1d6a9360-c92b-4660-8e5f-04155047bddc', + login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'qui', + avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(), + }; +}; + +export const aUser = (overrides?: Partial): User => { + return { + id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'a5756f00-41a6-422a-8a7d-d13ee6a63750', + creationDate: overrides && overrides.hasOwnProperty('creationDate') ? overrides.creationDate! : '1970-01-09T16:33:21.532Z', + login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero', + avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(), + status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online, + customStatus: overrides && overrides.hasOwnProperty('customStatus') ? overrides.customStatus! : AbcStatus.HasXyzStatus, + scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : 'Mohamed.Nader@Kiehn.io', + }; +}; +" +`; + exports[`should generate mock data functions 1`] = ` " export const anAbcType = (overrides?: Partial): AbcType => { diff --git a/tests/typescript-mock-data.spec.ts b/tests/typescript-mock-data.spec.ts index 1a7d02b..cc03005 100644 --- a/tests/typescript-mock-data.spec.ts +++ b/tests/typescript-mock-data.spec.ts @@ -177,3 +177,19 @@ it('should add custom prefix if the `prefix` config option is specified', async expect(result).not.toMatch(/const aUser/); expect(result).toMatchSnapshot(); }); + +it('should correctly generate the `casual` data for a scalar mapping of type string', async () => { + const result = await plugin(testSchema, [], { scalars: { AnyObject: 'email' } }); + + expect(result).toBeDefined(); + expect(result).toContain('Mohamed.Nader@Kiehn.io'); + expect(result).toMatchSnapshot(); +}); + +it('should correctly generate the `casual` data for a non-string scalar mapping', async () => { + const result = await plugin(testSchema, [], { scalars: { AnyObject: 'rgb_array' } }); + + expect(result).toBeDefined(); + expect(result).toContain(JSON.stringify([41, 98, 185])); + expect(result).toMatchSnapshot(); +});