Skip to content

Commit 558a8a2

Browse files
committed
Support __typename as field
1 parent 7de71c0 commit 558a8a2

File tree

2 files changed

+155
-2
lines changed

2 files changed

+155
-2
lines changed

src/__tests__/codegenApolloMock.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,144 @@ describe("codegenApolloMock", () => {
11361136
`);
11371137
});
11381138
});
1139+
1140+
describe("with __typename", () => {
1141+
let document: DocumentNode;
1142+
let output: string;
1143+
let apolloMock: ApolloMockFn;
1144+
1145+
beforeEach(async () => {
1146+
document = parse(`
1147+
query typename {
1148+
authors {
1149+
__typename
1150+
posts {
1151+
idField
1152+
}
1153+
}
1154+
objects {
1155+
__typename
1156+
}
1157+
search {
1158+
__typename
1159+
}
1160+
}
1161+
`);
1162+
1163+
const documents = [{ document, location: "typename.gql" }];
1164+
const config = getConfig({ documents });
1165+
1166+
output = await codegen(config);
1167+
apolloMock = getApolloMock(output);
1168+
});
1169+
1170+
it("should have matching operation", () => {
1171+
expect(output).toMatchInlineSnapshot(`
1172+
"import { createApolloMock } from 'apollo-typed-documents';
1173+
1174+
const operations = {};
1175+
1176+
export default createApolloMock(operations);
1177+
1178+
operations.typename = {};
1179+
operations.typename.variables = (values = {}, options = {}) => {
1180+
const __typename = '';
1181+
values = (({ }) => ({ }))(values);
1182+
values.__typename = __typename;
1183+
return {
1184+
1185+
};
1186+
}
1187+
operations.typename.data = (values = {}, options = {}) => {
1188+
const __typename = '';
1189+
values = (({ authors = null, objects = null, search = null }) => ({ authors, objects, search }))(values);
1190+
values.__typename = __typename;
1191+
return {
1192+
authors: !values.authors ? values.authors : values.authors.map(item => ((values = {}, options = {}) => {
1193+
const __typename = 'Author';
1194+
values = (({ __typename = null, posts = null }) => ({ __typename, posts }))(values);
1195+
values.__typename = __typename;
1196+
return {
1197+
__typename: values.__typename,
1198+
posts: !values.posts ? values.posts : values.posts.map(item => ((values = {}, options = {}) => {
1199+
const __typename = 'Post';
1200+
values = (({ idField = null }) => ({ idField }))(values);
1201+
values.__typename = __typename;
1202+
return {
1203+
idField: (values.idField === null || values.idField === undefined) ? [__typename, 'idField'].filter(v => v).join('-') : values.idField,
1204+
...(options.addTypename ? { __typename } : {})
1205+
};
1206+
})(item, options)),
1207+
...(options.addTypename ? { __typename } : {})
1208+
};
1209+
})(item, options)),
1210+
objects: !values.objects ? values.objects : values.objects.map(item => ((values = {}, options = {}) => {
1211+
const typenames = ['Author', 'Post'];
1212+
const __typename = typenames.find(typename => typename === values.__typename) || typenames[0];
1213+
values = (({ __typename = null }) => ({ __typename }))(values);
1214+
values.__typename = __typename;
1215+
return {
1216+
__typename: values.__typename,
1217+
...(options.addTypename ? { __typename } : {})
1218+
};
1219+
})(item, options)),
1220+
search: !values.search ? values.search : values.search.map(item => ((values = {}, options = {}) => {
1221+
const typenames = ['Author', 'Post'];
1222+
const __typename = typenames.find(typename => typename === values.__typename) || typenames[0];
1223+
values = (({ __typename = null }) => ({ __typename }))(values);
1224+
values.__typename = __typename;
1225+
return {
1226+
__typename: values.__typename,
1227+
...(options.addTypename ? { __typename } : {})
1228+
};
1229+
})(item, options))
1230+
};
1231+
}"
1232+
`);
1233+
});
1234+
1235+
it("should include __typename even when disabled in options", () => {
1236+
const result = apolloMock(
1237+
document,
1238+
{},
1239+
{ authors: [{ posts: [{}] }], objects: [{}], search: [{}] },
1240+
{ addTypename: false }
1241+
);
1242+
1243+
expect(result).toMatchInlineSnapshot(`
1244+
{
1245+
"request": {
1246+
"query": "...",
1247+
"variables": {}
1248+
},
1249+
"result": {
1250+
"data": {
1251+
"authors": [
1252+
{
1253+
"__typename": "Author",
1254+
"posts": [
1255+
{
1256+
"idField": "Post-idField"
1257+
}
1258+
]
1259+
}
1260+
],
1261+
"objects": [
1262+
{
1263+
"__typename": "Author"
1264+
}
1265+
],
1266+
"search": [
1267+
{
1268+
"__typename": "Author"
1269+
}
1270+
]
1271+
}
1272+
}
1273+
}
1274+
`);
1275+
});
1276+
});
11391277
});
11401278

11411279
describe("mutation", () => {

src/visitors/TypedVisitor.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface TypedField {
3434
isNonNull: boolean;
3535
isList: boolean;
3636
isFragment: boolean;
37+
isTypename: boolean;
3738
typenames: string[];
3839

3940
scalarType?: GraphQLScalarType;
@@ -56,6 +57,7 @@ const createTypedField = (
5657
isNonNull: false,
5758
isList: false,
5859
isFragment: false,
60+
isTypename: false,
5961
typenames: [],
6062
...field,
6163
});
@@ -232,15 +234,28 @@ export default class TypedVisitor {
232234
}
233235

234236
getOutputFieldForField(node: FieldNode, parentField: TypedField) {
237+
const name = node.name.value;
238+
239+
if (name === "__typename") {
240+
return createTypedField({ name, isTypename: true });
241+
}
242+
235243
const parentType = parentField.objectType || parentField.interfaceType;
236244

237245
if (!parentType) {
238246
throw new Error("Parent type is not an object or interface type");
239247
}
240248

241249
const fields = parentType.getFields();
242-
const field = fields[node.name.value];
243-
const typedField = createTypedField({ name: node.name.value });
250+
const field = fields[name];
251+
252+
if (!field) {
253+
throw new Error(
254+
`Field "${name}" does not exist on type "${parentType.name}"`
255+
);
256+
}
257+
258+
const typedField = createTypedField({ name });
244259

245260
let type = field.type;
246261

0 commit comments

Comments
 (0)