Skip to content

Commit 120911c

Browse files
authored
Bugfix/Feature: pascalCase; Bugfix: recursive nesting (#9)
This PR is going to fix the issue with pascalCase error, initially committed by @keesvanlierop Also, I extended that functionality to support upper case enums as well. The other thing that was failing for me was a recursive nesting. Check this schema ```graphql type User { id: Int! username: String! tasks: [Task!]! } type Task { id: Int! description: String author: User! status: TaskStatus! } ``` so, when I create a mock of a task -> user mock will be created, but then user mock will create a task mock and this will run recursively until the stack can't hold it anymore. Overrides won't help because they come **after** the mock is created so I switched to the getters, so they won't create a nested mock if there is an override.
1 parent 9b8abc5 commit 120911c

File tree

6 files changed

+263
-84
lines changed

6 files changed

+263
-84
lines changed

README.md

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Defines the file path containing all GraphQL types. This file can also be genera
1818

1919
Adds `__typename` property to mock data
2020

21+
### enumValues (`string`, defaultValue: `pascal-case#pascalCase`)
22+
23+
Change the case of the enums. Accept `upper-case#upperCase` or `pascal-case#pascalCase`
24+
2125
## Example of usage
2226

2327
**codegen.yml**
@@ -33,6 +37,7 @@ generates:
3337
plugins:
3438
- 'graphql-codegen-typescript-mock-data':
3539
typesFile: '../generated-types.ts'
40+
enumValues: upper-case#upperCase
3641
```
3742
3843
## Example or generated code
@@ -49,30 +54,53 @@ type User {
4954
id: ID!
5055
login: String!
5156
avatar: Avatar
57+
status: Status!
5258
}
5359

5460
type Query {
5561
user: User!
5662
}
63+
64+
input UpdateUserInput {
65+
id: ID!
66+
login: String
67+
avatar: Avatar
68+
}
69+
70+
enum Status {
71+
ONLINE
72+
OFFLINE
73+
}
74+
75+
type Mutation {
76+
updateUser(user: UpdateUserInput): User
77+
}
5778
```
5879

5980
The code generated will look like:
6081

6182
```typescript
6283
export const anAvatar = (overrides?: Partial<Avatar>): Avatar => {
6384
return {
64-
id: '1550ff93-cd31-49b4-bc38-ef1cb68bdc38',
65-
url: 'aliquid',
66-
...overrides,
85+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '0550ff93-dd31-49b4-8c38-ff1cb68bdc38',
86+
url: overrides && overrides.hasOwnProperty('url') ? overrides.url! : 'aliquid',
87+
};
88+
};
89+
90+
export const aUpdateUserInput = (overrides?: Partial<UpdateUserInput>): UpdateUserInput => {
91+
return {
92+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : '1d6a9360-c92b-4660-8e5f-04155047bddc',
93+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'qui',
94+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
6795
};
6896
};
6997

7098
export const aUser = (overrides?: Partial<User>): User => {
7199
return {
72-
id: 'b5756f00-51a6-422a-9a7d-c13ee6a63750',
73-
login: 'libero',
74-
avatar: anAvatar(),
75-
...overrides,
100+
id: overrides && overrides.hasOwnProperty('id') ? overrides.id! : 'a5756f00-41a6-422a-8a7d-d13ee6a63750',
101+
login: overrides && overrides.hasOwnProperty('login') ? overrides.login! : 'libero',
102+
avatar: overrides && overrides.hasOwnProperty('avatar') ? overrides.avatar! : anAvatar(),
103+
status: overrides && overrides.hasOwnProperty('status') ? overrides.status! : Status.Online,
76104
};
77105
};
78106
```

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
"dependencies": {
2525
"@graphql-codegen/plugin-helpers": "^1.13.1",
2626
"casual": "^1.6.2",
27-
"pascal-case": "^3.1.1"
27+
"pascal-case": "^3.1.1",
28+
"upper-case": "^2.0.1"
2829
},
2930
"peerDependencies": {
30-
"graphql": "^14.0.0"
31+
"graphql": "^14.6.0"
3132
},
3233
"devDependencies": {
3334
"@auto-it/conventional-commits": "^9.25.0",

src/index.ts

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@ import { printSchema, parse, visit, ASTKindToNode, NamedTypeNode, TypeNode, Visi
22
import casual from 'casual';
33
import { PluginFunction } from '@graphql-codegen/plugin-helpers';
44
import { pascalCase } from 'pascal-case';
5+
import { upperCase } from 'upper-case';
6+
7+
type EnumValuesTypes = 'upper-case#upperCase' | 'pascal-case#pascalCase';
8+
9+
const toMockName = (name: string) => {
10+
const isVowel = name.match(/^[AEIO]/);
11+
return isVowel ? `an${name}` : `a${name}`;
12+
};
13+
14+
const updateTextCase = (str: string, enumValues: EnumValuesTypes) => {
15+
const convert = (value: string) =>
16+
enumValues === 'upper-case#upperCase' ? upperCase(value || '') : pascalCase(value || '');
517

6-
export function toPascalCase(str: string) {
718
if (str.charAt(0) === '_') {
819
return str.replace(
920
/^(_*)(.*)/,
10-
(_match, underscorePrefix, typeName) => `${underscorePrefix}${pascalCase(typeName || '')}`,
21+
(_match, underscorePrefix, typeName) => `${underscorePrefix}${convert(typeName)}`,
1122
);
1223
}
1324

14-
return pascalCase(str || '');
15-
}
16-
17-
const toMockName = (name: string) => {
18-
const isVowel = name.match(/^[AEIO]/);
19-
return isVowel ? `an${name}` : `a${name}`;
25+
return convert(str);
2026
};
2127

2228
const hashedString = (value: string) => {
@@ -38,6 +44,7 @@ const getNamedType = (
3844
typeName: string,
3945
fieldName: string,
4046
types: TypeItem[],
47+
enumValues: EnumValuesTypes,
4148
namedType?: NamedTypeNode,
4249
): string | number | boolean => {
4350
if (!namedType) {
@@ -66,11 +73,17 @@ const getNamedType = (
6673
case 'enum': {
6774
// It's an enum
6875
const value = foundType.values ? foundType.values[0] : '';
69-
return `${foundType.name}.${toPascalCase(value)}`;
76+
return `${foundType.name}.${updateTextCase(value, enumValues)}`;
7077
}
7178
case 'union':
7279
// Return the first union type node.
73-
return getNamedType(typeName, fieldName, types, foundType.types && foundType.types[0]);
80+
return getNamedType(
81+
typeName,
82+
fieldName,
83+
types,
84+
enumValues,
85+
foundType.types && foundType.types[0],
86+
);
7487
default:
7588
throw `foundType is unknown: ${foundType.name}: ${foundType.type}`;
7689
}
@@ -84,15 +97,16 @@ const generateMockValue = (
8497
typeName: string,
8598
fieldName: string,
8699
types: TypeItem[],
100+
enumValues: EnumValuesTypes,
87101
currentType: TypeNode,
88102
): string | number | boolean => {
89103
switch (currentType.kind) {
90104
case 'NamedType':
91-
return getNamedType(typeName, fieldName, types, currentType as NamedTypeNode);
105+
return getNamedType(typeName, fieldName, types, enumValues, currentType as NamedTypeNode);
92106
case 'NonNullType':
93-
return generateMockValue(typeName, fieldName, types, currentType.type);
107+
return generateMockValue(typeName, fieldName, types, enumValues, currentType.type);
94108
case 'ListType': {
95-
const value = generateMockValue(typeName, fieldName, types, currentType.type);
109+
const value = generateMockValue(typeName, fieldName, types, enumValues, currentType.type);
96110
return `[${value}]`;
97111
}
98112
}
@@ -104,21 +118,21 @@ const getMockString = (typeName: string, fields: string, addTypename = false) =>
104118
export const ${toMockName(typeName)} = (overrides?: Partial<${typeName}>): ${typeName} => {
105119
return {${typename}
106120
${fields}
107-
...overrides
108121
};
109122
};`;
110123
};
111124

112125
export interface TypescriptMocksPluginConfig {
113126
typesFile?: string;
127+
enumValues?: EnumValuesTypes;
114128
addTypename?: boolean;
115129
}
116130

117131
interface TypeItem {
118132
name: string;
119133
type: string;
120134
values?: string[];
121-
types?: ReadonlyArray<NamedTypeNode>;
135+
types?: readonly NamedTypeNode[];
122136
}
123137

124138
type VisitorType = { [K in keyof ASTKindToNode]?: VisitFn<ASTKindToNode[keyof ASTKindToNode], ASTKindToNode[K]> };
@@ -129,6 +143,8 @@ type VisitorType = { [K in keyof ASTKindToNode]?: VisitFn<ASTKindToNode[keyof AS
129143
export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, documents, config) => {
130144
const printedSchema = printSchema(schema); // Returns a string representation of the schema
131145
const astNode = parse(printedSchema); // Transforms the string into ASTNode
146+
147+
const enumValues = config.enumValues || 'pascal-case#pascalCase';
132148
// List of types that are enums
133149
const types: TypeItem[] = [];
134150
const visitor: VisitorType = {
@@ -158,9 +174,9 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
158174
return {
159175
name: fieldName,
160176
mockFn: (typeName: string) => {
161-
const value = generateMockValue(typeName, fieldName, types, node.type);
177+
const value = generateMockValue(typeName, fieldName, types, enumValues, node.type);
162178

163-
return ` ${fieldName}: ${value},`;
179+
return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`;
164180
},
165181
};
166182
},
@@ -173,9 +189,15 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
173189
const mockFields = node.fields
174190
? node.fields
175191
.map((field) => {
176-
const value = generateMockValue(fieldName, field.name.value, types, field.type);
177-
178-
return ` ${field.name.value}: ${value},`;
192+
const value = generateMockValue(
193+
fieldName,
194+
field.name.value,
195+
types,
196+
enumValues,
197+
field.type,
198+
);
199+
200+
return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`;
179201
})
180202
.join('\n')
181203
: '';

0 commit comments

Comments
 (0)