Skip to content

Commit 43efcb5

Browse files
lilianammmatosrobrichard
authored andcommitted
Implement support for @defer directive
1 parent f2dc7e7 commit 43efcb5

File tree

12 files changed

+889
-59
lines changed

12 files changed

+889
-59
lines changed

src/execution/__tests__/defer-test.js

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import isAsyncIterable from '../../jsutils/isAsyncIterable';
5+
import { parse } from '../../language/parser';
6+
7+
import { GraphQLID, GraphQLString } from '../../type/scalars';
8+
import { GraphQLSchema } from '../../type/schema';
9+
import { GraphQLObjectType, GraphQLList } from '../../type/definition';
10+
11+
import { execute } from '../execute';
12+
13+
const friendType = new GraphQLObjectType({
14+
fields: {
15+
id: { type: GraphQLID },
16+
name: { type: GraphQLString },
17+
},
18+
name: 'Friend',
19+
});
20+
21+
const friends = [
22+
{ name: 'Han', id: 2 },
23+
{ name: 'Leia', id: 3 },
24+
{ name: 'C-3PO', id: 4 },
25+
];
26+
27+
const heroType = new GraphQLObjectType({
28+
fields: {
29+
id: { type: GraphQLID },
30+
name: { type: GraphQLString },
31+
errorField: {
32+
type: GraphQLString,
33+
resolve: () => {
34+
throw new Error('bad');
35+
},
36+
},
37+
friends: {
38+
type: new GraphQLList(friendType),
39+
resolve: () => friends,
40+
},
41+
},
42+
name: 'Hero',
43+
});
44+
45+
const hero = { name: 'Luke', id: 1 };
46+
47+
const query = new GraphQLObjectType({
48+
fields: {
49+
hero: {
50+
type: heroType,
51+
resolve: () => hero,
52+
},
53+
},
54+
name: 'Query',
55+
});
56+
57+
async function complete(document) {
58+
const schema = new GraphQLSchema({ query });
59+
60+
const result = await execute({
61+
schema,
62+
document,
63+
rootValue: {},
64+
});
65+
66+
if (isAsyncIterable(result)) {
67+
const results = [];
68+
for await (const patch of result) {
69+
results.push(patch);
70+
}
71+
return results;
72+
}
73+
return result;
74+
}
75+
76+
describe('Execute: defer directive', () => {
77+
it('Can defer fragments containing scalar types', async () => {
78+
const document = parse(`
79+
query HeroNameQuery {
80+
hero {
81+
id
82+
...NameFragment @defer
83+
}
84+
}
85+
fragment NameFragment on Hero {
86+
id
87+
name
88+
}
89+
`);
90+
const result = await complete(document);
91+
92+
expect(result).to.deep.equal([
93+
{
94+
data: {
95+
hero: {
96+
id: '1',
97+
},
98+
},
99+
hasNext: true,
100+
},
101+
{
102+
data: {
103+
id: '1',
104+
name: 'Luke',
105+
},
106+
path: ['hero'],
107+
hasNext: false,
108+
},
109+
]);
110+
});
111+
it('Can disable defer using if argument', async () => {
112+
const document = parse(`
113+
query HeroNameQuery {
114+
hero {
115+
id
116+
...NameFragment @defer(if: false)
117+
}
118+
}
119+
fragment NameFragment on Hero {
120+
name
121+
}
122+
`);
123+
const result = await complete(document);
124+
125+
expect(result).to.deep.equal({
126+
data: {
127+
hero: {
128+
id: '1',
129+
name: 'Luke',
130+
},
131+
},
132+
});
133+
});
134+
it('Can defer fragments containing on the top level Query field', async () => {
135+
const document = parse(`
136+
query HeroNameQuery {
137+
...QueryFragment @defer(label: "DeferQuery")
138+
}
139+
fragment QueryFragment on Query {
140+
hero {
141+
id
142+
}
143+
}
144+
`);
145+
const result = await complete(document);
146+
147+
expect(result).to.deep.equal([
148+
{
149+
data: {},
150+
hasNext: true,
151+
},
152+
{
153+
data: {
154+
hero: {
155+
id: '1',
156+
},
157+
},
158+
path: [],
159+
label: 'DeferQuery',
160+
hasNext: false,
161+
},
162+
]);
163+
});
164+
it('Can defer a fragment within an already deferred fragment', async () => {
165+
const document = parse(`
166+
query HeroNameQuery {
167+
hero {
168+
id
169+
...TopFragment @defer(label: "DeferTop")
170+
}
171+
}
172+
fragment TopFragment on Hero {
173+
name
174+
...NestedFragment @defer(label: "DeferNested")
175+
}
176+
fragment NestedFragment on Hero {
177+
friends {
178+
name
179+
}
180+
}
181+
`);
182+
const result = await complete(document);
183+
184+
expect(result).to.deep.equal([
185+
{
186+
data: {
187+
hero: {
188+
id: '1',
189+
},
190+
},
191+
hasNext: true,
192+
},
193+
{
194+
data: {
195+
friends: [{ name: 'Han' }, { name: 'Leia' }, { name: 'C-3PO' }],
196+
},
197+
path: ['hero'],
198+
label: 'DeferNested',
199+
hasNext: true,
200+
},
201+
{
202+
data: {
203+
name: 'Luke',
204+
},
205+
path: ['hero'],
206+
label: 'DeferTop',
207+
hasNext: false,
208+
},
209+
]);
210+
});
211+
it('Can defer an inline fragment', async () => {
212+
const document = parse(`
213+
query HeroNameQuery {
214+
hero {
215+
id
216+
... on Hero @defer(label: "InlineDeferred") {
217+
name
218+
}
219+
}
220+
}
221+
`);
222+
const result = await complete(document);
223+
224+
expect(result).to.deep.equal([
225+
{
226+
data: { hero: { id: '1' } },
227+
hasNext: true,
228+
},
229+
{
230+
data: { name: 'Luke' },
231+
path: ['hero'],
232+
label: 'InlineDeferred',
233+
hasNext: false,
234+
},
235+
]);
236+
});
237+
it('Handles errors thrown in deferred fragments', async () => {
238+
const document = parse(`
239+
query HeroNameQuery {
240+
hero {
241+
id
242+
...NameFragment @defer
243+
}
244+
}
245+
fragment NameFragment on Hero {
246+
errorField
247+
}
248+
`);
249+
const result = await complete(document);
250+
251+
expect(result).to.deep.equal([
252+
{
253+
data: { hero: { id: '1' } },
254+
hasNext: true,
255+
},
256+
{
257+
data: { errorField: null },
258+
path: ['hero'],
259+
errors: [
260+
{
261+
message: 'bad',
262+
locations: [{ line: 9, column: 9 }],
263+
path: ['hero', 'errorField'],
264+
},
265+
],
266+
hasNext: false,
267+
},
268+
]);
269+
});
270+
});

0 commit comments

Comments
 (0)