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: Customized Prefixing #23

Closed
wants to merge 4 commits into from
Closed
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ Defines the file path containing all GraphQL types. This file can also be genera

Adds `__typename` property to mock data

### prefix (`string`, defaultValue: `a` for constants & `an` for vowels)

The prefix to add to the mock function name. Cannot be empty since it will clash with the associated
typescript definition from `@graphql-codegen/typescript`

### enumValues (`string`, defaultValue: `pascal-case#pascalCase`)

Changes the case of the enums. Accepts `upper-case#upperCase`, `pascal-case#pascalCase` or `keep`
Expand All @@ -26,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 @@ -43,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 @@ -60,6 +75,7 @@ type User {
login: String!
avatar: Avatar
status: Status!
updatedAt: AWSTimestamp
}

type Query {
Expand Down Expand Up @@ -106,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
51 changes: 43 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ const createNameConverter = (convention: NamingConvention) => (value: string) =>
}
};

const toMockName = (name: string) => {
const toMockName = (name: string, prefix?: string) => {
if (prefix) {
return `${prefix}${name}`;
}
const isVowel = name.match(/^[AEIO]/);
return isVowel ? `an${name}` : `a${name}`;
};
Expand Down Expand Up @@ -59,7 +62,9 @@ const getNamedType = (
types: TypeItem[],
typenamesConvention: NamingConvention,
enumValuesConvention: NamingConvention,
prefix?: string,
namedType?: NamedTypeNode,
customScalars?: ScalarMap,
): string | number | boolean => {
if (!namedType) {
return '';
Expand Down Expand Up @@ -98,17 +103,24 @@ const getNamedType = (
types,
typenamesConvention,
enumValuesConvention,
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}'`;
// 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}`;
}
}
return `${toMockName(name)}()`;
return `${toMockName(name, prefix)}()`;
}
}
};
Expand All @@ -119,7 +131,9 @@ const generateMockValue = (
types: TypeItem[],
typenamesConvention: NamingConvention,
enumValuesConvention: NamingConvention,
prefix: string | undefined,
currentType: TypeNode,
customScalars: ScalarMap,
): string | number | boolean => {
switch (currentType.kind) {
case 'NamedType':
Expand All @@ -129,7 +143,9 @@ const generateMockValue = (
types,
typenamesConvention,
enumValuesConvention,
prefix,
currentType as NamedTypeNode,
customScalars,
);
case 'NonNullType':
return generateMockValue(
Expand All @@ -138,7 +154,9 @@ const generateMockValue = (
types,
typenamesConvention,
enumValuesConvention,
prefix,
currentType.type,
customScalars,
);
case 'ListType': {
const value = generateMockValue(
Expand All @@ -147,7 +165,9 @@ const generateMockValue = (
types,
typenamesConvention,
enumValuesConvention,
prefix,
currentType.type,
customScalars,
);
return `[${value}]`;
}
Expand All @@ -159,22 +179,27 @@ const getMockString = (
fields: string,
typenamesConvention: NamingConvention,
addTypename = false,
prefix,
) => {
const casedName = createNameConverter(typenamesConvention)(typeName);
const typename = addTypename ? `\n __typename: '${casedName}',` : '';
return `
export const ${toMockName(casedName)} = (overrides?: Partial<${casedName}>): ${casedName} => {
export const ${toMockName(casedName, prefix)} = (overrides?: Partial<${casedName}>): ${casedName} => {
return {${typename}
${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 @@ -230,7 +255,9 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
types,
typenamesConvention,
enumValuesConvention,
config.prefix,
node.type,
config.scalars,
);

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

return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`;
})
.join('\n')
: '';

return getMockString(fieldName, mockFields, typenamesConvention, false);
return getMockString(fieldName, mockFields, typenamesConvention, false, config.prefix);
},
};
},
Expand All @@ -278,7 +307,13 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
mockFn: () => {
const mockFields = fields ? fields.map(({ mockFn }: any) => mockFn(typeName)).join('\n') : '';

return getMockString(typeName, mockFields, typenamesConvention, !!config.addTypename);
return getMockString(
typeName,
mockFields,
typenamesConvention,
!!config.addTypename,
config.prefix,
);
},
};
},
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
@@ -1,5 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should add custom prefix if the \`prefix\` config option is specified 1`] = `
"
export const mockAbcType = (overrides?: Partial<AbcType>): AbcType => {
return {
abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit',
};
};

export const mockAvatar = (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 mockUpdateUserInput = (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! : mockAvatar(),
};
};

export const mockUser = (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! : mockAvatar(),
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! : 'neque',
};
};
"
`;

exports[`should generate mock data functions 1`] = `
"
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {
Expand Down Expand Up @@ -448,3 +485,40 @@ export const aUSER = (overrides?: Partial<USER>): USER => {
};
"
`;

exports[`should generate the \`casual\` data for a particular 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! : [email protected],
};
};
"
`;
18 changes: 18 additions & 0 deletions tests/typescript-mock-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,21 @@ 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 add custom prefix if the `prefix` config option is specified', async () => {
const result = await plugin(testSchema, [], { prefix: 'mock' });

expect(result).toBeDefined();
expect(result).toMatch(/const mockUser/);
expect(result).not.toMatch(/const aUser/);
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();
});