From 4b4a8cb937fd0443b20800a285160b96c9aec459 Mon Sep 17 00:00:00 2001 From: Aggelos Arvanitakis Date: Mon, 13 Jul 2020 16:20:09 +0300 Subject: [PATCH 1/5] feat: add `scalars` config option for custom GraphQL [scalar -> casual] mappings --- README.md | 12 ++++++ src/index.ts | 22 +++++++++-- .../typescript-mock-data.spec.ts.snap | 37 +++++++++++++++++++ tests/typescript-mock-data.spec.ts | 9 +++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe5c4d8..c6006e1 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,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** @@ -43,6 +49,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 @@ -50,6 +58,8 @@ generates: Given the following schema: ```graphql +scalar AWSTimestamp + type Avatar { id: ID! url: String! @@ -60,6 +70,7 @@ type User { login: String! avatar: Avatar status: Status! + updatedAt: AWSTimestamp } type Query { @@ -106,6 +117,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 38cb6a5..42e033c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -60,6 +60,7 @@ const getNamedType = ( typenamesConvention: NamingConvention, enumValuesConvention: NamingConvention, namedType?: NamedTypeNode, + customScalars?: ScalarMap, ): string | number | boolean => { if (!namedType) { return ''; @@ -101,9 +102,15 @@ const getNamedType = ( 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}'`; + // 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 this + const value = casual[customScalars[foundType.name]]; + return typeof value === 'function' ? value() : value; default: throw `foundType is unknown: ${foundType.name}: ${foundType.type}`; } @@ -120,6 +127,7 @@ const generateMockValue = ( typenamesConvention: NamingConvention, enumValuesConvention: NamingConvention, currentType: TypeNode, + customScalars: ScalarMap, ): string | number | boolean => { switch (currentType.kind) { case 'NamedType': @@ -130,6 +138,7 @@ const generateMockValue = ( typenamesConvention, enumValuesConvention, currentType as NamedTypeNode, + customScalars, ); case 'NonNullType': return generateMockValue( @@ -139,6 +148,7 @@ const generateMockValue = ( typenamesConvention, enumValuesConvention, currentType.type, + customScalars, ); case 'ListType': { const value = generateMockValue( @@ -148,6 +158,7 @@ const generateMockValue = ( typenamesConvention, enumValuesConvention, currentType.type, + customScalars, ); return `[${value}]`; } @@ -170,11 +181,14 @@ ${fields} };`; }; +type ScalarMap = { [name: string]: keyof (Casual.Casual | Casual.functions) }; + export interface TypescriptMocksPluginConfig { typesFile?: string; enumValues?: NamingConvention; typenames?: NamingConvention; addTypename?: boolean; + scalars?: ScalarMap; } interface TypeItem { @@ -231,6 +245,7 @@ export const plugin: PluginFunction = (schema, docu typenamesConvention, enumValuesConvention, node.type, + config.scalars, ); return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`; @@ -253,6 +268,7 @@ export const plugin: PluginFunction = (schema, docu typenamesConvention, enumValuesConvention, 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 e6c94b0..3422ab8 100644 --- a/tests/__snapshots__/typescript-mock-data.spec.ts.snap +++ b/tests/__snapshots__/typescript-mock-data.spec.ts.snap @@ -448,3 +448,40 @@ export const aUSER = (overrides?: Partial): USER => { }; " `; + +exports[`should generate the \`casual\` data for a particular 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! : Mohamed.Nader@Kiehn.io, + }; +}; +" +`; diff --git a/tests/typescript-mock-data.spec.ts b/tests/typescript-mock-data.spec.ts index 58460f5..32f4deb 100644 --- a/tests/typescript-mock-data.spec.ts +++ b/tests/typescript-mock-data.spec.ts @@ -168,3 +168,12 @@ it('should generate mock data with as-is types and enums if typenames is "keep"' expect(result).not.toMatch(/ABC(TYPE|STATUS)/); expect(result).toMatchSnapshot(); }); + +it('should generate the `casual` data for a particular scalar mapping', async () => { + const result = await plugin(testSchema, [], { scalars: { AnyObject: 'email' } }); + + const emailRegex = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/; + expect(result).toBeDefined(); + expect(emailRegex.test(result as string)).toBeTruthy(); + expect(result).toMatchSnapshot(); +}); From 026ff20e865abd5be88d1adfee26756b2f290298 Mon Sep 17 00:00:00 2001 From: Aggelos Arvanitakis Date: Mon, 13 Jul 2020 20:06:41 +0300 Subject: [PATCH 2/5] fix: issue with stringification --- src/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 42e033c..7a8583b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -109,8 +109,9 @@ const getNamedType = ( } // If there is a mapping to a `casual` type, then use this - const value = casual[customScalars[foundType.name]]; - return typeof value === 'function' ? value() : value; + const embeddedGenerator = casual[customScalars[foundType.name]]; + const value = typeof embeddedGenerator === 'function' ? embeddedGenerator() : embeddedGenerator; + return typeof value === 'string' ? `'${value}'` : value; default: throw `foundType is unknown: ${foundType.name}: ${foundType.type}`; } From a3511d4a629bedb2a231dc74197ee166e315ad1c Mon Sep 17 00:00:00 2001 From: Aggelos Arvanitakis Date: Mon, 13 Jul 2020 20:33:42 +0300 Subject: [PATCH 3/5] test: update snapshots --- tests/__snapshots__/typescript-mock-data.spec.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/__snapshots__/typescript-mock-data.spec.ts.snap b/tests/__snapshots__/typescript-mock-data.spec.ts.snap index 3422ab8..02092c3 100644 --- a/tests/__snapshots__/typescript-mock-data.spec.ts.snap +++ b/tests/__snapshots__/typescript-mock-data.spec.ts.snap @@ -480,7 +480,7 @@ export const aUser = (overrides?: Partial): User => { 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, + scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : 'Mohamed.Nader@Kiehn.io', }; }; " From b43a92e5b172f8d035419270cbc65acf80dd12cb Mon Sep 17 00:00:00 2001 From: Aggelos Arvanitakis Date: Mon, 13 Jul 2020 22:56:54 +0300 Subject: [PATCH 4/5] test: test non-string scalar & cleanup test assertions --- .../typescript-mock-data.spec.ts.snap | 111 ++++++++++++------ tests/typescript-mock-data.spec.ts | 13 +- 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/tests/__snapshots__/typescript-mock-data.spec.ts.snap b/tests/__snapshots__/typescript-mock-data.spec.ts.snap index 02092c3..977c2f3 100644 --- a/tests/__snapshots__/typescript-mock-data.spec.ts.snap +++ b/tests/__snapshots__/typescript-mock-data.spec.ts.snap @@ -1,5 +1,79 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +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! : 'America/Maceio', + }; +}; +" +`; + +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 => { @@ -448,40 +522,3 @@ export const aUSER = (overrides?: Partial): USER => { }; " `; - -exports[`should generate the \`casual\` data for a particular 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! : 'Mohamed.Nader@Kiehn.io', - }; -}; -" -`; diff --git a/tests/typescript-mock-data.spec.ts b/tests/typescript-mock-data.spec.ts index 32f4deb..3e7e252 100644 --- a/tests/typescript-mock-data.spec.ts +++ b/tests/typescript-mock-data.spec.ts @@ -169,11 +169,18 @@ it('should generate mock data with as-is types and enums if typenames is "keep"' expect(result).toMatchSnapshot(); }); -it('should generate the `casual` data for a particular scalar mapping', async () => { +it('should correctly generate the `casual` data for a scalar mapping of type string', async () => { const result = await plugin(testSchema, [], { scalars: { AnyObject: 'email' } }); - const emailRegex = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/; expect(result).toBeDefined(); - expect(emailRegex.test(result as string)).toBeTruthy(); + 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: 'timezone' } }); + + expect(result).toBeDefined(); + expect(result).toContain('America/Maceio'); expect(result).toMatchSnapshot(); }); From 125167875946d3007f6703c8ef05fe5f9d95e297 Mon Sep 17 00:00:00 2001 From: Aggelos Arvanitakis Date: Mon, 13 Jul 2020 23:19:08 +0300 Subject: [PATCH 5/5] fix: handle case of arrays and objects --- src/index.ts | 12 ++++++++++-- .../__snapshots__/typescript-mock-data.spec.ts.snap | 2 +- tests/typescript-mock-data.spec.ts | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7a8583b..e97b4fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -108,10 +108,18 @@ const getNamedType = ( return `'${casual.word}'`; } - // If there is a mapping to a `casual` type, then use this + // 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; - return typeof value === 'string' ? `'${value}'` : value; + + 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}`; } diff --git a/tests/__snapshots__/typescript-mock-data.spec.ts.snap b/tests/__snapshots__/typescript-mock-data.spec.ts.snap index 977c2f3..fb1f5eb 100644 --- a/tests/__snapshots__/typescript-mock-data.spec.ts.snap +++ b/tests/__snapshots__/typescript-mock-data.spec.ts.snap @@ -31,7 +31,7 @@ export const aUser = (overrides?: Partial): User => { 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! : 'America/Maceio', + scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : [41,98,185], }; }; " diff --git a/tests/typescript-mock-data.spec.ts b/tests/typescript-mock-data.spec.ts index 3e7e252..ecb2b21 100644 --- a/tests/typescript-mock-data.spec.ts +++ b/tests/typescript-mock-data.spec.ts @@ -178,9 +178,9 @@ it('should correctly generate the `casual` data for a scalar mapping of type str }); it('should correctly generate the `casual` data for a non-string scalar mapping', async () => { - const result = await plugin(testSchema, [], { scalars: { AnyObject: 'timezone' } }); + const result = await plugin(testSchema, [], { scalars: { AnyObject: 'rgb_array' } }); expect(result).toBeDefined(); - expect(result).toContain('America/Maceio'); + expect(result).toContain(JSON.stringify([41, 98, 185])); expect(result).toMatchSnapshot(); });