Skip to content

Commit d1e1716

Browse files
authored
add support for useImplementingTypes (#125)
Add support for useImplementingTypes GraphQL codegen configuration. When a GraphQL interface is used for a field, this flag will use the implementing types, instead of the interface itself.
1 parent ec5ada0 commit d1e1716

File tree

5 files changed

+306
-15
lines changed

5 files changed

+306
-15
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ When disabled, underscores will be retained for type names when the case is chan
157157
158158
When enabled, values will be generated dynamically when the mock function is called rather than statically when the mock function is generated. The values are generated consistently from a [casual seed](https://github.com/boo1ean/casual#seeding) that can be manually configured using the generated `seedMocks(seed: number)` function, as shown in [this test](https://github.com/JimmyPaolini/graphql-codegen-typescript-mock-data/blob/dynamic-mode/tests/dynamicValues/spec.ts#L13).
159159
160+
### useImplementingTypes (`boolean`, defaultValue: `false`)
161+
162+
When enabled, it will support the useImplementingTypes GraphQL codegen configuration.
163+
- When a GraphQL interface is used for a field, this flag will use the implementing types, instead of the interface itself.
164+
160165
### fieldGeneration (`{ [typeName: string]: { [fieldName: string]: GeneratorOptions } }`, defaultValue: `undefined`)
161166
162167
This setting allows you to add specific generation to a field for a given type. For example if you have a type called `User` and a field called `birthDate` you can override any generated value there as follows:

src/index.ts

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { ASTKindToNode, ListTypeNode, NamedTypeNode, parse, printSchema, TypeNode } from 'graphql';
1+
import {
2+
parse,
3+
printSchema,
4+
TypeNode,
5+
ASTKindToNode,
6+
ListTypeNode,
7+
NamedTypeNode,
8+
ObjectTypeDefinitionNode,
9+
} from 'graphql';
210
import { faker } from '@faker-js/faker';
311
import casual from 'casual';
412
import { oldVisit, PluginFunction, resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers';
@@ -26,6 +34,7 @@ type Options<T = TypeNode> = {
2634
generateLibrary: 'casual' | 'faker';
2735
fieldGeneration?: TypeFieldMap;
2836
enumsAsTypes?: boolean;
37+
useImplementingTypes: boolean;
2938
};
3039

3140
const convertName = (value: string, fn: (v: string) => string, transformUnderscore: boolean): string => {
@@ -230,6 +239,17 @@ const handleValueGeneration = (
230239
return baseGenerator();
231240
};
232241

242+
const getNamedImplementType = (opts: Options<TypeItem['types']>): string => {
243+
if (!opts.currentType || !('name' in opts.currentType)) {
244+
return '';
245+
}
246+
247+
const name = opts.currentType.name.value;
248+
const casedName = createNameConverter(opts.typeNamesConvention, opts.transformUnderscore)(name);
249+
250+
return `${toMockName(name, casedName, opts.prefix)}()`;
251+
};
252+
233253
const getNamedType = (opts: Options<NamedTypeNode>): string | number | boolean => {
234254
if (!opts.currentType) {
235255
return '';
@@ -264,8 +284,14 @@ const getNamedType = (opts: Options<NamedTypeNode>): string | number | boolean =
264284
return handleValueGeneration(opts, customScalar, mockValueGenerator.integer);
265285
}
266286
default: {
267-
const foundType = opts.types.find((enumType: TypeItem) => enumType.name === name);
268-
if (foundType) {
287+
const foundTypes = opts.types.filter((foundType: TypeItem) => {
288+
if (foundType.types && 'interfaces' in foundType.types)
289+
return foundType.types.interfaces.every((item) => item.name.value === name);
290+
return foundType.name === name;
291+
});
292+
293+
if (foundTypes.length) {
294+
const foundType = foundTypes[0];
269295
switch (foundType.type) {
270296
case 'enum': {
271297
// It's an enum
@@ -300,6 +326,22 @@ const getNamedType = (opts: Options<NamedTypeNode>): string | number | boolean =
300326
foundType.name === 'Date' ? mockValueGenerator.date : mockValueGenerator.word,
301327
);
302328
}
329+
case 'implement':
330+
if (
331+
opts.fieldGeneration &&
332+
opts.fieldGeneration[opts.typeName] &&
333+
opts.fieldGeneration[opts.typeName][opts.fieldName]
334+
)
335+
break;
336+
337+
return foundTypes
338+
.map((implementType: TypeItem) =>
339+
getNamedImplementType({
340+
...opts,
341+
currentType: implementType.types,
342+
}),
343+
)
344+
.join(' || ');
303345
default:
304346
throw `foundType is unknown: ${foundType.name}: ${foundType.type}`;
305347
}
@@ -470,13 +512,14 @@ export interface TypescriptMocksPluginConfig {
470512
fieldGeneration?: TypeFieldMap;
471513
locale?: string;
472514
enumsAsTypes?: boolean;
515+
useImplementingTypes?: boolean;
473516
}
474517

475518
interface TypeItem {
476519
name: string;
477-
type: 'enum' | 'scalar' | 'union';
520+
type: 'enum' | 'scalar' | 'union' | 'implement';
478521
values?: string[];
479-
types?: readonly NamedTypeNode[];
522+
types?: readonly NamedTypeNode[] | ObjectTypeDefinitionNode;
480523
}
481524

482525
type VisitFn<TAnyNode, TVisitedNode = TAnyNode> = (
@@ -516,14 +559,15 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
516559
const dynamicValues = !!config.dynamicValues;
517560
const generateLibrary = config.generateLibrary || 'casual';
518561
const enumsAsTypes = config.enumsAsTypes ?? false;
562+
const useImplementingTypes = config.useImplementingTypes ?? false;
519563

520564
if (generateLibrary === 'faker' && config.locale) {
521565
faker.setLocale(config.locale);
522566
}
523567

524568
// List of types that are enums
525569
const types: TypeItem[] = [];
526-
const visitor: VisitorType = {
570+
const typeVisitor: VisitorType = {
527571
EnumTypeDefinition: (node) => {
528572
const name = node.name.value;
529573
if (!types.find((enumType: TypeItem) => enumType.name === name)) {
@@ -544,6 +588,32 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
544588
});
545589
}
546590
},
591+
ObjectTypeDefinition: (node) => {
592+
// This function triggered per each type
593+
const typeName = node.name.value;
594+
595+
if (config.useImplementingTypes) {
596+
if (!types.find((objectType) => objectType.name === typeName)) {
597+
node.interfaces.length &&
598+
types.push({
599+
name: typeName,
600+
type: 'implement',
601+
types: node,
602+
});
603+
}
604+
}
605+
},
606+
ScalarTypeDefinition: (node) => {
607+
const name = node.name.value;
608+
if (!types.find((scalarType) => scalarType.name === name)) {
609+
types.push({
610+
name,
611+
type: 'scalar',
612+
});
613+
}
614+
},
615+
};
616+
const visitor: VisitorType = {
547617
FieldDefinition: (node) => {
548618
const fieldName = node.name.value;
549619

@@ -568,6 +638,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
568638
generateLibrary,
569639
fieldGeneration: config.fieldGeneration,
570640
enumsAsTypes,
641+
useImplementingTypes,
571642
});
572643

573644
return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`;
@@ -601,6 +672,7 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
601672
generateLibrary,
602673
fieldGeneration: config.fieldGeneration,
603674
enumsAsTypes,
675+
useImplementingTypes,
604676
});
605677

606678
return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`;
@@ -665,17 +737,10 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
665737
},
666738
};
667739
},
668-
ScalarTypeDefinition: (node) => {
669-
const name = node.name.value;
670-
if (!types.find((enumType) => enumType.name === name)) {
671-
types.push({
672-
name,
673-
type: 'scalar',
674-
});
675-
}
676-
},
677740
};
678741

742+
// run on the types first
743+
oldVisit(astNode, { leave: typeVisitor });
679744
const result = oldVisit(astNode, { leave: visitor });
680745
const definitions = result.definitions.filter((definition: any) => !!definition);
681746
const typesFile = config.typesFile ? config.typesFile.replace(/\.[\w]+$/, '') : null;
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`should support useImplementingTypes 1`] = `
4+
"
5+
export const mockAConfig = (overrides?: Partial<AConfig>): AConfig => {
6+
return {
7+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
8+
};
9+
};
10+
11+
export const mockA = (overrides?: Partial<A>): A => {
12+
return {
13+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'cae147b0-1c04-459e-82db-624dd87433b4',
14+
str: overrides && overrides.hasOwnProperty('str') ? overrides.str! : 'ea',
15+
obj: overrides && overrides.hasOwnProperty('obj') ? overrides.obj! : mockB(),
16+
config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : mockTestAConfig() || mockTestTwoAConfig(),
17+
};
18+
};
19+
20+
export const mockB = (overrides?: Partial<B>): B => {
21+
return {
22+
int: overrides && overrides.hasOwnProperty('int') ? overrides.int! : 696,
23+
flt: overrides && overrides.hasOwnProperty('flt') ? overrides.flt! : 7.55,
24+
bool: overrides && overrides.hasOwnProperty('bool') ? overrides.bool! : false,
25+
};
26+
};
27+
28+
export const mockTestAConfig = (overrides?: Partial<TestAConfig>): TestAConfig => {
29+
return {
30+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
31+
active: overrides && overrides.hasOwnProperty('active') ? overrides.active! : true,
32+
};
33+
};
34+
35+
export const mockTestTwoAConfig = (overrides?: Partial<TestTwoAConfig>): TestTwoAConfig => {
36+
return {
37+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
38+
username: overrides && overrides.hasOwnProperty('username') ? overrides.username! : 'et',
39+
};
40+
};
41+
"
42+
`;
43+
44+
exports[`shouldn't support useImplementingTypes 1`] = `
45+
"
46+
export const mockAConfig = (overrides?: Partial<AConfig>): AConfig => {
47+
return {
48+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
49+
};
50+
};
51+
52+
export const mockA = (overrides?: Partial<A>): A => {
53+
return {
54+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'cae147b0-1c04-459e-82db-624dd87433b4',
55+
str: overrides && overrides.hasOwnProperty('str') ? overrides.str! : 'ea',
56+
obj: overrides && overrides.hasOwnProperty('obj') ? overrides.obj! : mockB(),
57+
config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : mockAConfig(),
58+
};
59+
};
60+
61+
export const mockB = (overrides?: Partial<B>): B => {
62+
return {
63+
int: overrides && overrides.hasOwnProperty('int') ? overrides.int! : 696,
64+
flt: overrides && overrides.hasOwnProperty('flt') ? overrides.flt! : 7.55,
65+
bool: overrides && overrides.hasOwnProperty('bool') ? overrides.bool! : false,
66+
};
67+
};
68+
69+
export const mockTestAConfig = (overrides?: Partial<TestAConfig>): TestAConfig => {
70+
return {
71+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
72+
active: overrides && overrides.hasOwnProperty('active') ? overrides.active! : true,
73+
};
74+
};
75+
76+
export const mockTestTwoAConfig = (overrides?: Partial<TestTwoAConfig>): TestTwoAConfig => {
77+
return {
78+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
79+
username: overrides && overrides.hasOwnProperty('username') ? overrides.username! : 'et',
80+
};
81+
};
82+
"
83+
`;
84+
85+
exports[`support useImplementingTypes with fieldGeneration prop 1`] = `
86+
"
87+
export const mockAConfig = (overrides?: Partial<AConfig>): AConfig => {
88+
return {
89+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
90+
};
91+
};
92+
93+
export const mockA = (overrides?: Partial<A>): A => {
94+
return {
95+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'cae147b0-1c04-459e-82db-624dd87433b4',
96+
str: overrides && overrides.hasOwnProperty('str') ? overrides.str! : 'ea',
97+
obj: overrides && overrides.hasOwnProperty('obj') ? overrides.obj! : mockB(),
98+
config: overrides && overrides.hasOwnProperty('config') ? overrides.config! : '[email protected]',
99+
};
100+
};
101+
102+
export const mockB = (overrides?: Partial<B>): B => {
103+
return {
104+
int: overrides && overrides.hasOwnProperty('int') ? overrides.int! : 696,
105+
flt: overrides && overrides.hasOwnProperty('flt') ? overrides.flt! : 7.55,
106+
bool: overrides && overrides.hasOwnProperty('bool') ? overrides.bool! : false,
107+
};
108+
};
109+
110+
export const mockTestAConfig = (overrides?: Partial<TestAConfig>): TestAConfig => {
111+
return {
112+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
113+
active: overrides && overrides.hasOwnProperty('active') ? overrides.active! : true,
114+
};
115+
};
116+
117+
export const mockTestTwoAConfig = (overrides?: Partial<TestTwoAConfig>): TestTwoAConfig => {
118+
return {
119+
configTypes: overrides && overrides.hasOwnProperty('configTypes') ? overrides.configTypes! : [ConfigTypes.Test],
120+
username: overrides && overrides.hasOwnProperty('username') ? overrides.username! : 'et',
121+
};
122+
};
123+
"
124+
`;

tests/useImplementingTypes/schema.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { buildSchema } from 'graphql';
2+
3+
export default buildSchema(/* GraphQL */ `
4+
interface AConfig {
5+
configTypes: [configTypes!]!
6+
}
7+
8+
enum configTypes {
9+
TEST
10+
TEST2
11+
}
12+
13+
type A {
14+
id: ID!
15+
str: String!
16+
obj: B!
17+
config: AConfig!
18+
}
19+
20+
type B {
21+
int: Int!
22+
flt: Float!
23+
bool: Boolean!
24+
}
25+
26+
type TestAConfig implements AConfig {
27+
configTypes: [configTypes!]!
28+
active: Boolean!
29+
}
30+
31+
type TestTwoAConfig implements AConfig {
32+
configTypes: [configTypes!]!
33+
username: String!
34+
}
35+
`);

0 commit comments

Comments
 (0)