Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add scalars config option for custom GraphQL [scalar -> casual] mappings #21

Merged
merged 6 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]];
ardeois marked this conversation as resolved.
Show resolved Hide resolved
const value = typeof embeddedGenerator === 'function' ? embeddedGenerator() : embeddedGenerator;

if (typeof value === 'string') {
return `'${value}'`;
}
if (typeof value === 'object') {
return `${JSON.stringify(value)}`;
ardeois marked this conversation as resolved.
Show resolved Hide resolved
}
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();
});
ardeois marked this conversation as resolved.
Show resolved Hide resolved