@apollo/federation-internals 2.4.0 → 2.4.1
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/CHANGELOG.md +22 -0
- package/dist/coreSpec.js +1 -1
- package/dist/coreSpec.js.map +1 -1
- package/dist/operations.d.ts +92 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +182 -46
- package/dist/operations.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/operations.test.ts +598 -45
- package/src/__tests__/schemaUpgrader.test.ts +1 -1
- package/src/operations.ts +305 -99
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -32,6 +32,34 @@ function astSSet(...selections: SelectionNode[]): SelectionSetNode {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
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
|
+
|
|
35
63
|
test('handles fragments using other fragments', () => {
|
|
36
64
|
const schema = parseSchema(`
|
|
37
65
|
type Query {
|
|
@@ -126,9 +154,8 @@ describe('fragments optimization', () => {
|
|
|
126
154
|
`);
|
|
127
155
|
|
|
128
156
|
const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
|
|
129
|
-
// Note that we
|
|
130
|
-
//
|
|
131
|
-
// make the query bigger).
|
|
157
|
+
// Note that while we didn't use `onU` for `t` in the query, it's technically ok to use
|
|
158
|
+
// it and it makes the query smaller, so it gets used.
|
|
132
159
|
expect(optimized.toString()).toMatchString(`
|
|
133
160
|
fragment OnT1 on T1 {
|
|
134
161
|
a
|
|
@@ -144,15 +171,17 @@ describe('fragments optimization', () => {
|
|
|
144
171
|
b
|
|
145
172
|
}
|
|
146
173
|
|
|
174
|
+
fragment OnU on U {
|
|
175
|
+
...OnI
|
|
176
|
+
...OnT1
|
|
177
|
+
...OnT2
|
|
178
|
+
}
|
|
179
|
+
|
|
147
180
|
{
|
|
148
181
|
t {
|
|
149
|
-
...
|
|
150
|
-
...OnT2
|
|
151
|
-
...OnI
|
|
182
|
+
...OnU
|
|
152
183
|
u {
|
|
153
|
-
...
|
|
154
|
-
...OnT1
|
|
155
|
-
...OnT2
|
|
184
|
+
...OnU
|
|
156
185
|
}
|
|
157
186
|
}
|
|
158
187
|
}
|
|
@@ -176,69 +205,537 @@ describe('fragments optimization', () => {
|
|
|
176
205
|
}
|
|
177
206
|
`);
|
|
178
207
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
208
|
+
testFragmentsRoundtrip({
|
|
209
|
+
schema,
|
|
210
|
+
query: `
|
|
211
|
+
fragment OnT1 on T1 {
|
|
212
|
+
t2 {
|
|
213
|
+
x
|
|
214
|
+
}
|
|
183
215
|
}
|
|
184
|
-
}
|
|
185
216
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
query {
|
|
218
|
+
t1a {
|
|
219
|
+
...OnT1
|
|
220
|
+
t2 {
|
|
221
|
+
y
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
t2a {
|
|
225
|
+
...OnT1
|
|
191
226
|
}
|
|
192
227
|
}
|
|
193
|
-
|
|
194
|
-
|
|
228
|
+
`,
|
|
229
|
+
expanded: `
|
|
230
|
+
{
|
|
231
|
+
t1a {
|
|
232
|
+
t2 {
|
|
233
|
+
x
|
|
234
|
+
y
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
t2a {
|
|
238
|
+
t2 {
|
|
239
|
+
x
|
|
240
|
+
}
|
|
241
|
+
}
|
|
195
242
|
}
|
|
243
|
+
`,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('handles nested fragments with field intersection', () => {
|
|
248
|
+
const schema = parseSchema(`
|
|
249
|
+
type Query {
|
|
250
|
+
t: T
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
type T {
|
|
254
|
+
a: A
|
|
255
|
+
b: Int
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
type A {
|
|
259
|
+
x: String
|
|
260
|
+
y: String
|
|
261
|
+
z: String
|
|
196
262
|
}
|
|
197
263
|
`);
|
|
198
264
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
265
|
+
|
|
266
|
+
// The subtlety here is that `FA` contains `__typename` and so after we're reused it, the
|
|
267
|
+
// selection will look like:
|
|
268
|
+
// {
|
|
269
|
+
// t {
|
|
270
|
+
// a {
|
|
271
|
+
// ...FA
|
|
272
|
+
// }
|
|
273
|
+
// }
|
|
274
|
+
// }
|
|
275
|
+
// But to recognize that `FT` can be reused from there, we need to be able to see that
|
|
276
|
+
// the `__typename` that `FT` wants is inside `FA` (and since FA applies on the parent type `A`
|
|
277
|
+
// directly, it is fine to reuse).
|
|
278
|
+
testFragmentsRoundtrip({
|
|
279
|
+
schema,
|
|
280
|
+
query: `
|
|
281
|
+
fragment FA on A {
|
|
282
|
+
__typename
|
|
283
|
+
x
|
|
284
|
+
y
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
fragment FT on T {
|
|
288
|
+
a {
|
|
289
|
+
__typename
|
|
290
|
+
...FA
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
query {
|
|
295
|
+
t {
|
|
296
|
+
...FT
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
`,
|
|
300
|
+
expanded: `
|
|
301
|
+
{
|
|
302
|
+
t {
|
|
303
|
+
a {
|
|
304
|
+
__typename
|
|
205
305
|
x
|
|
306
|
+
y
|
|
206
307
|
}
|
|
207
308
|
}
|
|
208
|
-
|
|
209
|
-
|
|
309
|
+
}
|
|
310
|
+
`,
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('handles fragment matching subset of field selection', () => {
|
|
315
|
+
const schema = parseSchema(`
|
|
316
|
+
type Query {
|
|
317
|
+
t: T
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
type T {
|
|
321
|
+
a: String
|
|
322
|
+
b: B
|
|
323
|
+
c: Int
|
|
324
|
+
d: D
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
type B {
|
|
328
|
+
x: String
|
|
329
|
+
y: String
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
type D {
|
|
333
|
+
m: String
|
|
334
|
+
n: String
|
|
335
|
+
}
|
|
336
|
+
`);
|
|
337
|
+
|
|
338
|
+
testFragmentsRoundtrip({
|
|
339
|
+
schema,
|
|
340
|
+
query: `
|
|
341
|
+
fragment FragT on T {
|
|
342
|
+
b {
|
|
343
|
+
__typename
|
|
344
|
+
x
|
|
345
|
+
}
|
|
346
|
+
c
|
|
347
|
+
d {
|
|
348
|
+
m
|
|
210
349
|
}
|
|
211
350
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
351
|
+
|
|
352
|
+
{
|
|
353
|
+
t {
|
|
354
|
+
...FragT
|
|
355
|
+
d {
|
|
356
|
+
n
|
|
357
|
+
}
|
|
358
|
+
a
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
`,
|
|
362
|
+
expanded: `
|
|
363
|
+
{
|
|
364
|
+
t {
|
|
365
|
+
b {
|
|
366
|
+
__typename
|
|
215
367
|
x
|
|
216
368
|
}
|
|
369
|
+
c
|
|
370
|
+
d {
|
|
371
|
+
m
|
|
372
|
+
n
|
|
373
|
+
}
|
|
374
|
+
a
|
|
217
375
|
}
|
|
218
376
|
}
|
|
377
|
+
`,
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('handles fragment matching subset of inline fragment selection', () => {
|
|
382
|
+
// Pretty much the same test than the previous one, but matching inside a fragment selection inside
|
|
383
|
+
// of inside a field selection.
|
|
384
|
+
const schema = parseSchema(`
|
|
385
|
+
type Query {
|
|
386
|
+
i: I
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
interface I {
|
|
390
|
+
a: String
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
type T {
|
|
394
|
+
a: String
|
|
395
|
+
b: B
|
|
396
|
+
c: Int
|
|
397
|
+
d: D
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
type B {
|
|
401
|
+
x: String
|
|
402
|
+
y: String
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
type D {
|
|
406
|
+
m: String
|
|
407
|
+
n: String
|
|
219
408
|
}
|
|
220
409
|
`);
|
|
221
410
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
411
|
+
testFragmentsRoundtrip({
|
|
412
|
+
schema,
|
|
413
|
+
query: `
|
|
414
|
+
fragment FragT on T {
|
|
415
|
+
b {
|
|
416
|
+
__typename
|
|
417
|
+
x
|
|
418
|
+
}
|
|
419
|
+
c
|
|
420
|
+
d {
|
|
421
|
+
m
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
{
|
|
426
|
+
i {
|
|
427
|
+
... on T {
|
|
428
|
+
...FragT
|
|
429
|
+
d {
|
|
430
|
+
n
|
|
431
|
+
}
|
|
432
|
+
a
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
`,
|
|
437
|
+
expanded: `
|
|
438
|
+
{
|
|
439
|
+
i {
|
|
440
|
+
... on T {
|
|
441
|
+
b {
|
|
442
|
+
__typename
|
|
443
|
+
x
|
|
444
|
+
}
|
|
445
|
+
c
|
|
446
|
+
d {
|
|
447
|
+
m
|
|
448
|
+
n
|
|
449
|
+
}
|
|
450
|
+
a
|
|
451
|
+
}
|
|
452
|
+
}
|
|
227
453
|
}
|
|
454
|
+
`,
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('intersecting fragments', () => {
|
|
459
|
+
const schema = parseSchema(`
|
|
460
|
+
type Query {
|
|
461
|
+
t: T
|
|
228
462
|
}
|
|
229
463
|
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
464
|
+
type T {
|
|
465
|
+
a: String
|
|
466
|
+
b: B
|
|
467
|
+
c: Int
|
|
468
|
+
d: D
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
type B {
|
|
472
|
+
x: String
|
|
473
|
+
y: String
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
type D {
|
|
477
|
+
m: String
|
|
478
|
+
n: String
|
|
479
|
+
}
|
|
480
|
+
`);
|
|
481
|
+
|
|
482
|
+
testFragmentsRoundtrip({
|
|
483
|
+
schema,
|
|
484
|
+
// Note: the code that reuse fragments iterates on fragments in the order they are defined in the document, but when it reuse
|
|
485
|
+
// a fragment, it puts it at the beginning of the selection (somewhat random, it just feel often easier to read), so the net
|
|
486
|
+
// effect on this example is that `Frag2`, which will be reused after `Frag1` will appear first in the re-optimized selection.
|
|
487
|
+
// So we put it first in the input too so that input and output actually match (the `testFragmentsRoundtrip` compares strings,
|
|
488
|
+
// so it is sensible to ordering; we could theoretically use `Operation.equals` instead of string equality, which wouldn't
|
|
489
|
+
// really on ordering, but `Operation.equals` is not entirely trivial and comparing strings make problem a bit more obvious).
|
|
490
|
+
query: `
|
|
491
|
+
fragment Frag1 on T {
|
|
492
|
+
b {
|
|
493
|
+
x
|
|
494
|
+
}
|
|
495
|
+
c
|
|
496
|
+
d {
|
|
497
|
+
m
|
|
235
498
|
}
|
|
236
499
|
}
|
|
237
|
-
|
|
238
|
-
|
|
500
|
+
|
|
501
|
+
fragment Frag2 on T {
|
|
502
|
+
a
|
|
503
|
+
b {
|
|
504
|
+
__typename
|
|
505
|
+
x
|
|
506
|
+
}
|
|
507
|
+
d {
|
|
508
|
+
m
|
|
509
|
+
n
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
{
|
|
514
|
+
t {
|
|
515
|
+
...Frag2
|
|
516
|
+
...Frag1
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
`,
|
|
520
|
+
expanded: `
|
|
521
|
+
{
|
|
522
|
+
t {
|
|
523
|
+
a
|
|
524
|
+
b {
|
|
525
|
+
__typename
|
|
526
|
+
x
|
|
527
|
+
}
|
|
528
|
+
d {
|
|
529
|
+
m
|
|
530
|
+
n
|
|
531
|
+
}
|
|
532
|
+
c
|
|
533
|
+
}
|
|
239
534
|
}
|
|
535
|
+
`,
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test('fragments whose application makes a type condition trivial', () => {
|
|
540
|
+
const schema = parseSchema(`
|
|
541
|
+
type Query {
|
|
542
|
+
t: T
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
interface I {
|
|
546
|
+
x: String
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
type T implements I {
|
|
550
|
+
x: String
|
|
551
|
+
a: String
|
|
240
552
|
}
|
|
241
553
|
`);
|
|
554
|
+
|
|
555
|
+
testFragmentsRoundtrip({
|
|
556
|
+
schema,
|
|
557
|
+
query: `
|
|
558
|
+
fragment FragI on I {
|
|
559
|
+
x
|
|
560
|
+
... on T {
|
|
561
|
+
a
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
{
|
|
566
|
+
t {
|
|
567
|
+
...FragI
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
`,
|
|
571
|
+
expanded: `
|
|
572
|
+
{
|
|
573
|
+
t {
|
|
574
|
+
x
|
|
575
|
+
a
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
`,
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
describe('applied directives', () => {
|
|
583
|
+
test('reuse fragments with directives on the fragment, but only when there is those directives', () => {
|
|
584
|
+
const schema = parseSchema(`
|
|
585
|
+
type Query {
|
|
586
|
+
t1: T
|
|
587
|
+
t2: T
|
|
588
|
+
t3: T
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
type T {
|
|
592
|
+
a: Int
|
|
593
|
+
b: Int
|
|
594
|
+
c: Int
|
|
595
|
+
d: Int
|
|
596
|
+
}
|
|
597
|
+
`);
|
|
598
|
+
|
|
599
|
+
testFragmentsRoundtrip({
|
|
600
|
+
schema,
|
|
601
|
+
query: `
|
|
602
|
+
fragment DirectiveOnDef on T @include(if: $cond1) {
|
|
603
|
+
a
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
607
|
+
t1 {
|
|
608
|
+
...DirectiveOnDef
|
|
609
|
+
}
|
|
610
|
+
t2 {
|
|
611
|
+
... on T @include(if: $cond2) {
|
|
612
|
+
a
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
t3 {
|
|
616
|
+
...DirectiveOnDef @include(if: $cond2)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
`,
|
|
620
|
+
expanded: `
|
|
621
|
+
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
622
|
+
t1 {
|
|
623
|
+
... on T @include(if: $cond1) {
|
|
624
|
+
a
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
t2 {
|
|
628
|
+
... on T @include(if: $cond2) {
|
|
629
|
+
a
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
t3 {
|
|
633
|
+
... on T @include(if: $cond1) @include(if: $cond2) {
|
|
634
|
+
a
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
`,
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test('reuse fragments with directives in the fragment selection, but only when there is those directives', () => {
|
|
643
|
+
const schema = parseSchema(`
|
|
644
|
+
type Query {
|
|
645
|
+
t1: T
|
|
646
|
+
t2: T
|
|
647
|
+
t3: T
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
type T {
|
|
651
|
+
a: Int
|
|
652
|
+
b: Int
|
|
653
|
+
c: Int
|
|
654
|
+
d: Int
|
|
655
|
+
}
|
|
656
|
+
`);
|
|
657
|
+
|
|
658
|
+
testFragmentsRoundtrip({
|
|
659
|
+
schema,
|
|
660
|
+
query: `
|
|
661
|
+
fragment DirectiveInDef on T {
|
|
662
|
+
a @include(if: $cond1)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
666
|
+
t1 {
|
|
667
|
+
a
|
|
668
|
+
}
|
|
669
|
+
t2 {
|
|
670
|
+
...DirectiveInDef
|
|
671
|
+
}
|
|
672
|
+
t3 {
|
|
673
|
+
a @include(if: $cond2)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
`,
|
|
677
|
+
expanded: `
|
|
678
|
+
query myQuery($cond1: Boolean!, $cond2: Boolean!) {
|
|
679
|
+
t1 {
|
|
680
|
+
a
|
|
681
|
+
}
|
|
682
|
+
t2 {
|
|
683
|
+
a @include(if: $cond1)
|
|
684
|
+
}
|
|
685
|
+
t3 {
|
|
686
|
+
a @include(if: $cond2)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
`,
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test('reuse fragments with directives on spread, but only when there is those directives', () => {
|
|
694
|
+
const schema = parseSchema(`
|
|
695
|
+
type Query {
|
|
696
|
+
t1: T
|
|
697
|
+
t2: T
|
|
698
|
+
t3: T
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
type T {
|
|
702
|
+
a: Int
|
|
703
|
+
b: Int
|
|
704
|
+
c: Int
|
|
705
|
+
d: Int
|
|
706
|
+
}
|
|
707
|
+
`);
|
|
708
|
+
|
|
709
|
+
testFragmentsRoundtrip({
|
|
710
|
+
schema,
|
|
711
|
+
query: `
|
|
712
|
+
fragment NoDirectiveDef on T {
|
|
713
|
+
a
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
query myQuery($cond1: Boolean!) {
|
|
717
|
+
t1 {
|
|
718
|
+
...NoDirectiveDef
|
|
719
|
+
}
|
|
720
|
+
t2 {
|
|
721
|
+
...NoDirectiveDef @include(if: $cond1)
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
`,
|
|
725
|
+
expanded: `
|
|
726
|
+
query myQuery($cond1: Boolean!) {
|
|
727
|
+
t1 {
|
|
728
|
+
a
|
|
729
|
+
}
|
|
730
|
+
t2 {
|
|
731
|
+
... on T @include(if: $cond1) {
|
|
732
|
+
a
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
`,
|
|
737
|
+
});
|
|
738
|
+
});
|
|
242
739
|
});
|
|
243
740
|
});
|
|
244
741
|
|
|
@@ -477,7 +974,6 @@ describe('basic operations', () => {
|
|
|
477
974
|
})
|
|
478
975
|
});
|
|
479
976
|
|
|
480
|
-
|
|
481
977
|
describe('MutableSelectionSet', () => {
|
|
482
978
|
test('memoizer', () => {
|
|
483
979
|
const schema = parseSchema(`
|
|
@@ -546,3 +1042,60 @@ describe('MutableSelectionSet', () => {
|
|
|
546
1042
|
expect(sets).toStrictEqual(['{}', '{ t { v1 } }', '{ t { v1 v3 } }', '{ t { v1 v3 v2 } }', '{ t { v1 v3 v4 } }']);
|
|
547
1043
|
});
|
|
548
1044
|
});
|
|
1045
|
+
|
|
1046
|
+
describe('unsatisfiable branches removal', () => {
|
|
1047
|
+
const schema = parseSchema(`
|
|
1048
|
+
type Query {
|
|
1049
|
+
i: I
|
|
1050
|
+
j: J
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
interface I {
|
|
1054
|
+
a: Int
|
|
1055
|
+
b: Int
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
interface J {
|
|
1059
|
+
b: Int
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
type T1 implements I & J {
|
|
1063
|
+
a: Int
|
|
1064
|
+
b: Int
|
|
1065
|
+
c: Int
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
type T2 implements I {
|
|
1069
|
+
a: Int
|
|
1070
|
+
b: Int
|
|
1071
|
+
d: Int
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
type T3 implements J {
|
|
1075
|
+
a: Int
|
|
1076
|
+
b: Int
|
|
1077
|
+
d: Int
|
|
1078
|
+
}
|
|
1079
|
+
`);
|
|
1080
|
+
|
|
1081
|
+
const withoutUnsatisfiableBranches = (op: string) => {
|
|
1082
|
+
return parseOperation(schema, op).trimUnsatisfiableBranches().toString(false, false)
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
it.each([
|
|
1087
|
+
'{ i { a } }',
|
|
1088
|
+
'{ i { ... on T1 { a b c } } }',
|
|
1089
|
+
])('is identity if there is no unsatisfiable branches', (op) => {
|
|
1090
|
+
expect(withoutUnsatisfiableBranches(op)).toBe(op);
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
it.each([
|
|
1094
|
+
{ input: '{ i { ... on I { a } } }', output: '{ i { a } }' },
|
|
1095
|
+
{ input: '{ i { ... on T1 { ... on I { a b } } } }', output: '{ i { ... on T1 { a b } } }' },
|
|
1096
|
+
{ input: '{ i { ... on I { a ... on T2 { d } } } }', output: '{ i { a ... on T2 { d } } }' },
|
|
1097
|
+
{ input: '{ i { ... on T2 { ... on I { a ... on J { b } } } } }', output: '{ i { ... on T2 { a } } }' },
|
|
1098
|
+
])('removes unsatisfiable branches', ({input, output}) => {
|
|
1099
|
+
expect(withoutUnsatisfiableBranches(input)).toBe(output);
|
|
1100
|
+
});
|
|
1101
|
+
});
|
|
@@ -212,7 +212,7 @@ test('reject @interfaceObject usage if not all subgraphs are fed2', () => {
|
|
|
212
212
|
|
|
213
213
|
const s1 = `
|
|
214
214
|
extend schema
|
|
215
|
-
@link(url: "https://specs.apollo.dev/federation/v2.
|
|
215
|
+
@link(url: "https://specs.apollo.dev/federation/v2.3", import: [ "@key", "@interfaceObject"])
|
|
216
216
|
|
|
217
217
|
type Query {
|
|
218
218
|
a: A
|