Skip to content

Commit 71c9e0f

Browse files
Add support for oneOf directive (#180)
This PR adds support for the 'oneOf' directive on input objects. The generated mocking function is a little different than most others: - It only accepts a non-Partial type as an override - It picks an arbitrary fieldName/member from the input object and creates a default mock for that that field In order to install `@graphql-tools/utils`, I needed to upgrade Typescript Once I upgraded typescript, there were type errors for the 'indefinite' package, so I added the `@types/indefinite` package
1 parent 87b3d13 commit 71c9e0f

File tree

6 files changed

+390
-95
lines changed

6 files changed

+390
-95
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"dependencies": {
2525
"@faker-js/faker": "^8.4.1",
2626
"@graphql-codegen/plugin-helpers": "^5.0.4",
27+
"@graphql-tools/utils": "^10.7.2",
2728
"casual": "^1.6.2",
2829
"change-case-all": "^1.0.15",
2930
"indefinite": "^2.4.1",
@@ -40,6 +41,7 @@
4041
"@graphql-codegen/testing": "^3.0.3",
4142
"@graphql-codegen/typescript": "^4.0.7",
4243
"@types/jest": "^27.0.2",
44+
"@types/indefinite": "^2.3.4",
4345
"@typescript-eslint/eslint-plugin": "^5.1.0",
4446
"@typescript-eslint/parser": "^5.1.0",
4547
"auto": "^11.1.6",
@@ -57,7 +59,7 @@
5759
"prettier": "^2.4.1",
5860
"prettier-config-landr": "^0.2.0",
5961
"ts-jest": "^27.0.7",
60-
"typescript": "^4.4.4"
62+
"typescript": "^5.7.3"
6163
},
6264
"sideEffects": false,
6365
"scripts": {

src/index.ts

Lines changed: 69 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import {
2-
parse,
3-
printSchema,
4-
TypeNode,
5-
ASTKindToNode,
6-
ListTypeNode,
7-
NamedTypeNode,
8-
ObjectTypeDefinitionNode,
9-
} from 'graphql';
1+
import { parse, TypeNode, ASTKindToNode, ListTypeNode, NamedTypeNode, ObjectTypeDefinitionNode } from 'graphql';
102
import * as allFakerLocales from '@faker-js/faker';
113
import casual from 'casual';
124
import { oldVisit, PluginFunction, resolveExternalModuleAndFn } from '@graphql-codegen/plugin-helpers';
135
import { sentenceCase } from 'sentence-case';
146
import a from 'indefinite';
7+
import { printSchemaWithDirectives } from '@graphql-tools/utils';
158
import { setupFunctionTokens, setupMockValueGenerator } from './mockValueGenerator';
169

1710
type NamingConvention = 'change-case-all#pascalCase' | 'keep' | string;
@@ -460,6 +453,7 @@ const getMockString = (
460453
typesPrefix = '',
461454
transformUnderscore: boolean,
462455
typeNamesMapping?: Record<string, string>,
456+
hasOneOfDirective = false,
463457
) => {
464458
const typeNameConverter = createNameConverter(typeNamesConvention, transformUnderscore);
465459
const NewTypeName = typeNamesMapping[typeName] || typeName;
@@ -468,6 +462,10 @@ const getMockString = (
468462
const typename = addTypename ? `\n __typename: '${typeName}',` : '';
469463
const typenameReturnType = addTypename ? `{ __typename: '${typeName}' } & ` : '';
470464

465+
const overridesArgumentString = !hasOneOfDirective
466+
? `overrides?: Partial<${casedNameWithPrefix}>`
467+
: `override?: ${casedNameWithPrefix}`;
468+
471469
if (terminateCircularRelationships) {
472470
const relationshipsToOmitInit =
473471
terminateCircularRelationships === 'immediate' ? '_relationshipsToOmit' : 'new Set(_relationshipsToOmit)';
@@ -476,7 +474,7 @@ export const ${toMockName(
476474
typeName,
477475
casedName,
478476
prefix,
479-
)} = (overrides?: Partial<${casedNameWithPrefix}>, _relationshipsToOmit: Set<string> = new Set()): ${typenameReturnType}${casedNameWithPrefix} => {
477+
)} = (${overridesArgumentString}, _relationshipsToOmit: Set<string> = new Set()): ${typenameReturnType}${casedNameWithPrefix} => {
480478
const relationshipsToOmit: Set<string> = ${relationshipsToOmitInit};
481479
relationshipsToOmit.add('${casedName}');
482480
return {${typename}
@@ -489,7 +487,7 @@ export const ${toMockName(
489487
typeName,
490488
casedName,
491489
prefix,
492-
)} = (overrides?: Partial<${casedNameWithPrefix}>): ${typenameReturnType}${casedNameWithPrefix} => {
490+
)} = (${overridesArgumentString}): ${typenameReturnType}${casedNameWithPrefix} => {
493491
return {${typename}
494492
${fields}
495493
};
@@ -622,7 +620,8 @@ type VisitorType = { [K in keyof ASTKindToNode]?: VisitFn<ASTKindToNode[keyof AS
622620
// https://astexplorer.net
623621
// Paste your graphql schema in it, and you'll be able to see what the `astNode` will look like
624622
export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, documents, config) => {
625-
const printedSchema = printSchema(schema); // Returns a string representation of the schema
623+
const printedSchema = printSchemaWithDirectives(schema); // Returns a string representation of the schema
624+
626625
const astNode = parse(printedSchema); // Transforms the string into ASTNode
627626

628627
if ('typenames' in config) {
@@ -690,6 +689,30 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
690689
}
691690
},
692691
};
692+
693+
const sharedGenerateMockOpts = {
694+
customScalars: config.scalars,
695+
defaultNullableToNull,
696+
dynamicValues,
697+
enumsAsTypes,
698+
enumsPrefix: config.enumsPrefix,
699+
enumValuesConvention,
700+
fieldGeneration: config.fieldGeneration,
701+
generateLibrary,
702+
generatorLocale,
703+
listElementCount,
704+
nonNull: false,
705+
prefix: config.prefix,
706+
terminateCircularRelationships: getTerminateCircularRelationshipsConfig(config),
707+
transformUnderscore,
708+
typeNamesConvention,
709+
typeNamesMapping,
710+
types,
711+
typesPrefix: config.typesPrefix,
712+
useImplementingTypes,
713+
useTypeImports,
714+
};
715+
693716
const visitor: VisitorType = {
694717
FieldDefinition: (node) => {
695718
const fieldName = node.name.value;
@@ -700,27 +723,8 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
700723
const value = generateMockValue({
701724
typeName,
702725
fieldName,
703-
types,
704-
typeNamesConvention,
705-
enumValuesConvention,
706-
terminateCircularRelationships: getTerminateCircularRelationshipsConfig(config),
707-
prefix: config.prefix,
708-
typesPrefix: config.typesPrefix,
709-
enumsPrefix: config.enumsPrefix,
710726
currentType: node.type,
711-
customScalars: config.scalars,
712-
transformUnderscore,
713-
listElementCount,
714-
dynamicValues,
715-
generateLibrary,
716-
generatorLocale,
717-
fieldGeneration: config.fieldGeneration,
718-
enumsAsTypes,
719-
useTypeImports,
720-
useImplementingTypes,
721-
defaultNullableToNull,
722-
nonNull: false,
723-
typeNamesMapping: config.typeNamesMapping,
727+
...sharedGenerateMockOpts,
724728
});
725729

726730
return ` ${fieldName}: overrides && overrides.hasOwnProperty('${fieldName}') ? overrides.${fieldName}! : ${value},`;
@@ -733,50 +737,49 @@ export const plugin: PluginFunction<TypescriptMocksPluginConfig> = (schema, docu
733737
return {
734738
typeName: fieldName,
735739
mockFn: () => {
736-
const mockFields = node.fields
737-
? node.fields
738-
.map((field) => {
739-
const value = generateMockValue({
740-
typeName: fieldName,
741-
fieldName: field.name.value,
742-
types,
743-
typeNamesConvention,
744-
enumValuesConvention,
745-
terminateCircularRelationships: getTerminateCircularRelationshipsConfig(config),
746-
prefix: config.prefix,
747-
typesPrefix: config.typesPrefix,
748-
enumsPrefix: config.enumsPrefix,
749-
currentType: field.type,
750-
customScalars: config.scalars,
751-
transformUnderscore,
752-
listElementCount,
753-
dynamicValues,
754-
generateLibrary,
755-
generatorLocale,
756-
fieldGeneration: config.fieldGeneration,
757-
enumsAsTypes,
758-
useTypeImports,
759-
useImplementingTypes,
760-
defaultNullableToNull,
761-
nonNull: false,
762-
typeNamesMapping: config.typeNamesMapping,
763-
});
764-
765-
return ` ${field.name.value}: overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value},`;
766-
})
767-
.join('\n')
768-
: '';
740+
let mockFieldsString = '';
741+
742+
const { directives } = node;
743+
const hasOneOfDirective = directives.some((directive) => directive.name.value === 'oneOf');
744+
745+
if (node.fields && node.fields.length > 0 && hasOneOfDirective) {
746+
const field = node.fields[0];
747+
const value = generateMockValue({
748+
typeName: fieldName,
749+
fieldName: field.name.value,
750+
currentType: field.type,
751+
...sharedGenerateMockOpts,
752+
});
753+
754+
mockFieldsString = ` ...(override ? override : {${field.name.value} : ${value}}),`;
755+
} else if (node.fields) {
756+
mockFieldsString = node.fields
757+
.map((field) => {
758+
const value = generateMockValue({
759+
typeName: fieldName,
760+
fieldName: field.name.value,
761+
currentType: field.type,
762+
...sharedGenerateMockOpts,
763+
});
764+
765+
const valueWithOverride = `overrides && overrides.hasOwnProperty('${field.name.value}') ? overrides.${field.name.value}! : ${value}`;
766+
767+
return ` ${field.name.value}: ${valueWithOverride},`;
768+
})
769+
.join('\n');
770+
}
769771

770772
return getMockString(
771773
fieldName,
772-
mockFields,
774+
mockFieldsString,
773775
typeNamesConvention,
774776
getTerminateCircularRelationshipsConfig(config),
775777
false,
776778
config.prefix,
777779
config.typesPrefix,
778780
transformUnderscore,
779781
typeNamesMapping,
782+
hasOneOfDirective,
780783
);
781784
},
782785
};

0 commit comments

Comments
 (0)