Skip to content

Commit f396313

Browse files
authored
Merge pull request #70 from mizdra/fix-abstract-type
Fix problem with interface and union fields not being optional
2 parents cc0d7ad + 7c09da0 commit f396313

File tree

6 files changed

+459
-213
lines changed

6 files changed

+459
-213
lines changed

e2e/esm/index.e2e.ts

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -119,49 +119,67 @@ describe('GraphQL features test', () => {
119119
field4: ({ field: string } | null)[];
120120
}>();
121121
});
122-
it('interface', async () => {
123-
const TypeWithInterfaceField = defineInterfaceTest_TypeWithInterfaceFieldFactory({
124-
defaultFields: {
125-
interface: undefined,
126-
},
127-
});
128-
const ImplementingTypeFactory = defineInterfaceTest_ImplementingTypeFactory({
129-
defaultFields: {
130-
id: 'ImplementingType-0',
131-
field: 'field',
132-
},
133-
});
134-
const typeWithInterfaceField = await TypeWithInterfaceField.build({
135-
interface: await ImplementingTypeFactory.build(),
122+
describe('interface', () => {
123+
it('basic', async () => {
124+
const TypeWithInterfaceField = defineInterfaceTest_TypeWithInterfaceFieldFactory({
125+
defaultFields: {
126+
interface: undefined,
127+
},
128+
});
129+
const ImplementingTypeFactory = defineInterfaceTest_ImplementingTypeFactory({
130+
defaultFields: {
131+
id: 'ImplementingType-0',
132+
field: 'field',
133+
},
134+
});
135+
const typeWithInterfaceField = await TypeWithInterfaceField.build({
136+
interface: await ImplementingTypeFactory.build(),
137+
});
138+
expect(typeWithInterfaceField).toStrictEqual({
139+
interface: {
140+
id: 'ImplementingType-0',
141+
field: 'field',
142+
},
143+
});
144+
expectTypeOf(typeWithInterfaceField).toEqualTypeOf<{ interface: { id: string; field: string } }>();
136145
});
137-
expect(typeWithInterfaceField).toStrictEqual({
138-
interface: {
139-
id: 'ImplementingType-0',
140-
field: 'field',
141-
},
146+
it('the fields of a union field is optional', async () => {
147+
defineInterfaceTest_TypeWithInterfaceFieldFactory({
148+
defaultFields: {
149+
interface: {},
150+
},
151+
});
142152
});
143-
expectTypeOf(typeWithInterfaceField).toEqualTypeOf<{ interface: { id: string; field: string } }>();
144153
});
145-
it('union', async () => {
146-
const TypeFactory = defineUnionTest_TypeFactory({
147-
defaultFields: {
148-
union: undefined,
149-
},
150-
});
151-
const Member1Factory = defineUnionTest_Member1Factory({
152-
defaultFields: {
153-
field1: 'field1',
154-
},
155-
});
156-
const type = await TypeFactory.build({
157-
union: await Member1Factory.build(),
154+
describe('union', () => {
155+
it('basic', async () => {
156+
const TypeFactory = defineUnionTest_TypeFactory({
157+
defaultFields: {
158+
union: undefined,
159+
},
160+
});
161+
const Member1Factory = defineUnionTest_Member1Factory({
162+
defaultFields: {
163+
field1: 'field1',
164+
},
165+
});
166+
const type = await TypeFactory.build({
167+
union: await Member1Factory.build(),
168+
});
169+
expect(type).toStrictEqual({
170+
union: {
171+
field1: 'field1',
172+
},
173+
});
174+
expectTypeOf(type).toEqualTypeOf<{ union: { field1: string } }>();
158175
});
159-
expect(type).toStrictEqual({
160-
union: {
161-
field1: 'field1',
162-
},
176+
it('the fields of a union field is optional', async () => {
177+
defineUnionTest_TypeFactory({
178+
defaultFields: {
179+
union: {},
180+
},
181+
});
163182
});
164-
expectTypeOf(type).toEqualTypeOf<{ union: { field1: string } }>();
165183
});
166184
it('enum', async () => {
167185
const TypeFactory = defineEnumTest_TypeFactory({

src/__snapshots__/code-generator.test.ts.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ exports[`generateCode > generates code 1`] = `
88
type FieldsResolver,
99
defineTypeFactoryInternal,
1010
} from '@mizdra/graphql-codegen-typescript-fabbrica/helper';
11-
import type { Maybe, Book, Author } from './types';
11+
import type { Maybe, Book, Author, Node } from './types';
1212
1313
export * from '@mizdra/graphql-codegen-typescript-fabbrica/helper';
1414
export type OptionalBook = {
@@ -101,5 +101,7 @@ export function defineAuthorFactory<
101101
return defineAuthorFactoryInternal(options);
102102
}
103103
104+
export type OptionalNode = OptionalBook | OptionalAuthor;
105+
104106
"
105107
`;

src/code-generator.test.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import { fakeConfig, oneOf } from './test/util.js';
66

77
describe('generateOptionalTypeDefinitionCode', () => {
88
it('generates description comment', () => {
9-
const typeInfo: TypeInfo = {
9+
const typeInfo1: TypeInfo = {
10+
type: 'object',
1011
name: 'Book',
1112
fields: [
1213
{ name: 'id', typeString: 'string | undefined' },
1314
{ name: 'title', typeString: 'string | undefined', comment: transformComment('The book title') },
1415
],
1516
comment: transformComment('The book'),
1617
};
17-
const actual = generateOptionalTypeDefinitionCode(typeInfo);
18-
expect(actual).toMatchInlineSnapshot(`
18+
expect(generateOptionalTypeDefinitionCode(typeInfo1)).toMatchInlineSnapshot(`
1919
"/** The book */
2020
export type OptionalBook = {
2121
id?: string | undefined;
@@ -24,6 +24,17 @@ describe('generateOptionalTypeDefinitionCode', () => {
2424
};
2525
"
2626
`);
27+
const typeInfo2: TypeInfo = {
28+
type: 'abstract',
29+
name: 'Node',
30+
possibleTypes: ['Book', 'Author'],
31+
comment: transformComment('The node'),
32+
};
33+
expect(generateOptionalTypeDefinitionCode(typeInfo2)).toMatchInlineSnapshot(`
34+
"/** The node */
35+
export type OptionalNode = OptionalBook | OptionalAuthor;
36+
"
37+
`);
2738
});
2839
});
2940

@@ -35,6 +46,7 @@ describe('generateCode', () => {
3546
});
3647
const typeInfos: TypeInfo[] = [
3748
{
49+
type: 'object',
3850
name: 'Book',
3951
fields: [
4052
{ name: 'id', typeString: 'string | undefined' },
@@ -43,13 +55,19 @@ describe('generateCode', () => {
4355
],
4456
},
4557
{
58+
type: 'object',
4659
name: 'Author',
4760
fields: [
4861
{ name: 'id', typeString: 'string | undefined' },
4962
{ name: 'name', typeString: 'string | undefined' },
5063
{ name: 'books', typeString: 'Book[] | undefined' },
5164
],
5265
},
66+
{
67+
type: 'abstract',
68+
name: 'Node',
69+
possibleTypes: ['Book', 'Author'],
70+
},
5371
];
5472
const actual = generateCode(config, typeInfos);
5573
expect(actual).toMatchSnapshot();

src/code-generator.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Config } from './config.js';
2-
import { TypeInfo } from './schema-scanner.js';
2+
import { ObjectTypeInfo, TypeInfo } from './schema-scanner.js';
33

44
function generatePreludeCode(config: Config, typeInfos: TypeInfo[]): string {
55
const joinedTypeNames = typeInfos.map(({ name }) => name).join(', ');
@@ -19,28 +19,37 @@ export * from '@mizdra/graphql-codegen-typescript-fabbrica/helper';
1919
}
2020

2121
export function generateOptionalTypeDefinitionCode(typeInfo: TypeInfo): string {
22-
const { name, fields } = typeInfo;
23-
const comment = typeInfo.comment ?? '';
24-
const joinedPropDefinitions = fields
25-
.map((field) => {
26-
const comment = field.comment ? ` ${field.comment}` : '';
27-
return `${comment} ${field.name}?: ${field.typeString};`;
28-
})
29-
.join('\n');
30-
return `
22+
if (typeInfo.type === 'object') {
23+
const { name, fields } = typeInfo;
24+
const comment = typeInfo.comment ?? '';
25+
const joinedPropDefinitions = fields
26+
.map((field) => {
27+
const comment = field.comment ? ` ${field.comment}` : '';
28+
return `${comment} ${field.name}?: ${field.typeString};`;
29+
})
30+
.join('\n');
31+
return `
3132
${comment}export type Optional${name} = {
3233
${joinedPropDefinitions}
3334
};
3435
`.trimStart();
36+
} else {
37+
const { name, possibleTypes } = typeInfo;
38+
const comment = typeInfo.comment ?? '';
39+
const joinedPossibleTypes = possibleTypes.map((type) => `Optional${type}`).join(' | ');
40+
return `
41+
${comment}export type Optional${name} = ${joinedPossibleTypes};
42+
`.trimStart();
43+
}
3544
}
3645

37-
function generateFieldNamesDefinitionCode(typeInfo: TypeInfo): string {
46+
function generateFieldNamesDefinitionCode(typeInfo: ObjectTypeInfo): string {
3847
const { name, fields } = typeInfo;
3948
const joinedFieldNames = fields.map((field) => `'${field.name}'`).join(', ');
4049
return `const ${name}FieldNames = [${joinedFieldNames}] as const;\n`;
4150
}
4251

43-
function generateTypeFactoryCode(config: Config, typeInfo: TypeInfo): string {
52+
function generateTypeFactoryCode(config: Config, typeInfo: ObjectTypeInfo): string {
4453
const { name } = typeInfo;
4554
function wrapRequired(str: string) {
4655
return config.nonOptionalDefaultFields ? `Required<${str}>` : str;
@@ -91,10 +100,12 @@ export function generateCode(config: Config, typeInfos: TypeInfo[]): string {
91100
for (const typeInfo of typeInfos) {
92101
code += generateOptionalTypeDefinitionCode(typeInfo);
93102
code += '\n';
94-
code += generateFieldNamesDefinitionCode(typeInfo);
95-
code += '\n';
96-
code += generateTypeFactoryCode(config, typeInfo);
97-
code += '\n';
103+
if (typeInfo.type === 'object') {
104+
code += generateFieldNamesDefinitionCode(typeInfo);
105+
code += '\n';
106+
code += generateTypeFactoryCode(config, typeInfo);
107+
code += '\n';
108+
}
98109
}
99110
return code;
100111
}

0 commit comments

Comments
 (0)