@apollo/federation-internals 2.4.4 → 2.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Subgraph.d.ts +1 -0
- package/dist/Subgraph.d.ts.map +1 -0
- package/dist/Subgraph.js +2 -0
- package/dist/Subgraph.js.map +1 -0
- package/dist/argumentCompositionStrategies.d.ts +34 -0
- package/dist/argumentCompositionStrategies.d.ts.map +1 -0
- package/dist/argumentCompositionStrategies.js +35 -0
- package/dist/argumentCompositionStrategies.js.map +1 -0
- package/dist/buildSchema.d.ts +10 -0
- package/dist/buildSchema.d.ts.map +1 -0
- package/dist/buildSchema.js +362 -0
- package/dist/buildSchema.js.map +1 -0
- package/dist/coreSpec.d.ts +127 -0
- package/dist/coreSpec.d.ts.map +1 -0
- package/dist/coreSpec.js +590 -0
- package/dist/coreSpec.js.map +1 -0
- package/dist/debug.d.ts +15 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/debug.js +122 -0
- package/dist/debug.js.map +1 -0
- package/dist/definitions.d.ts +663 -0
- package/dist/definitions.d.ts.map +1 -0
- package/dist/definitions.js +2841 -0
- package/dist/definitions.js.map +1 -0
- package/dist/directiveAndTypeSpecification.d.ts +67 -0
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
- package/dist/directiveAndTypeSpecification.js +271 -0
- package/dist/directiveAndTypeSpecification.js.map +1 -0
- package/dist/error.d.ts +128 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +315 -0
- package/dist/error.js.map +1 -0
- package/dist/extractSubgraphsFromSupergraph.d.ts +8 -0
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -0
- package/dist/extractSubgraphsFromSupergraph.js +576 -0
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -0
- package/dist/federation.d.ts +175 -0
- package/dist/federation.d.ts.map +1 -0
- package/dist/federation.js +1414 -0
- package/dist/federation.js.map +1 -0
- package/dist/federationSpec.d.ts +25 -0
- package/dist/federationSpec.d.ts.map +1 -0
- package/dist/federationSpec.js +125 -0
- package/dist/federationSpec.js.map +1 -0
- package/dist/genErrorCodeDoc.d.ts +2 -0
- package/dist/genErrorCodeDoc.d.ts.map +1 -0
- package/dist/genErrorCodeDoc.js +61 -0
- package/dist/genErrorCodeDoc.js.map +1 -0
- package/dist/graphQLJSSchemaToAST.d.ts +8 -0
- package/dist/graphQLJSSchemaToAST.d.ts.map +1 -0
- package/dist/graphQLJSSchemaToAST.js +96 -0
- package/dist/graphQLJSSchemaToAST.js.map +1 -0
- package/dist/inaccessibleSpec.d.ts +18 -0
- package/dist/inaccessibleSpec.d.ts.map +1 -0
- package/dist/inaccessibleSpec.js +655 -0
- package/dist/inaccessibleSpec.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection.d.ts +6 -0
- package/dist/introspection.d.ts.map +1 -0
- package/dist/introspection.js +96 -0
- package/dist/introspection.js.map +1 -0
- package/dist/joinSpec.d.ts +51 -0
- package/dist/joinSpec.d.ts.map +1 -0
- package/dist/joinSpec.js +160 -0
- package/dist/joinSpec.js.map +1 -0
- package/dist/knownCoreFeatures.d.ts +5 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -0
- package/dist/knownCoreFeatures.js +20 -0
- package/dist/knownCoreFeatures.js.map +1 -0
- package/dist/operations.d.ts +418 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +2068 -0
- package/dist/operations.js.map +1 -0
- package/dist/precompute.d.ts +3 -0
- package/dist/precompute.d.ts.map +1 -0
- package/dist/precompute.js +54 -0
- package/dist/precompute.js.map +1 -0
- package/dist/print.d.ts +28 -0
- package/dist/print.d.ts.map +1 -0
- package/dist/print.js +299 -0
- package/dist/print.js.map +1 -0
- package/dist/schemaUpgrader.d.ts +121 -0
- package/dist/schemaUpgrader.d.ts.map +1 -0
- package/dist/schemaUpgrader.js +570 -0
- package/dist/schemaUpgrader.js.map +1 -0
- package/dist/suggestions.d.ts +3 -0
- package/dist/suggestions.d.ts.map +1 -0
- package/dist/suggestions.js +44 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/supergraphs.d.ts +10 -0
- package/dist/supergraphs.d.ts.map +1 -0
- package/dist/supergraphs.js +76 -0
- package/dist/supergraphs.js.map +1 -0
- package/dist/tagSpec.d.ts +19 -0
- package/dist/tagSpec.d.ts.map +1 -0
- package/dist/tagSpec.js +66 -0
- package/dist/tagSpec.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +64 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +64 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +326 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +239 -0
- package/dist/validate.js.map +1 -0
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts +4 -0
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -0
- package/dist/validation/KnownTypeNamesInFederationRule.js +41 -0
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -0
- package/dist/values.d.ts +23 -0
- package/dist/values.d.ts.map +1 -0
- package/dist/values.js +580 -0
- package/dist/values.js.map +1 -0
- package/package.json +1 -1
- package/src/operations.ts +145 -20
- package/src/utils.ts +1 -1
- package/CHANGELOG.md +0 -205
- package/jest.config.js +0 -11
- package/src/__tests__/coreSpec.test.ts +0 -212
- package/src/__tests__/definitions.test.ts +0 -982
- package/src/__tests__/directiveAndTypeSpecifications.test.ts +0 -41
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +0 -748
- package/src/__tests__/federation.test.ts +0 -31
- package/src/__tests__/graphQLJSSchemaToAST.test.ts +0 -156
- package/src/__tests__/matchers/index.ts +0 -1
- package/src/__tests__/matchers/toMatchString.ts +0 -87
- package/src/__tests__/operations.test.ts +0 -1266
- package/src/__tests__/removeInaccessibleElements.test.ts +0 -2471
- package/src/__tests__/schemaUpgrader.test.ts +0 -287
- package/src/__tests__/subgraphValidation.test.ts +0 -1254
- package/src/__tests__/supergraphSdl.graphql +0 -281
- package/src/__tests__/testUtils.ts +0 -28
- package/src/__tests__/toAPISchema.test.ts +0 -53
- package/src/__tests__/tsconfig.json +0 -7
- package/src/__tests__/utils.test.ts +0 -92
- package/src/__tests__/values.test.ts +0 -390
- package/tsconfig.json +0 -10
- package/tsconfig.test.json +0 -8
|
@@ -1,1266 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defaultRootName,
|
|
3
|
-
Schema,
|
|
4
|
-
SchemaRootKind,
|
|
5
|
-
} from '../../dist/definitions';
|
|
6
|
-
import { buildSchema } from '../../dist/buildSchema';
|
|
7
|
-
import { MutableSelectionSet, Operation, operationFromDocument, parseOperation } from '../../dist/operations';
|
|
8
|
-
import './matchers';
|
|
9
|
-
import { DocumentNode, FieldNode, GraphQLError, Kind, OperationDefinitionNode, OperationTypeNode, SelectionNode, SelectionSetNode } from 'graphql';
|
|
10
|
-
|
|
11
|
-
function parseSchema(schema: string): Schema {
|
|
12
|
-
try {
|
|
13
|
-
return buildSchema(schema);
|
|
14
|
-
} catch (e) {
|
|
15
|
-
throw new Error('Error parsing the schema:\n' + e.toString());
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function astField(name: string, selectionSet?: SelectionSetNode): FieldNode {
|
|
20
|
-
return {
|
|
21
|
-
kind: Kind.FIELD,
|
|
22
|
-
name: { kind: Kind.NAME, value: name },
|
|
23
|
-
selectionSet,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function astSSet(...selections: SelectionNode[]): SelectionSetNode {
|
|
28
|
-
return {
|
|
29
|
-
kind: Kind.SELECTION_SET,
|
|
30
|
-
selections,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('fragments optimization', () => {
|
|
35
|
-
// Takes a query with fragments as inputs, expand all those fragments, and ensures that all the
|
|
36
|
-
// fragments gets optimized back, and that we get back the exact same query.
|
|
37
|
-
function testFragmentsRoundtrip({
|
|
38
|
-
schema,
|
|
39
|
-
query,
|
|
40
|
-
expanded,
|
|
41
|
-
}: {
|
|
42
|
-
schema: Schema,
|
|
43
|
-
query: string,
|
|
44
|
-
expanded: string,
|
|
45
|
-
}) {
|
|
46
|
-
const operation = parseOperation(schema, query);
|
|
47
|
-
// We call `trimUnsatisfiableBranches` because the selections we care about in the query planner
|
|
48
|
-
// will effectively have had gone through that function (and even if that function wasn't called,
|
|
49
|
-
// the query planning algorithm would still end up removing unsatisfiable branches anyway), so
|
|
50
|
-
// it is a more interesting test.
|
|
51
|
-
const withoutFragments = operation.expandAllFragments().trimUnsatisfiableBranches();
|
|
52
|
-
|
|
53
|
-
expect(withoutFragments.toString()).toMatchString(expanded);
|
|
54
|
-
|
|
55
|
-
// We force keeping all reused fragments, even if they are used only once, because the tests using
|
|
56
|
-
// this are just about testing the reuse of fragments and this make things shorter/easier to write.
|
|
57
|
-
// There is tests in `buildPlan.test.ts` that double-check that we don't reuse fragments used only
|
|
58
|
-
// once in actual query plans.
|
|
59
|
-
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!, 1);
|
|
60
|
-
expect(optimized.toString()).toMatchString(operation.toString());
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
test('handles fragments using other fragments', () => {
|
|
64
|
-
const schema = parseSchema(`
|
|
65
|
-
type Query {
|
|
66
|
-
t: T1
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface I {
|
|
70
|
-
b: Int
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
type T1 {
|
|
74
|
-
a: Int
|
|
75
|
-
b: Int
|
|
76
|
-
u: U
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
type T2 {
|
|
80
|
-
x: String
|
|
81
|
-
y: String
|
|
82
|
-
b: Int
|
|
83
|
-
u: U
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
union U = T1 | T2
|
|
87
|
-
`);
|
|
88
|
-
|
|
89
|
-
const operation = parseOperation(schema, `
|
|
90
|
-
fragment OnT1 on T1 {
|
|
91
|
-
a
|
|
92
|
-
b
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fragment OnT2 on T2 {
|
|
96
|
-
x
|
|
97
|
-
y
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fragment OnI on I {
|
|
101
|
-
b
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
fragment OnU on U {
|
|
105
|
-
...OnI
|
|
106
|
-
...OnT1
|
|
107
|
-
...OnT2
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
query {
|
|
111
|
-
t {
|
|
112
|
-
...OnT1
|
|
113
|
-
...OnT2
|
|
114
|
-
...OnI
|
|
115
|
-
u {
|
|
116
|
-
...OnU
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
`);
|
|
121
|
-
|
|
122
|
-
const withoutFragments = parseOperation(schema, operation.toString(true, true));
|
|
123
|
-
expect(withoutFragments.toString()).toMatchString(`
|
|
124
|
-
{
|
|
125
|
-
t {
|
|
126
|
-
... on T1 {
|
|
127
|
-
a
|
|
128
|
-
b
|
|
129
|
-
}
|
|
130
|
-
... on T2 {
|
|
131
|
-
x
|
|
132
|
-
y
|
|
133
|
-
}
|
|
134
|
-
... on I {
|
|
135
|
-
b
|
|
136
|
-
}
|
|
137
|
-
u {
|
|
138
|
-
... on U {
|
|
139
|
-
... on I {
|
|
140
|
-
b
|
|
141
|
-
}
|
|
142
|
-
... on T1 {
|
|
143
|
-
a
|
|
144
|
-
b
|
|
145
|
-
}
|
|
146
|
-
... on T2 {
|
|
147
|
-
x
|
|
148
|
-
y
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
`);
|
|
155
|
-
|
|
156
|
-
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
157
|
-
expect(optimized.toString()).toMatchString(`
|
|
158
|
-
fragment OnT1 on T1 {
|
|
159
|
-
a
|
|
160
|
-
b
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
fragment OnT2 on T2 {
|
|
164
|
-
x
|
|
165
|
-
y
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
fragment OnI on I {
|
|
169
|
-
b
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
{
|
|
173
|
-
t {
|
|
174
|
-
...OnI
|
|
175
|
-
...OnT1
|
|
176
|
-
...OnT2
|
|
177
|
-
u {
|
|
178
|
-
...OnI
|
|
179
|
-
...OnT1
|
|
180
|
-
...OnT2
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
`);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('handles fragments with nested selections', () => {
|
|
188
|
-
const schema = parseSchema(`
|
|
189
|
-
type Query {
|
|
190
|
-
t1a: T1
|
|
191
|
-
t2a: T1
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
type T1 {
|
|
195
|
-
t2: T2
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
type T2 {
|
|
199
|
-
x: String
|
|
200
|
-
y: String
|
|
201
|
-
}
|
|
202
|
-
`);
|
|
203
|
-
|
|
204
|
-
testFragmentsRoundtrip({
|
|
205
|
-
schema,
|
|
206
|
-
query: `
|
|
207
|
-
fragment OnT1 on T1 {
|
|
208
|
-
t2 {
|
|
209
|
-
x
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
query {
|
|
214
|
-
t1a {
|
|
215
|
-
...OnT1
|
|
216
|
-
t2 {
|
|
217
|
-
y
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
t2a {
|
|
221
|
-
...OnT1
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
`,
|
|
225
|
-
expanded: `
|
|
226
|
-
{
|
|
227
|
-
t1a {
|
|
228
|
-
t2 {
|
|
229
|
-
x
|
|
230
|
-
y
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
t2a {
|
|
234
|
-
t2 {
|
|
235
|
-
x
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
`,
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test('handles nested fragments with field intersection', () => {
|
|
244
|
-
const schema = parseSchema(`
|
|
245
|
-
type Query {
|
|
246
|
-
t: T
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
type T {
|
|
250
|
-
a: A
|
|
251
|
-
b: Int
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
type A {
|
|
255
|
-
x: String
|
|
256
|
-
y: String
|
|
257
|
-
z: String
|
|
258
|
-
}
|
|
259
|
-
`);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// The subtlety here is that `FA` contains `__typename` and so after we're reused it, the
|
|
263
|
-
// selection will look like:
|
|
264
|
-
// {
|
|
265
|
-
// t {
|
|
266
|
-
// a {
|
|
267
|
-
// ...FA
|
|
268
|
-
// }
|
|
269
|
-
// }
|
|
270
|
-
// }
|
|
271
|
-
// But to recognize that `FT` can be reused from there, we need to be able to see that
|
|
272
|
-
// the `__typename` that `FT` wants is inside `FA` (and since FA applies on the parent type `A`
|
|
273
|
-
// directly, it is fine to reuse).
|
|
274
|
-
testFragmentsRoundtrip({
|
|
275
|
-
schema,
|
|
276
|
-
query: `
|
|
277
|
-
fragment FA on A {
|
|
278
|
-
__typename
|
|
279
|
-
x
|
|
280
|
-
y
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
fragment FT on T {
|
|
284
|
-
a {
|
|
285
|
-
__typename
|
|
286
|
-
...FA
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
query {
|
|
291
|
-
t {
|
|
292
|
-
...FT
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
`,
|
|
296
|
-
expanded: `
|
|
297
|
-
{
|
|
298
|
-
t {
|
|
299
|
-
a {
|
|
300
|
-
__typename
|
|
301
|
-
x
|
|
302
|
-
y
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
`,
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test('handles fragment matching subset of field selection', () => {
|
|
311
|
-
const schema = parseSchema(`
|
|
312
|
-
type Query {
|
|
313
|
-
t: T
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
type T {
|
|
317
|
-
a: String
|
|
318
|
-
b: B
|
|
319
|
-
c: Int
|
|
320
|
-
d: D
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
type B {
|
|
324
|
-
x: String
|
|
325
|
-
y: String
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
type D {
|
|
329
|
-
m: String
|
|
330
|
-
n: String
|
|
331
|
-
}
|
|
332
|
-
`);
|
|
333
|
-
|
|
334
|
-
testFragmentsRoundtrip({
|
|
335
|
-
schema,
|
|
336
|
-
query: `
|
|
337
|
-
fragment FragT on T {
|
|
338
|
-
b {
|
|
339
|
-
__typename
|
|
340
|
-
x
|
|
341
|
-
}
|
|
342
|
-
c
|
|
343
|
-
d {
|
|
344
|
-
m
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
{
|
|
349
|
-
t {
|
|
350
|
-
...FragT
|
|
351
|
-
d {
|
|
352
|
-
n
|
|
353
|
-
}
|
|
354
|
-
a
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
`,
|
|
358
|
-
expanded: `
|
|
359
|
-
{
|
|
360
|
-
t {
|
|
361
|
-
b {
|
|
362
|
-
__typename
|
|
363
|
-
x
|
|
364
|
-
}
|
|
365
|
-
c
|
|
366
|
-
d {
|
|
367
|
-
m
|
|
368
|
-
n
|
|
369
|
-
}
|
|
370
|
-
a
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
`,
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
test('handles fragment matching subset of inline fragment selection', () => {
|
|
378
|
-
// Pretty much the same test than the previous one, but matching inside a fragment selection inside
|
|
379
|
-
// of inside a field selection.
|
|
380
|
-
const schema = parseSchema(`
|
|
381
|
-
type Query {
|
|
382
|
-
i: I
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
interface I {
|
|
386
|
-
a: String
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
type T {
|
|
390
|
-
a: String
|
|
391
|
-
b: B
|
|
392
|
-
c: Int
|
|
393
|
-
d: D
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
type B {
|
|
397
|
-
x: String
|
|
398
|
-
y: String
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
type D {
|
|
402
|
-
m: String
|
|
403
|
-
n: String
|
|
404
|
-
}
|
|
405
|
-
`);
|
|
406
|
-
|
|
407
|
-
testFragmentsRoundtrip({
|
|
408
|
-
schema,
|
|
409
|
-
query: `
|
|
410
|
-
fragment FragT on T {
|
|
411
|
-
b {
|
|
412
|
-
__typename
|
|
413
|
-
x
|
|
414
|
-
}
|
|
415
|
-
c
|
|
416
|
-
d {
|
|
417
|
-
m
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
{
|
|
422
|
-
i {
|
|
423
|
-
... on T {
|
|
424
|
-
...FragT
|
|
425
|
-
d {
|
|
426
|
-
n
|
|
427
|
-
}
|
|
428
|
-
a
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
`,
|
|
433
|
-
expanded: `
|
|
434
|
-
{
|
|
435
|
-
i {
|
|
436
|
-
... on T {
|
|
437
|
-
b {
|
|
438
|
-
__typename
|
|
439
|
-
x
|
|
440
|
-
}
|
|
441
|
-
c
|
|
442
|
-
d {
|
|
443
|
-
m
|
|
444
|
-
n
|
|
445
|
-
}
|
|
446
|
-
a
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
`,
|
|
451
|
-
});
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
test('intersecting fragments', () => {
|
|
455
|
-
const schema = parseSchema(`
|
|
456
|
-
type Query {
|
|
457
|
-
t: T
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
type T {
|
|
461
|
-
a: String
|
|
462
|
-
b: B
|
|
463
|
-
c: Int
|
|
464
|
-
d: D
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
type B {
|
|
468
|
-
x: String
|
|
469
|
-
y: String
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
type D {
|
|
473
|
-
m: String
|
|
474
|
-
n: String
|
|
475
|
-
}
|
|
476
|
-
`);
|
|
477
|
-
|
|
478
|
-
testFragmentsRoundtrip({
|
|
479
|
-
schema,
|
|
480
|
-
// Note: the code that reuse fragments iterates on fragments in the order they are defined in the document, but when it reuse
|
|
481
|
-
// a fragment, it puts it at the beginning of the selection (somewhat random, it just feel often easier to read), so the net
|
|
482
|
-
// effect on this example is that `Frag2`, which will be reused after `Frag1` will appear first in the re-optimized selection.
|
|
483
|
-
// So we put it first in the input too so that input and output actually match (the `testFragmentsRoundtrip` compares strings,
|
|
484
|
-
// so it is sensible to ordering; we could theoretically use `Operation.equals` instead of string equality, which wouldn't
|
|
485
|
-
// really on ordering, but `Operation.equals` is not entirely trivial and comparing strings make problem a bit more obvious).
|
|
486
|
-
query: `
|
|
487
|
-
fragment Frag1 on T {
|
|
488
|
-
b {
|
|
489
|
-
x
|
|
490
|
-
}
|
|
491
|
-
c
|
|
492
|
-
d {
|
|
493
|
-
m
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
fragment Frag2 on T {
|
|
498
|
-
a
|
|
499
|
-
b {
|
|
500
|
-
__typename
|
|
501
|
-
x
|
|
502
|
-
}
|
|
503
|
-
d {
|
|
504
|
-
m
|
|
505
|
-
n
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
{
|
|
510
|
-
t {
|
|
511
|
-
...Frag2
|
|
512
|
-
...Frag1
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
`,
|
|
516
|
-
expanded: `
|
|
517
|
-
{
|
|
518
|
-
t {
|
|
519
|
-
a
|
|
520
|
-
b {
|
|
521
|
-
__typename
|
|
522
|
-
x
|
|
523
|
-
}
|
|
524
|
-
d {
|
|
525
|
-
m
|
|
526
|
-
n
|
|
527
|
-
}
|
|
528
|
-
c
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
`,
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
test('fragments whose application makes a type condition trivial', () => {
|
|
536
|
-
const schema = parseSchema(`
|
|
537
|
-
type Query {
|
|
538
|
-
t: T
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
interface I {
|
|
542
|
-
x: String
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
type T implements I {
|
|
546
|
-
x: String
|
|
547
|
-
a: String
|
|
548
|
-
}
|
|
549
|
-
`);
|
|
550
|
-
|
|
551
|
-
testFragmentsRoundtrip({
|
|
552
|
-
schema,
|
|
553
|
-
query: `
|
|
554
|
-
fragment FragI on I {
|
|
555
|
-
x
|
|
556
|
-
... on T {
|
|
557
|
-
a
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
{
|
|
562
|
-
t {
|
|
563
|
-
...FragI
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
`,
|
|
567
|
-
expanded: `
|
|
568
|
-
{
|
|
569
|
-
t {
|
|
570
|
-
x
|
|
571
|
-
a
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
`,
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
test('handles fragment matching at the top level of another fragment', () => {
|
|
579
|
-
const schema = parseSchema(`
|
|
580
|
-
type Query {
|
|
581
|
-
t: T
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
type T {
|
|
585
|
-
a: String
|
|
586
|
-
u: U
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
type U {
|
|
590
|
-
x: String
|
|
591
|
-
y: String
|
|
592
|
-
}
|
|
593
|
-
`);
|
|
594
|
-
|
|
595
|
-
testFragmentsRoundtrip({
|
|
596
|
-
schema,
|
|
597
|
-
query: `
|
|
598
|
-
fragment Frag1 on T {
|
|
599
|
-
a
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
fragment Frag2 on T {
|
|
603
|
-
u {
|
|
604
|
-
x
|
|
605
|
-
y
|
|
606
|
-
}
|
|
607
|
-
...Frag1
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
fragment Frag3 on Query {
|
|
611
|
-
t {
|
|
612
|
-
...Frag2
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
{
|
|
617
|
-
...Frag3
|
|
618
|
-
}
|
|
619
|
-
`,
|
|
620
|
-
expanded: `
|
|
621
|
-
{
|
|
622
|
-
t {
|
|
623
|
-
u {
|
|
624
|
-
x
|
|
625
|
-
y
|
|
626
|
-
}
|
|
627
|
-
a
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
`,
|
|
631
|
-
});
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
test('handles fragments used in a context where they get trimmed', () => {
|
|
635
|
-
const schema = parseSchema(`
|
|
636
|
-
type Query {
|
|
637
|
-
t1: T1
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
interface I {
|
|
641
|
-
x: Int
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
type T1 implements I {
|
|
645
|
-
x: Int
|
|
646
|
-
y: Int
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
type T2 implements I {
|
|
650
|
-
x: Int
|
|
651
|
-
z: Int
|
|
652
|
-
}
|
|
653
|
-
`);
|
|
654
|
-
|
|
655
|
-
testFragmentsRoundtrip({
|
|
656
|
-
schema,
|
|
657
|
-
query: `
|
|
658
|
-
fragment FragOnI on I {
|
|
659
|
-
... on T1 {
|
|
660
|
-
y
|
|
661
|
-
}
|
|
662
|
-
... on T2 {
|
|
663
|
-
z
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
{
|
|
668
|
-
t1 {
|
|
669
|
-
...FragOnI
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
`,
|
|
673
|
-
expanded: `
|
|
674
|
-
{
|
|
675
|
-
t1 {
|
|
676
|
-
y
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
`,
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
test('handles fragments used in the context of non-intersecting abstract types', () => {
|
|
684
|
-
const schema = parseSchema(`
|
|
685
|
-
type Query {
|
|
686
|
-
i2: I2
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
interface I1 {
|
|
690
|
-
x: Int
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
interface I2 {
|
|
694
|
-
y: Int
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
interface I3 {
|
|
698
|
-
z: Int
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
type T1 implements I1 & I2 {
|
|
702
|
-
x: Int
|
|
703
|
-
y: Int
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
type T2 implements I1 & I3 {
|
|
707
|
-
x: Int
|
|
708
|
-
z: Int
|
|
709
|
-
}
|
|
710
|
-
`);
|
|
711
|
-
|
|
712
|
-
testFragmentsRoundtrip({
|
|
713
|
-
schema,
|
|
714
|
-
query: `
|
|
715
|
-
fragment FragOnI1 on I1 {
|
|
716
|
-
... on I2 {
|
|
717
|
-
y
|
|
718
|
-
}
|
|
719
|
-
... on I3 {
|
|
720
|
-
z
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
{
|
|
725
|
-
i2 {
|
|
726
|
-
...FragOnI1
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
`,
|
|
730
|
-
expanded: `
|
|
731
|
-
{
|
|
732
|
-
i2 {
|
|
733
|
-
... on I1 {
|
|
734
|
-
... on I2 {
|
|
735
|
-
y
|
|
736
|
-
}
|
|
737
|
-
... on I3 {
|
|
738
|
-
z
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
`,
|
|
744
|
-
});
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
describe('applied directives', () => {
|
|
748
|
-
test('reuse fragments with directives on the fragment, but only when there is those directives', () => {
|
|
749
|
-
const schema = parseSchema(`
|
|
750
|
-
type Query {
|
|
751
|
-
t1: T
|
|
752
|
-
t2: T
|
|
753
|
-
t3: T
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
type T {
|
|
757
|
-
a: Int
|
|
758
|
-
b: Int
|
|
759
|
-
c: Int
|
|
760
|
-
d: Int
|
|
761
|
-
}
|
|
762
|
-
`);
|
|
763
|
-
|
|
764
|
-
testFragmentsRoundtrip({
|
|
765
|
-
schema,
|
|
766
|
-
query: `
|
|
767
|
-
fragment DirectiveOnDef on T @include(if: $cond1) {
|
|
768
|
-
a
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
772
|
-
t1 {
|
|
773
|
-
...DirectiveOnDef
|
|
774
|
-
}
|
|
775
|
-
t2 {
|
|
776
|
-
... on T @include(if: $cond2) {
|
|
777
|
-
a
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
t3 {
|
|
781
|
-
...DirectiveOnDef @include(if: $cond2)
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
`,
|
|
785
|
-
expanded: `
|
|
786
|
-
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
787
|
-
t1 {
|
|
788
|
-
... on T @include(if: $cond1) {
|
|
789
|
-
a
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
t2 {
|
|
793
|
-
... on T @include(if: $cond2) {
|
|
794
|
-
a
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
t3 {
|
|
798
|
-
... on T @include(if: $cond1) @include(if: $cond2) {
|
|
799
|
-
a
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
`,
|
|
804
|
-
});
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
test('reuse fragments with directives in the fragment selection, but only when there is those directives', () => {
|
|
808
|
-
const schema = parseSchema(`
|
|
809
|
-
type Query {
|
|
810
|
-
t1: T
|
|
811
|
-
t2: T
|
|
812
|
-
t3: T
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
type T {
|
|
816
|
-
a: Int
|
|
817
|
-
b: Int
|
|
818
|
-
c: Int
|
|
819
|
-
d: Int
|
|
820
|
-
}
|
|
821
|
-
`);
|
|
822
|
-
|
|
823
|
-
testFragmentsRoundtrip({
|
|
824
|
-
schema,
|
|
825
|
-
query: `
|
|
826
|
-
fragment DirectiveInDef on T {
|
|
827
|
-
a @include(if: $cond1)
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
831
|
-
t1 {
|
|
832
|
-
a
|
|
833
|
-
}
|
|
834
|
-
t2 {
|
|
835
|
-
...DirectiveInDef
|
|
836
|
-
}
|
|
837
|
-
t3 {
|
|
838
|
-
a @include(if: $cond2)
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
`,
|
|
842
|
-
expanded: `
|
|
843
|
-
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
844
|
-
t1 {
|
|
845
|
-
a
|
|
846
|
-
}
|
|
847
|
-
t2 {
|
|
848
|
-
a @include(if: $cond1)
|
|
849
|
-
}
|
|
850
|
-
t3 {
|
|
851
|
-
a @include(if: $cond2)
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
`,
|
|
855
|
-
});
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
test('reuse fragments with directives on spread, but only when there is those directives', () => {
|
|
859
|
-
const schema = parseSchema(`
|
|
860
|
-
type Query {
|
|
861
|
-
t1: T
|
|
862
|
-
t2: T
|
|
863
|
-
t3: T
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
type T {
|
|
867
|
-
a: Int
|
|
868
|
-
b: Int
|
|
869
|
-
c: Int
|
|
870
|
-
d: Int
|
|
871
|
-
}
|
|
872
|
-
`);
|
|
873
|
-
|
|
874
|
-
testFragmentsRoundtrip({
|
|
875
|
-
schema,
|
|
876
|
-
query: `
|
|
877
|
-
fragment NoDirectiveDef on T {
|
|
878
|
-
a
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
query myQuery($cond1: Boolean!) {
|
|
882
|
-
t1 {
|
|
883
|
-
...NoDirectiveDef
|
|
884
|
-
}
|
|
885
|
-
t2 {
|
|
886
|
-
...NoDirectiveDef @include(if: $cond1)
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
`,
|
|
890
|
-
expanded: `
|
|
891
|
-
query myQuery($cond1: Boolean!) {
|
|
892
|
-
t1 {
|
|
893
|
-
a
|
|
894
|
-
}
|
|
895
|
-
t2 {
|
|
896
|
-
... on T @include(if: $cond1) {
|
|
897
|
-
a
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
`,
|
|
902
|
-
});
|
|
903
|
-
});
|
|
904
|
-
});
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
describe('validations', () => {
|
|
908
|
-
test.each([
|
|
909
|
-
{ directive: '@defer', rootKind: 'mutation' },
|
|
910
|
-
{ directive: '@defer', rootKind: 'subscription' },
|
|
911
|
-
{ directive: '@stream', rootKind: 'mutation' },
|
|
912
|
-
{ directive: '@stream', rootKind: 'subscription' },
|
|
913
|
-
])('reject $directive on $rootKind type', ({ directive, rootKind }) => {
|
|
914
|
-
const schema = parseSchema(`
|
|
915
|
-
type Query {
|
|
916
|
-
x: String
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
type Mutation {
|
|
920
|
-
x: String
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
type Subscription {
|
|
924
|
-
x: String
|
|
925
|
-
}
|
|
926
|
-
`);
|
|
927
|
-
|
|
928
|
-
expect(() => {
|
|
929
|
-
parseOperation(schema, `
|
|
930
|
-
${rootKind} {
|
|
931
|
-
... ${directive} {
|
|
932
|
-
x
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
`)
|
|
936
|
-
}).toThrowError(new GraphQLError(`The @defer and @stream directives cannot be used on ${rootKind} root type "${defaultRootName(rootKind as SchemaRootKind)}"`));
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
test('allows nullable variable for non-nullable input field with default', () => {
|
|
940
|
-
const schema = parseSchema(`
|
|
941
|
-
input I {
|
|
942
|
-
x: Int! = 42
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
type Query {
|
|
946
|
-
f(i: I): Int
|
|
947
|
-
}
|
|
948
|
-
`);
|
|
949
|
-
|
|
950
|
-
// Just testing that this parse correctly and does not throw an exception.
|
|
951
|
-
parseOperation(schema, `
|
|
952
|
-
query test($x: Int) {
|
|
953
|
-
f(i: { x: $x })
|
|
954
|
-
}
|
|
955
|
-
`);
|
|
956
|
-
});
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
describe('empty branches removal', () => {
|
|
960
|
-
const schema = parseSchema(`
|
|
961
|
-
type Query {
|
|
962
|
-
t: T
|
|
963
|
-
u: Int
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
type T {
|
|
967
|
-
a: Int
|
|
968
|
-
b: Int
|
|
969
|
-
c: C
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
type C {
|
|
973
|
-
x: String
|
|
974
|
-
y: String
|
|
975
|
-
}
|
|
976
|
-
`);
|
|
977
|
-
|
|
978
|
-
const withoutEmptyBranches = (op: string | SelectionSetNode) => {
|
|
979
|
-
let operation: Operation;
|
|
980
|
-
if (typeof op === 'string') {
|
|
981
|
-
operation = parseOperation(schema, op);
|
|
982
|
-
} else {
|
|
983
|
-
// Note that testing the removal of empty branches requires to take inputs that are not valid operations in the first place,
|
|
984
|
-
// so we can't build those from `parseOperation` (this call the graphQL-js `parse` under the hood, and there is no way to
|
|
985
|
-
// disable validation for that method). So instead, we manually build the AST (using some helper methods defined above) and
|
|
986
|
-
// build the operation from there, disabling validation.
|
|
987
|
-
const opDef: OperationDefinitionNode = {
|
|
988
|
-
kind: Kind.OPERATION_DEFINITION,
|
|
989
|
-
operation: OperationTypeNode.QUERY,
|
|
990
|
-
selectionSet: op,
|
|
991
|
-
}
|
|
992
|
-
const document: DocumentNode = {
|
|
993
|
-
kind: Kind.DOCUMENT,
|
|
994
|
-
definitions: [opDef],
|
|
995
|
-
}
|
|
996
|
-
operation = operationFromDocument(schema, document, { validate: false });
|
|
997
|
-
}
|
|
998
|
-
return operation.selectionSet.withoutEmptyBranches()?.toString()
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
it.each([
|
|
1003
|
-
'{ t { a } }',
|
|
1004
|
-
'{ t { a b } }',
|
|
1005
|
-
'{ t { a c { x y } } }',
|
|
1006
|
-
])('is identity if there is no empty branch', (op) => {
|
|
1007
|
-
expect(withoutEmptyBranches(op)).toBe(op);
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
it('removes simple empty branches', () => {
|
|
1011
|
-
expect(withoutEmptyBranches(
|
|
1012
|
-
astSSet(
|
|
1013
|
-
astField('t', astSSet(
|
|
1014
|
-
astField('a'),
|
|
1015
|
-
astField('c', astSSet()),
|
|
1016
|
-
))
|
|
1017
|
-
)
|
|
1018
|
-
)).toBe('{ t { a } }');
|
|
1019
|
-
|
|
1020
|
-
expect(withoutEmptyBranches(
|
|
1021
|
-
astSSet(
|
|
1022
|
-
astField('t', astSSet(
|
|
1023
|
-
astField('c', astSSet()),
|
|
1024
|
-
astField('a'),
|
|
1025
|
-
))
|
|
1026
|
-
)
|
|
1027
|
-
)).toBe('{ t { a } }');
|
|
1028
|
-
|
|
1029
|
-
expect(withoutEmptyBranches(
|
|
1030
|
-
astSSet(
|
|
1031
|
-
astField('t', astSSet())
|
|
1032
|
-
)
|
|
1033
|
-
)).toBeUndefined();
|
|
1034
|
-
});
|
|
1035
|
-
|
|
1036
|
-
it('removes cascading empty branches', () => {
|
|
1037
|
-
expect(withoutEmptyBranches(
|
|
1038
|
-
astSSet(
|
|
1039
|
-
astField('t', astSSet(
|
|
1040
|
-
astField('c', astSSet()),
|
|
1041
|
-
))
|
|
1042
|
-
)
|
|
1043
|
-
)).toBeUndefined();
|
|
1044
|
-
|
|
1045
|
-
expect(withoutEmptyBranches(
|
|
1046
|
-
astSSet(
|
|
1047
|
-
astField('u'),
|
|
1048
|
-
astField('t', astSSet(
|
|
1049
|
-
astField('c', astSSet()),
|
|
1050
|
-
))
|
|
1051
|
-
)
|
|
1052
|
-
)).toBe('{ u }');
|
|
1053
|
-
|
|
1054
|
-
expect(withoutEmptyBranches(
|
|
1055
|
-
astSSet(
|
|
1056
|
-
astField('t', astSSet(
|
|
1057
|
-
astField('c', astSSet()),
|
|
1058
|
-
)),
|
|
1059
|
-
astField('u'),
|
|
1060
|
-
)
|
|
1061
|
-
)).toBe('{ u }');
|
|
1062
|
-
});
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
describe('basic operations', () => {
|
|
1066
|
-
const schema = parseSchema(`
|
|
1067
|
-
type Query {
|
|
1068
|
-
t: T
|
|
1069
|
-
i: I
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
type T {
|
|
1073
|
-
v1: Int
|
|
1074
|
-
v2: String
|
|
1075
|
-
v3: I
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
interface I {
|
|
1079
|
-
x: Int
|
|
1080
|
-
y: Int
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
type A implements I {
|
|
1084
|
-
x: Int
|
|
1085
|
-
y: Int
|
|
1086
|
-
a1: String
|
|
1087
|
-
a2: String
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
type B implements I {
|
|
1091
|
-
x: Int
|
|
1092
|
-
y: Int
|
|
1093
|
-
b1: Int
|
|
1094
|
-
b2: T
|
|
1095
|
-
}
|
|
1096
|
-
`);
|
|
1097
|
-
|
|
1098
|
-
const operation = parseOperation(schema, `
|
|
1099
|
-
{
|
|
1100
|
-
t {
|
|
1101
|
-
v1
|
|
1102
|
-
v3 {
|
|
1103
|
-
x
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
i {
|
|
1107
|
-
... on A {
|
|
1108
|
-
a1
|
|
1109
|
-
a2
|
|
1110
|
-
}
|
|
1111
|
-
... on B {
|
|
1112
|
-
y
|
|
1113
|
-
b2 {
|
|
1114
|
-
v2
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
`);
|
|
1120
|
-
|
|
1121
|
-
test('forEachElement', () => {
|
|
1122
|
-
// We collect a pair of (parent type, field-or-fragment).
|
|
1123
|
-
const actual: [string, string][] = [];
|
|
1124
|
-
operation.selectionSet.forEachElement((elt) => actual.push([elt.parentType.name, elt.toString()]));
|
|
1125
|
-
expect(actual).toStrictEqual([
|
|
1126
|
-
['Query', 't'],
|
|
1127
|
-
['T', 'v1'],
|
|
1128
|
-
['T', 'v3'],
|
|
1129
|
-
['I', 'x'],
|
|
1130
|
-
['Query', 'i'],
|
|
1131
|
-
['I', '... on A'],
|
|
1132
|
-
['A', 'a1'],
|
|
1133
|
-
['A', 'a2'],
|
|
1134
|
-
['I', '... on B'],
|
|
1135
|
-
['B', 'y'],
|
|
1136
|
-
['B', 'b2'],
|
|
1137
|
-
['T', 'v2'],
|
|
1138
|
-
]);
|
|
1139
|
-
})
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
describe('MutableSelectionSet', () => {
|
|
1143
|
-
test('memoizer', () => {
|
|
1144
|
-
const schema = parseSchema(`
|
|
1145
|
-
type Query {
|
|
1146
|
-
t: T
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
type T {
|
|
1150
|
-
v1: Int
|
|
1151
|
-
v2: String
|
|
1152
|
-
v3: Int
|
|
1153
|
-
v4: Int
|
|
1154
|
-
}
|
|
1155
|
-
`);
|
|
1156
|
-
|
|
1157
|
-
type Value = {
|
|
1158
|
-
count: number
|
|
1159
|
-
};
|
|
1160
|
-
|
|
1161
|
-
let calls = 0;
|
|
1162
|
-
const sets: string[] = [];
|
|
1163
|
-
|
|
1164
|
-
const queryType = schema.schemaDefinition.rootType('query')!;
|
|
1165
|
-
const ss = MutableSelectionSet.emptyWithMemoized<Value>(
|
|
1166
|
-
queryType,
|
|
1167
|
-
(s) => {
|
|
1168
|
-
sets.push(s.toString());
|
|
1169
|
-
return { count: ++calls };
|
|
1170
|
-
}
|
|
1171
|
-
);
|
|
1172
|
-
|
|
1173
|
-
expect(ss.memoized().count).toBe(1);
|
|
1174
|
-
// Calling a 2nd time with no change to make sure we're not re-generating the value.
|
|
1175
|
-
expect(ss.memoized().count).toBe(1);
|
|
1176
|
-
|
|
1177
|
-
ss.updates().add(parseOperation(schema, `{ t { v1 } }`).selectionSet);
|
|
1178
|
-
|
|
1179
|
-
expect(ss.memoized().count).toBe(2);
|
|
1180
|
-
expect(sets).toStrictEqual(['{}', '{ t { v1 } }']);
|
|
1181
|
-
|
|
1182
|
-
ss.updates().add(parseOperation(schema, `{ t { v3 } }`).selectionSet);
|
|
1183
|
-
|
|
1184
|
-
expect(ss.memoized().count).toBe(3);
|
|
1185
|
-
expect(sets).toStrictEqual(['{}', '{ t { v1 } }', '{ t { v1 v3 } }']);
|
|
1186
|
-
|
|
1187
|
-
// Still making sure we don't re-compute without updates.
|
|
1188
|
-
expect(ss.memoized().count).toBe(3);
|
|
1189
|
-
|
|
1190
|
-
const cloned = ss.clone();
|
|
1191
|
-
expect(cloned.memoized().count).toBe(3);
|
|
1192
|
-
|
|
1193
|
-
cloned.updates().add(parseOperation(schema, `{ t { v2 } }`).selectionSet);
|
|
1194
|
-
|
|
1195
|
-
// The value of `ss` should not have be recomputed, so it should still be 3.
|
|
1196
|
-
expect(ss.memoized().count).toBe(3);
|
|
1197
|
-
// But that of the clone should have changed.
|
|
1198
|
-
expect(cloned.memoized().count).toBe(4);
|
|
1199
|
-
expect(sets).toStrictEqual(['{}', '{ t { v1 } }', '{ t { v1 v3 } }', '{ t { v1 v3 v2 } }']);
|
|
1200
|
-
|
|
1201
|
-
// And here we make sure that if we update the fist selection, we don't have v3 in the set received
|
|
1202
|
-
ss.updates().add(parseOperation(schema, `{ t { v4 } }`).selectionSet);
|
|
1203
|
-
// Here, only `ss` memoized value has been recomputed. But since both increment the same `calls` variable,
|
|
1204
|
-
// the total count should be 5 (even if the previous count for `ss` was only 3).
|
|
1205
|
-
expect(ss.memoized().count).toBe(5);
|
|
1206
|
-
expect(cloned.memoized().count).toBe(4);
|
|
1207
|
-
expect(sets).toStrictEqual(['{}', '{ t { v1 } }', '{ t { v1 v3 } }', '{ t { v1 v3 v2 } }', '{ t { v1 v3 v4 } }']);
|
|
1208
|
-
});
|
|
1209
|
-
});
|
|
1210
|
-
|
|
1211
|
-
describe('unsatisfiable branches removal', () => {
|
|
1212
|
-
const schema = parseSchema(`
|
|
1213
|
-
type Query {
|
|
1214
|
-
i: I
|
|
1215
|
-
j: J
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
interface I {
|
|
1219
|
-
a: Int
|
|
1220
|
-
b: Int
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
interface J {
|
|
1224
|
-
b: Int
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
type T1 implements I & J {
|
|
1228
|
-
a: Int
|
|
1229
|
-
b: Int
|
|
1230
|
-
c: Int
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
type T2 implements I {
|
|
1234
|
-
a: Int
|
|
1235
|
-
b: Int
|
|
1236
|
-
d: Int
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
type T3 implements J {
|
|
1240
|
-
a: Int
|
|
1241
|
-
b: Int
|
|
1242
|
-
d: Int
|
|
1243
|
-
}
|
|
1244
|
-
`);
|
|
1245
|
-
|
|
1246
|
-
const withoutUnsatisfiableBranches = (op: string) => {
|
|
1247
|
-
return parseOperation(schema, op).trimUnsatisfiableBranches().toString(false, false)
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
it.each([
|
|
1252
|
-
'{ i { a } }',
|
|
1253
|
-
'{ i { ... on T1 { a b c } } }',
|
|
1254
|
-
])('is identity if there is no unsatisfiable branches', (op) => {
|
|
1255
|
-
expect(withoutUnsatisfiableBranches(op)).toBe(op);
|
|
1256
|
-
});
|
|
1257
|
-
|
|
1258
|
-
it.each([
|
|
1259
|
-
{ input: '{ i { ... on I { a } } }', output: '{ i { a } }' },
|
|
1260
|
-
{ input: '{ i { ... on T1 { ... on I { a b } } } }', output: '{ i { ... on T1 { a b } } }' },
|
|
1261
|
-
{ input: '{ i { ... on I { a ... on T2 { d } } } }', output: '{ i { a ... on T2 { d } } }' },
|
|
1262
|
-
{ input: '{ i { ... on T2 { ... on I { a ... on J { b } } } } }', output: '{ i { ... on T2 { a } } }' },
|
|
1263
|
-
])('removes unsatisfiable branches', ({input, output}) => {
|
|
1264
|
-
expect(withoutUnsatisfiableBranches(input)).toBe(output);
|
|
1265
|
-
});
|
|
1266
|
-
});
|