Skip to content

Commit 3c442db

Browse files
3nviardeois
andauthored
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 <[email protected]>
1 parent b743244 commit 3c442db

File tree

4 files changed

+132
-4
lines changed

4 files changed

+132
-4
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ Changes the case of the enums. Accepts `upper-case#upperCase`, `pascal-case#pasc
3131

3232
Changes the case of the enums. Accepts `upper-case#upperCase`, `pascal-case#pascalCase` or `keep`
3333

34+
### scalars (`{ [Scalar: string]: keyof Casual.Casual | Casual.functions }`, defaultValue: `undefined`)
35+
36+
Allows you to define mappings for your custom scalars. Allows you to map any GraphQL Scalar to a
37+
[casual](https://github.com/boo1ean/casual#embedded-generators) embedded generator (string or
38+
function key)
39+
3440
## Example of usage
3541

3642
**codegen.yml**
@@ -48,13 +54,17 @@ generates:
4854
typesFile: '../generated-types.ts'
4955
enumValues: upper-case#upperCase
5056
typenames: keep
57+
scalars:
58+
AWSTimestamp: unix_time # gets translated to casual.unix_time
5159
```
5260
5361
## Example or generated code
5462
5563
Given the following schema:
5664
5765
```graphql
66+
scalar AWSTimestamp
67+
5868
type Avatar {
5969
id: ID!
6070
url: String!
@@ -65,6 +75,7 @@ type User {
6575
login: String!
6676
avatar: Avatar
6777
status: Status!
78+
updatedAt: AWSTimestamp
6879
}
6980

7081
type Query {
@@ -111,6 +122,7 @@ export const aUser = (overrides?: Partial<User>): User => {
111122
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero',
112123
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
113124
status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online,
125+
updatedAt: overrides && overrides.hasOwnProperty('updatedAt') ? overrides.updatedAt! : 1458071232,
114126
};
115127
};
116128
```

src/index.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const getNamedType = (
6464
enumValuesConvention: NamingConvention,
6565
prefix?: string,
6666
namedType?: NamedTypeNode,
67+
customScalars?: ScalarMap,
6768
): string | number | boolean => {
6869
if (!namedType) {
6970
return '';
@@ -105,10 +106,26 @@ const getNamedType = (
105106
prefix,
106107
foundType.types && foundType.types[0],
107108
);
108-
case 'scalar':
109-
// it's a scalar, let's use a string as a value.
110-
// This could be improved with a custom scalar definition in the config
111-
return `'${casual.word}'`;
109+
case 'scalar': {
110+
// it's a scalar, let's use a string as a value if there is no custom
111+
// mapping for this particular scalar
112+
if (!customScalars || !customScalars[foundType.name]) {
113+
return `'${casual.word}'`;
114+
}
115+
116+
// If there is a mapping to a `casual` type, then use it and make sure
117+
// to call it if it's a function
118+
const embeddedGenerator = casual[customScalars[foundType.name]];
119+
const value = typeof embeddedGenerator === 'function' ? embeddedGenerator() : embeddedGenerator;
120+
121+
if (typeof value === 'string') {
122+
return `'${value}'`;
123+
}
124+
if (typeof value === 'object') {
125+
return `${JSON.stringify(value)}`;
126+
}
127+
return value;
128+
}
112129
default:
113130
throw `foundType is unknown: ${foundType.name}: ${foundType.type}`;
114131
}
@@ -126,6 +143,7 @@ const generateMockValue = (
126143
enumValuesConvention: NamingConvention,
127144
prefix: string | undefined,
128145
currentType: TypeNode,
146+
customScalars: ScalarMap,
129147
): string | number | boolean => {
130148
switch (currentType.kind) {
131149
case 'NamedType':
@@ -137,6 +155,7 @@ const generateMockValue = (
137155
enumValuesConvention,
138156
prefix,
139157
currentType as NamedTypeNode,
158+
customScalars,
140159
);
141160
case 'NonNullType':
142161
return generateMockValue(
@@ -147,6 +166,7 @@ const generateMockValue = (
147166
enumValuesConvention,
148167
prefix,
149168
currentType.type,
169+
customScalars,
150170
);
151171
case 'ListType': {
152172
const value = generateMockValue(
@@ -157,6 +177,7 @@ const generateMockValue = (
157177
enumValuesConvention,
158178
prefix,
159179
currentType.type,
180+
customScalars,
160181
);
161182
return `[${value}]`;
162183
}
@@ -180,12 +201,15 @@ ${fields}
180201
};`;
181202
};
182203

204+
type ScalarMap = { [name: string]: keyof (Casual.Casual | Casual.functions) };
205+
183206
export interface TypescriptMocksPluginConfig {
184207
typesFile?: string;
185208
enumValues?: NamingConvention;
186209
typenames?: NamingConvention;
187210
addTypename?: boolean;
188211
prefix?: string;
212+
scalars?: ScalarMap;
189213
}
190214

191215
interface TypeItem {
@@ -243,6 +267,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
243267
enumValuesConvention,
244268
config.prefix,
245269
node.type,
270+
config.scalars,
246271
);
247272

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

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

tests/__snapshots__/typescript-mock-data.spec.ts.snap

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,80 @@ export const mockUser = (overrides?: Partial<User>): User => {
3737
"
3838
`;
3939
40+
exports[`should correctly generate the \`casual\` data for a non-string scalar mapping 1`] = `
41+
"
42+
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {
43+
return {
44+
abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit',
45+
};
46+
};
47+
48+
export const anAvatar = (overrides?: Partial<Avatar>): Avatar => {
49+
return {
50+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '0550ff93-dd31-49b4-8c38-ff1cb68bdc38',
51+
url: overrides && overrides.hasOwnProperty('url') ? overrides.url! : 'aliquid',
52+
};
53+
};
54+
55+
export const aUpdateUserInput = (overrides?: Partial<UpdateUserInput>): UpdateUserInput => {
56+
return {
57+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '1d6a9360-c92b-4660-8e5f-04155047bddc',
58+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'qui',
59+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
60+
};
61+
};
62+
63+
export const aUser = (overrides?: Partial<User>): User => {
64+
return {
65+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'a5756f00-41a6-422a-8a7d-d13ee6a63750',
66+
creationDate: overrides && overrides.hasOwnProperty('creationDate') ? overrides.creationDate! : '1970-01-09T16:33:21.532Z',
67+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero',
68+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
69+
status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online,
70+
customStatus: overrides && overrides.hasOwnProperty('customStatus') ? overrides.customStatus! : AbcStatus.HasXyzStatus,
71+
scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : [41,98,185],
72+
};
73+
};
74+
"
75+
`;
76+
77+
exports[`should correctly generate the \`casual\` data for a scalar mapping of type string 1`] = `
78+
"
79+
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {
80+
return {
81+
abc: overrides && overrides.hasOwnProperty('abc') ? overrides.abc! : 'sit',
82+
};
83+
};
84+
85+
export const anAvatar = (overrides?: Partial<Avatar>): Avatar => {
86+
return {
87+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '0550ff93-dd31-49b4-8c38-ff1cb68bdc38',
88+
url: overrides && overrides.hasOwnProperty('url') ? overrides.url! : 'aliquid',
89+
};
90+
};
91+
92+
export const aUpdateUserInput = (overrides?: Partial<UpdateUserInput>): UpdateUserInput => {
93+
return {
94+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '1d6a9360-c92b-4660-8e5f-04155047bddc',
95+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'qui',
96+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
97+
};
98+
};
99+
100+
export const aUser = (overrides?: Partial<User>): User => {
101+
return {
102+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'a5756f00-41a6-422a-8a7d-d13ee6a63750',
103+
creationDate: overrides && overrides.hasOwnProperty('creationDate') ? overrides.creationDate! : '1970-01-09T16:33:21.532Z',
104+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero',
105+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
106+
status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online,
107+
customStatus: overrides && overrides.hasOwnProperty('customStatus') ? overrides.customStatus! : AbcStatus.HasXyzStatus,
108+
scalarValue: overrides && overrides.hasOwnProperty('scalarValue') ? overrides.scalarValue! : '[email protected]',
109+
};
110+
};
111+
"
112+
`;
113+
40114
exports[`should generate mock data functions 1`] = `
41115
"
42116
export const anAbcType = (overrides?: Partial<AbcType>): AbcType => {

tests/typescript-mock-data.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,19 @@ it('should add custom prefix if the `prefix` config option is specified', async
177177
expect(result).not.toMatch(/const aUser/);
178178
expect(result).toMatchSnapshot();
179179
});
180+
181+
it('should correctly generate the `casual` data for a scalar mapping of type string', async () => {
182+
const result = await plugin(testSchema, [], { scalars: { AnyObject: 'email' } });
183+
184+
expect(result).toBeDefined();
185+
expect(result).toContain('[email protected]');
186+
expect(result).toMatchSnapshot();
187+
});
188+
189+
it('should correctly generate the `casual` data for a non-string scalar mapping', async () => {
190+
const result = await plugin(testSchema, [], { scalars: { AnyObject: 'rgb_array' } });
191+
192+
expect(result).toBeDefined();
193+
expect(result).toContain(JSON.stringify([41, 98, 185]));
194+
expect(result).toMatchSnapshot();
195+
});

0 commit comments

Comments
 (0)