Skip to content

Commit

Permalink
feat: add scalars config option for custom GraphQL [scalar -> casua…
Browse files Browse the repository at this point in the history
…l] 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 <[email protected]>
  • Loading branch information
3nvi and ardeois authored Jul 14, 2020
1 parent b743244 commit 3c442db
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 4 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand All @@ -48,13 +54,17 @@ 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
Given the following schema:
```graphql
scalar AWSTimestamp

type Avatar {
id: ID!
url: String!
Expand All @@ -65,6 +75,7 @@ type User {
login: String!
avatar: Avatar
status: Status!
updatedAt: AWSTimestamp
}

type Query {
Expand Down Expand Up @@ -111,6 +122,7 @@ export const aUser = (overrides?: Partial<User>): 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,
};
};
```
Expand Down
34 changes: 30 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const getNamedType = (
enumValuesConvention: NamingConvention,
prefix?: string,
namedType?: NamedTypeNode,
customScalars?: ScalarMap,
): string | number | boolean => {
if (!namedType) {
return '';
Expand Down Expand Up @@ -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}`;
}
Expand All @@ -126,6 +143,7 @@ const generateMockValue = (
enumValuesConvention: NamingConvention,
prefix: string | undefined,
currentType: TypeNode,
customScalars: ScalarMap,
): string | number | boolean => {
switch (currentType.kind) {
case 'NamedType':
Expand All @@ -137,6 +155,7 @@ const generateMockValue = (
enumValuesConvention,
prefix,
currentType as NamedTypeNode,
customScalars,
);
case 'NonNullType':
return generateMockValue(
Expand All @@ -147,6 +166,7 @@ const generateMockValue = (
enumValuesConvention,
prefix,
currentType.type,
customScalars,
);
case 'ListType': {
const value = generateMockValue(
Expand All @@ -157,6 +177,7 @@ const generateMockValue = (
enumValuesConvention,
prefix,
currentType.type,
customScalars,
);
return `[${value}]`;
}
Expand All @@ -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 {
Expand Down Expand Up @@ -243,6 +267,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
enumValuesConvention,
config.prefix,
node.type,
config.scalars,
);

return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`;
Expand All @@ -266,6 +291,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
enumValuesConvention,
config.prefix,
field.type,
config.scalars,
);

return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`;
Expand Down
74 changes: 74 additions & 0 deletions tests/__snapshots__/typescript-mock-data.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,80 @@ export const mockUser = (overrides?: Partial<User>): User => {
"
`;
exports[`should correctly generate the \`casual\` data for a non-string scalar mapping 1`] = `
"
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {
return {
abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit',
};
};
export const anAvatar = (overrides?: Partial<Avatar>): 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>): 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>): 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>): AbcType => {
return {
abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit',
};
};
export const anAvatar = (overrides?: Partial<Avatar>): 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>): 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>): 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! : '[email protected]',
};
};
"
`;
exports[`should generate mock data functions 1`] = `
"
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {
Expand Down
16 changes: 16 additions & 0 deletions tests/typescript-mock-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('[email protected]');
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();
});

0 comments on commit 3c442db

Please sign in to comment.