Total Complexity | 42 |
Total Lines | 1331 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like OverlappingFieldsCanBeMergedTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use OverlappingFieldsCanBeMergedTest, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
15 | class OverlappingFieldsCanBeMergedTest extends ValidatorTestCase |
||
16 | { |
||
17 | // Validate: Overlapping fields can be merged |
||
18 | /** |
||
19 | * @see it('unique fields') |
||
20 | */ |
||
21 | public function testUniqueFields() : void |
||
22 | { |
||
23 | $this->expectPassesRule( |
||
24 | new OverlappingFieldsCanBeMerged(), |
||
25 | ' |
||
26 | fragment uniqueFields on Dog { |
||
27 | name |
||
28 | nickname |
||
29 | } |
||
30 | ' |
||
31 | ); |
||
32 | } |
||
33 | |||
34 | /** |
||
35 | * @see it('identical fields') |
||
36 | */ |
||
37 | public function testIdenticalFields() : void |
||
42 | fragment mergeIdenticalFields on Dog { |
||
43 | name |
||
44 | name |
||
45 | } |
||
46 | ' |
||
47 | ); |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * @see it('identical fields with identical args') |
||
52 | */ |
||
53 | public function testIdenticalFieldsWithIdenticalArgs() : void |
||
54 | { |
||
55 | $this->expectPassesRule( |
||
56 | new OverlappingFieldsCanBeMerged(), |
||
57 | ' |
||
58 | fragment mergeIdenticalFieldsWithIdenticalArgs on Dog { |
||
59 | doesKnowCommand(dogCommand: SIT) |
||
60 | doesKnowCommand(dogCommand: SIT) |
||
61 | } |
||
62 | ' |
||
63 | ); |
||
64 | } |
||
65 | |||
66 | /** |
||
67 | * @see it('identical fields with identical directives') |
||
68 | */ |
||
69 | public function testIdenticalFieldsWithIdenticalDirectives() : void |
||
70 | { |
||
71 | $this->expectPassesRule( |
||
72 | new OverlappingFieldsCanBeMerged(), |
||
73 | ' |
||
74 | fragment mergeSameFieldsWithSameDirectives on Dog { |
||
75 | name @include(if: true) |
||
76 | name @include(if: true) |
||
77 | } |
||
78 | ' |
||
79 | ); |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * @see it('different args with different aliases') |
||
84 | */ |
||
85 | public function testDifferentArgsWithDifferentAliases() : void |
||
86 | { |
||
87 | $this->expectPassesRule( |
||
88 | new OverlappingFieldsCanBeMerged(), |
||
89 | ' |
||
90 | fragment differentArgsWithDifferentAliases on Dog { |
||
91 | knowsSit : doesKnowCommand(dogCommand: SIT) |
||
92 | knowsDown : doesKnowCommand(dogCommand: DOWN) |
||
93 | } |
||
94 | ' |
||
95 | ); |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * @see it('different directives with different aliases') |
||
100 | */ |
||
101 | public function testDifferentDirectivesWithDifferentAliases() : void |
||
102 | { |
||
103 | $this->expectPassesRule( |
||
104 | new OverlappingFieldsCanBeMerged(), |
||
105 | ' |
||
106 | fragment differentDirectivesWithDifferentAliases on Dog { |
||
107 | nameIfTrue : name @include(if: true) |
||
108 | nameIfFalse : name @include(if: false) |
||
109 | } |
||
110 | ' |
||
111 | ); |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * @see it('different skip/include directives accepted') |
||
116 | */ |
||
117 | public function testDifferentSkipIncludeDirectivesAccepted() : void |
||
118 | { |
||
119 | // Note: Differing skip/include directives don't create an ambiguous return |
||
120 | // value and are acceptable in conditions where differing runtime values |
||
121 | // may have the same desired effect of including or skipping a field. |
||
122 | $this->expectPassesRule( |
||
123 | new OverlappingFieldsCanBeMerged(), |
||
124 | ' |
||
125 | fragment differentDirectivesWithDifferentAliases on Dog { |
||
126 | name @include(if: true) |
||
127 | name @include(if: false) |
||
128 | } |
||
129 | ' |
||
130 | ); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * @see it('Same aliases with different field targets') |
||
135 | */ |
||
136 | public function testSameAliasesWithDifferentFieldTargets() : void |
||
137 | { |
||
138 | $this->expectFailsRule( |
||
139 | new OverlappingFieldsCanBeMerged(), |
||
140 | ' |
||
141 | fragment sameAliasesWithDifferentFieldTargets on Dog { |
||
142 | fido : name |
||
143 | fido : nickname |
||
144 | } |
||
145 | ', |
||
146 | [ |
||
147 | FormattedError::create( |
||
|
|||
148 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
149 | 'fido', |
||
150 | 'name and nickname are different fields' |
||
151 | ), |
||
152 | [new SourceLocation(3, 9), new SourceLocation(4, 9)] |
||
153 | ), |
||
154 | ] |
||
155 | ); |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * @see it('Same aliases allowed on non-overlapping fields') |
||
160 | */ |
||
161 | public function testSameAliasesAllowedOnNonOverlappingFields() : void |
||
162 | { |
||
163 | // This is valid since no object can be both a "Dog" and a "Cat", thus |
||
164 | // these fields can never overlap. |
||
165 | $this->expectPassesRule( |
||
166 | new OverlappingFieldsCanBeMerged(), |
||
167 | ' |
||
168 | fragment sameAliasesWithDifferentFieldTargets on Pet { |
||
169 | ... on Dog { |
||
170 | name |
||
171 | } |
||
172 | ... on Cat { |
||
173 | name: nickname |
||
174 | } |
||
175 | } |
||
176 | ' |
||
177 | ); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * @see it('Alias masking direct field access') |
||
182 | */ |
||
183 | public function testAliasMaskingDirectFieldAccess() : void |
||
184 | { |
||
185 | $this->expectFailsRule( |
||
186 | new OverlappingFieldsCanBeMerged(), |
||
187 | ' |
||
188 | fragment aliasMaskingDirectFieldAccess on Dog { |
||
189 | name : nickname |
||
190 | name |
||
191 | } |
||
192 | ', |
||
193 | [ |
||
194 | FormattedError::create( |
||
195 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
196 | 'name', |
||
197 | 'nickname and name are different fields' |
||
198 | ), |
||
199 | [new SourceLocation(3, 9), new SourceLocation(4, 9)] |
||
200 | ), |
||
201 | ] |
||
202 | ); |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * @see it('different args, second adds an argument') |
||
207 | */ |
||
208 | public function testDifferentArgsSecondAddsAnArgument() : void |
||
209 | { |
||
210 | $this->expectFailsRule( |
||
211 | new OverlappingFieldsCanBeMerged(), |
||
212 | ' |
||
213 | fragment conflictingArgs on Dog { |
||
214 | doesKnowCommand |
||
215 | doesKnowCommand(dogCommand: HEEL) |
||
216 | } |
||
217 | ', |
||
218 | [ |
||
219 | FormattedError::create( |
||
220 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
221 | 'doesKnowCommand', |
||
222 | 'they have differing arguments' |
||
223 | ), |
||
224 | [new SourceLocation(3, 9), new SourceLocation(4, 9)] |
||
225 | ), |
||
226 | ] |
||
227 | ); |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * @see it('different args, second missing an argument') |
||
232 | */ |
||
233 | public function testDifferentArgsSecondMissingAnArgument() : void |
||
234 | { |
||
235 | $this->expectFailsRule( |
||
236 | new OverlappingFieldsCanBeMerged(), |
||
237 | ' |
||
238 | fragment conflictingArgs on Dog { |
||
239 | doesKnowCommand(dogCommand: SIT) |
||
240 | doesKnowCommand |
||
241 | } |
||
242 | ', |
||
243 | [ |
||
244 | FormattedError::create( |
||
245 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
246 | 'doesKnowCommand', |
||
247 | 'they have differing arguments' |
||
248 | ), |
||
249 | [new SourceLocation(3, 9), new SourceLocation(4, 9)] |
||
250 | ), |
||
251 | ] |
||
252 | ); |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * @see it('conflicting args') |
||
257 | */ |
||
258 | public function testConflictingArgs() : void |
||
259 | { |
||
260 | $this->expectFailsRule( |
||
261 | new OverlappingFieldsCanBeMerged(), |
||
262 | ' |
||
263 | fragment conflictingArgs on Dog { |
||
264 | doesKnowCommand(dogCommand: SIT) |
||
265 | doesKnowCommand(dogCommand: HEEL) |
||
266 | } |
||
267 | ', |
||
268 | [ |
||
269 | FormattedError::create( |
||
270 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
271 | 'doesKnowCommand', |
||
272 | 'they have differing arguments' |
||
273 | ), |
||
274 | [new SourceLocation(3, 9), new SourceLocation(4, 9)] |
||
275 | ), |
||
276 | ] |
||
277 | ); |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * @see it('allows different args where no conflict is possible') |
||
282 | */ |
||
283 | public function testAllowsDifferentArgsWhereNoConflictIsPossible() : void |
||
284 | { |
||
285 | // This is valid since no object can be both a "Dog" and a "Cat", thus |
||
286 | // these fields can never overlap. |
||
287 | $this->expectPassesRule( |
||
288 | new OverlappingFieldsCanBeMerged(), |
||
289 | ' |
||
290 | fragment conflictingArgs on Pet { |
||
291 | ... on Dog { |
||
292 | name(surname: true) |
||
293 | } |
||
294 | ... on Cat { |
||
295 | name |
||
296 | } |
||
297 | } |
||
298 | ' |
||
299 | ); |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * @see it('encounters conflict in fragments') |
||
304 | */ |
||
305 | public function testEncountersConflictInFragments() : void |
||
306 | { |
||
307 | $this->expectFailsRule( |
||
308 | new OverlappingFieldsCanBeMerged(), |
||
309 | ' |
||
310 | { |
||
311 | ...A |
||
312 | ...B |
||
313 | } |
||
314 | fragment A on Type { |
||
315 | x: a |
||
316 | } |
||
317 | fragment B on Type { |
||
318 | x: b |
||
319 | } |
||
320 | ', |
||
321 | [ |
||
322 | FormattedError::create( |
||
323 | OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'a and b are different fields'), |
||
324 | [new SourceLocation(7, 9), new SourceLocation(10, 9)] |
||
325 | ), |
||
326 | ] |
||
327 | ); |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * @see it('reports each conflict once') |
||
332 | */ |
||
333 | public function testReportsEachConflictOnce() : void |
||
334 | { |
||
335 | $this->expectFailsRule( |
||
336 | new OverlappingFieldsCanBeMerged(), |
||
337 | ' |
||
338 | { |
||
339 | f1 { |
||
340 | ...A |
||
341 | ...B |
||
342 | } |
||
343 | f2 { |
||
344 | ...B |
||
345 | ...A |
||
346 | } |
||
347 | f3 { |
||
348 | ...A |
||
349 | ...B |
||
350 | x: c |
||
351 | } |
||
352 | } |
||
353 | fragment A on Type { |
||
354 | x: a |
||
355 | } |
||
356 | fragment B on Type { |
||
357 | x: b |
||
358 | } |
||
359 | ', |
||
360 | [ |
||
361 | FormattedError::create( |
||
362 | OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'a and b are different fields'), |
||
363 | [new SourceLocation(18, 9), new SourceLocation(21, 9)] |
||
364 | ), |
||
365 | FormattedError::create( |
||
366 | OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'c and a are different fields'), |
||
367 | [new SourceLocation(14, 11), new SourceLocation(18, 9)] |
||
368 | ), |
||
369 | FormattedError::create( |
||
370 | OverlappingFieldsCanBeMerged::fieldsConflictMessage('x', 'c and b are different fields'), |
||
371 | [new SourceLocation(14, 11), new SourceLocation(21, 9)] |
||
372 | ), |
||
373 | ] |
||
374 | ); |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * @see it('deep conflict') |
||
379 | */ |
||
380 | public function testDeepConflict() : void |
||
381 | { |
||
382 | $this->expectFailsRule( |
||
383 | new OverlappingFieldsCanBeMerged(), |
||
384 | ' |
||
385 | { |
||
386 | field { |
||
387 | x: a |
||
388 | }, |
||
389 | field { |
||
390 | x: b |
||
391 | } |
||
392 | } |
||
393 | ', |
||
394 | [ |
||
395 | FormattedError::create( |
||
396 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
397 | 'field', |
||
398 | [['x', 'a and b are different fields']] |
||
399 | ), |
||
400 | [ |
||
401 | new SourceLocation(3, 9), |
||
402 | new SourceLocation(4, 11), |
||
403 | new SourceLocation(6, 9), |
||
404 | new SourceLocation(7, 11), |
||
405 | ] |
||
406 | ), |
||
407 | ] |
||
408 | ); |
||
409 | } |
||
410 | |||
411 | /** |
||
412 | * @see it('deep conflict with multiple issues') |
||
413 | */ |
||
414 | public function testDeepConflictWithMultipleIssues() : void |
||
446 | ] |
||
447 | ), |
||
448 | ] |
||
449 | ); |
||
450 | } |
||
451 | |||
452 | /** |
||
453 | * @see it('very deep conflict') |
||
454 | */ |
||
455 | public function testVeryDeepConflict() : void |
||
456 | { |
||
457 | $this->expectFailsRule( |
||
458 | new OverlappingFieldsCanBeMerged(), |
||
459 | ' |
||
460 | { |
||
461 | field { |
||
462 | deepField { |
||
463 | x: a |
||
464 | } |
||
465 | }, |
||
466 | field { |
||
467 | deepField { |
||
468 | x: b |
||
469 | } |
||
470 | } |
||
471 | } |
||
472 | ', |
||
473 | [ |
||
474 | FormattedError::create( |
||
475 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
476 | 'field', |
||
477 | [['deepField', [['x', 'a and b are different fields']]]] |
||
478 | ), |
||
479 | [ |
||
480 | new SourceLocation(3, 9), |
||
481 | new SourceLocation(4, 11), |
||
482 | new SourceLocation(5, 13), |
||
483 | new SourceLocation(8, 9), |
||
484 | new SourceLocation(9, 11), |
||
485 | new SourceLocation(10, 13), |
||
486 | ] |
||
487 | ), |
||
488 | ] |
||
489 | ); |
||
490 | } |
||
491 | |||
492 | /** |
||
493 | * @see it('reports deep conflict to nearest common ancestor') |
||
494 | */ |
||
495 | public function testReportsDeepConflictToNearestCommonAncestor() : void |
||
496 | { |
||
497 | $this->expectFailsRule( |
||
498 | new OverlappingFieldsCanBeMerged(), |
||
499 | ' |
||
500 | { |
||
501 | field { |
||
502 | deepField { |
||
503 | x: a |
||
504 | } |
||
505 | deepField { |
||
506 | x: b |
||
507 | } |
||
508 | }, |
||
509 | field { |
||
510 | deepField { |
||
511 | y |
||
512 | } |
||
513 | } |
||
514 | } |
||
515 | ', |
||
516 | [ |
||
517 | FormattedError::create( |
||
518 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
519 | 'deepField', |
||
520 | [['x', 'a and b are different fields']] |
||
521 | ), |
||
522 | [ |
||
523 | new SourceLocation(4, 11), |
||
524 | new SourceLocation(5, 13), |
||
525 | new SourceLocation(7, 11), |
||
526 | new SourceLocation(8, 13), |
||
527 | ] |
||
528 | ), |
||
529 | ] |
||
530 | ); |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * @see it('reports deep conflict to nearest common ancestor in fragments') |
||
535 | */ |
||
536 | public function testReportsDeepConflictToNearestCommonAncestorInFragments() : void |
||
537 | { |
||
538 | $this->expectFailsRule( |
||
539 | new OverlappingFieldsCanBeMerged(), |
||
540 | ' |
||
541 | { |
||
542 | field { |
||
543 | ...F |
||
544 | } |
||
545 | field { |
||
546 | ...F |
||
547 | } |
||
548 | } |
||
549 | fragment F on T { |
||
550 | deepField { |
||
551 | deeperField { |
||
552 | x: a |
||
553 | } |
||
554 | deeperField { |
||
555 | x: b |
||
556 | } |
||
557 | } |
||
558 | deepField { |
||
559 | deeperField { |
||
560 | y |
||
561 | } |
||
562 | } |
||
563 | } |
||
564 | ', |
||
565 | [ |
||
566 | FormattedError::create( |
||
567 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
568 | 'deeperField', |
||
569 | [['x', 'a and b are different fields']] |
||
570 | ), |
||
571 | [ |
||
572 | new SourceLocation(12, 11), |
||
573 | new SourceLocation(13, 13), |
||
574 | new SourceLocation(15, 11), |
||
575 | new SourceLocation(16, 13), |
||
576 | ] |
||
577 | ), |
||
578 | ] |
||
579 | ); |
||
580 | } |
||
581 | |||
582 | /** |
||
583 | * @see it('reports deep conflict in nested fragments') |
||
584 | */ |
||
585 | public function testReportsDeepConflictInNestedFragments() : void |
||
586 | { |
||
587 | $this->expectFailsRule( |
||
588 | new OverlappingFieldsCanBeMerged(), |
||
589 | ' |
||
590 | { |
||
591 | field { |
||
592 | ...F |
||
593 | } |
||
594 | field { |
||
595 | ...I |
||
596 | } |
||
597 | } |
||
598 | fragment F on T { |
||
599 | x: a |
||
600 | ...G |
||
601 | } |
||
602 | fragment G on T { |
||
603 | y: c |
||
604 | } |
||
605 | fragment I on T { |
||
606 | y: d |
||
607 | ...J |
||
608 | } |
||
609 | fragment J on T { |
||
610 | x: b |
||
611 | } |
||
612 | ', |
||
613 | [ |
||
614 | FormattedError::create( |
||
615 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
616 | 'field', |
||
617 | [ |
||
618 | ['x', 'a and b are different fields'], |
||
619 | ['y', 'c and d are different fields'], |
||
620 | ] |
||
621 | ), |
||
622 | [ |
||
623 | new SourceLocation(3, 9), |
||
624 | new SourceLocation(11, 9), |
||
625 | new SourceLocation(15, 9), |
||
626 | new SourceLocation(6, 9), |
||
627 | new SourceLocation(22, 9), |
||
628 | new SourceLocation(18, 9), |
||
629 | ] |
||
630 | ), |
||
631 | ] |
||
632 | ); |
||
633 | } |
||
634 | |||
635 | /** |
||
636 | * @see it('ignores unknown fragments') |
||
637 | */ |
||
638 | public function testIgnoresUnknownFragments() : void |
||
639 | { |
||
640 | $this->expectPassesRule( |
||
641 | new OverlappingFieldsCanBeMerged(), |
||
642 | ' |
||
643 | { |
||
644 | field { |
||
645 | ...Unknown |
||
646 | ...Known |
||
647 | } |
||
648 | } |
||
649 | fragment Known on T { |
||
650 | field |
||
651 | ...OtherUnknown |
||
652 | } |
||
653 | ' |
||
654 | ); |
||
655 | } |
||
656 | |||
657 | // Describe: return types must be unambiguous |
||
658 | |||
659 | /** |
||
660 | * @see it('conflicting return types which potentially overlap') |
||
661 | */ |
||
662 | public function testConflictingReturnTypesWhichPotentiallyOverlap() : void |
||
663 | { |
||
664 | // This is invalid since an object could potentially be both the Object |
||
665 | // type IntBox and the interface type NonNullStringBox1. While that |
||
666 | // condition does not exist in the current schema, the schema could |
||
667 | // expand in the future to allow this. Thus it is invalid. |
||
668 | $this->expectFailsRuleWithSchema( |
||
669 | $this->getSchema(), |
||
670 | new OverlappingFieldsCanBeMerged(), |
||
671 | ' |
||
672 | { |
||
673 | someBox { |
||
674 | ...on IntBox { |
||
675 | scalar |
||
676 | } |
||
677 | ...on NonNullStringBox1 { |
||
678 | scalar |
||
679 | } |
||
680 | } |
||
681 | } |
||
682 | ', |
||
683 | [ |
||
684 | FormattedError::create( |
||
685 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
686 | 'scalar', |
||
687 | 'they return conflicting types Int and String!' |
||
688 | ), |
||
689 | [ |
||
690 | new SourceLocation(5, 15), |
||
691 | new SourceLocation(8, 15), |
||
692 | ] |
||
693 | ), |
||
694 | ] |
||
695 | ); |
||
696 | } |
||
697 | |||
698 | private function getSchema() |
||
699 | { |
||
700 | $StringBox = null; |
||
701 | $IntBox = null; |
||
702 | $SomeBox = null; |
||
703 | |||
704 | $SomeBox = new InterfaceType([ |
||
705 | 'name' => 'SomeBox', |
||
706 | 'fields' => function () use (&$SomeBox) { |
||
707 | return [ |
||
708 | 'deepBox' => ['type' => $SomeBox], |
||
709 | 'unrelatedField' => ['type' => Type::string()], |
||
710 | ]; |
||
711 | }, |
||
712 | ]); |
||
713 | |||
714 | $StringBox = new ObjectType([ |
||
715 | 'name' => 'StringBox', |
||
716 | 'interfaces' => [$SomeBox], |
||
717 | 'fields' => function () use (&$StringBox, &$IntBox) { |
||
718 | return [ |
||
719 | 'scalar' => ['type' => Type::string()], |
||
720 | 'deepBox' => ['type' => $StringBox], |
||
721 | 'unrelatedField' => ['type' => Type::string()], |
||
722 | 'listStringBox' => ['type' => Type::listOf($StringBox)], |
||
723 | 'stringBox' => ['type' => $StringBox], |
||
724 | 'intBox' => ['type' => $IntBox], |
||
725 | ]; |
||
726 | }, |
||
727 | ]); |
||
728 | |||
729 | $IntBox = new ObjectType([ |
||
730 | 'name' => 'IntBox', |
||
731 | 'interfaces' => [$SomeBox], |
||
732 | 'fields' => function () use (&$StringBox, &$IntBox) { |
||
733 | return [ |
||
734 | 'scalar' => ['type' => Type::int()], |
||
735 | 'deepBox' => ['type' => $IntBox], |
||
736 | 'unrelatedField' => ['type' => Type::string()], |
||
737 | 'listStringBox' => ['type' => Type::listOf($StringBox)], |
||
738 | 'stringBox' => ['type' => $StringBox], |
||
739 | 'intBox' => ['type' => $IntBox], |
||
740 | ]; |
||
741 | }, |
||
742 | ]); |
||
743 | |||
744 | $NonNullStringBox1 = new InterfaceType([ |
||
745 | 'name' => 'NonNullStringBox1', |
||
746 | 'fields' => [ |
||
747 | 'scalar' => ['type' => Type::nonNull(Type::string())], |
||
748 | ], |
||
749 | ]); |
||
750 | |||
751 | $NonNullStringBox1Impl = new ObjectType([ |
||
752 | 'name' => 'NonNullStringBox1Impl', |
||
753 | 'interfaces' => [$SomeBox, $NonNullStringBox1], |
||
754 | 'fields' => [ |
||
755 | 'scalar' => ['type' => Type::nonNull(Type::string())], |
||
756 | 'unrelatedField' => ['type' => Type::string()], |
||
757 | 'deepBox' => ['type' => $SomeBox], |
||
758 | ], |
||
759 | ]); |
||
760 | |||
761 | $NonNullStringBox2 = new InterfaceType([ |
||
762 | 'name' => 'NonNullStringBox2', |
||
763 | 'fields' => [ |
||
764 | 'scalar' => ['type' => Type::nonNull(Type::string())], |
||
765 | ], |
||
766 | ]); |
||
767 | |||
768 | $NonNullStringBox2Impl = new ObjectType([ |
||
769 | 'name' => 'NonNullStringBox2Impl', |
||
770 | 'interfaces' => [$SomeBox, $NonNullStringBox2], |
||
771 | 'fields' => [ |
||
772 | 'scalar' => ['type' => Type::nonNull(Type::string())], |
||
773 | 'unrelatedField' => ['type' => Type::string()], |
||
774 | 'deepBox' => ['type' => $SomeBox], |
||
775 | ], |
||
776 | ]); |
||
777 | |||
778 | $Connection = new ObjectType([ |
||
779 | 'name' => 'Connection', |
||
780 | 'fields' => [ |
||
781 | 'edges' => [ |
||
782 | 'type' => Type::listOf(new ObjectType([ |
||
783 | 'name' => 'Edge', |
||
784 | 'fields' => [ |
||
785 | 'node' => [ |
||
786 | 'type' => new ObjectType([ |
||
787 | 'name' => 'Node', |
||
788 | 'fields' => [ |
||
789 | 'id' => ['type' => Type::id()], |
||
790 | 'name' => ['type' => Type::string()], |
||
791 | ], |
||
792 | ]), |
||
793 | ], |
||
794 | ], |
||
795 | ])), |
||
796 | ], |
||
797 | ], |
||
798 | ]); |
||
799 | |||
800 | $schema = new Schema([ |
||
801 | 'query' => new ObjectType([ |
||
802 | 'name' => 'QueryRoot', |
||
803 | 'fields' => [ |
||
804 | 'someBox' => ['type' => $SomeBox], |
||
805 | 'connection' => ['type' => $Connection], |
||
806 | ], |
||
807 | ]), |
||
808 | 'types' => [$IntBox, $StringBox, $NonNullStringBox1Impl, $NonNullStringBox2Impl], |
||
809 | ]); |
||
810 | |||
811 | return $schema; |
||
812 | } |
||
813 | |||
814 | /** |
||
815 | * @see it('compatible return shapes on different return types') |
||
816 | */ |
||
817 | public function testCompatibleReturnShapesOnDifferentReturnTypes() : void |
||
818 | { |
||
819 | // In this case `deepBox` returns `SomeBox` in the first usage, and |
||
820 | // `StringBox` in the second usage. These return types are not the same! |
||
821 | // however this is valid because the return *shapes* are compatible. |
||
822 | $this->expectPassesRuleWithSchema( |
||
823 | $this->getSchema(), |
||
824 | new OverlappingFieldsCanBeMerged(), |
||
825 | ' |
||
826 | { |
||
827 | someBox { |
||
828 | ... on SomeBox { |
||
829 | deepBox { |
||
830 | unrelatedField |
||
831 | } |
||
832 | } |
||
833 | ... on StringBox { |
||
834 | deepBox { |
||
835 | unrelatedField |
||
836 | } |
||
837 | } |
||
838 | } |
||
839 | } |
||
840 | ' |
||
841 | ); |
||
842 | } |
||
843 | |||
844 | /** |
||
845 | * @see it('disallows differing return types despite no overlap') |
||
846 | */ |
||
847 | public function testDisallowsDifferingReturnTypesDespiteNoOverlap() : void |
||
848 | { |
||
849 | $this->expectFailsRuleWithSchema( |
||
850 | $this->getSchema(), |
||
851 | new OverlappingFieldsCanBeMerged(), |
||
852 | ' |
||
853 | { |
||
854 | someBox { |
||
855 | ... on IntBox { |
||
856 | scalar |
||
857 | } |
||
858 | ... on StringBox { |
||
859 | scalar |
||
860 | } |
||
861 | } |
||
862 | } |
||
863 | ', |
||
864 | [ |
||
865 | FormattedError::create( |
||
866 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
867 | 'scalar', |
||
868 | 'they return conflicting types Int and String' |
||
869 | ), |
||
870 | [ |
||
871 | new SourceLocation(5, 15), |
||
872 | new SourceLocation(8, 15), |
||
873 | ] |
||
874 | ), |
||
875 | ] |
||
876 | ); |
||
877 | } |
||
878 | |||
879 | /** |
||
880 | * @see it('reports correctly when a non-exclusive follows an exclusive') |
||
881 | */ |
||
882 | public function testReportsCorrectlyWhenANonExclusiveFollowsAnExclusive() : void |
||
883 | { |
||
884 | $this->expectFailsRuleWithSchema( |
||
885 | $this->getSchema(), |
||
886 | new OverlappingFieldsCanBeMerged(), |
||
887 | ' |
||
888 | { |
||
889 | someBox { |
||
890 | ... on IntBox { |
||
891 | deepBox { |
||
892 | ...X |
||
893 | } |
||
894 | } |
||
895 | } |
||
896 | someBox { |
||
897 | ... on StringBox { |
||
898 | deepBox { |
||
899 | ...Y |
||
900 | } |
||
901 | } |
||
902 | } |
||
903 | memoed: someBox { |
||
904 | ... on IntBox { |
||
905 | deepBox { |
||
906 | ...X |
||
907 | } |
||
908 | } |
||
909 | } |
||
910 | memoed: someBox { |
||
911 | ... on StringBox { |
||
912 | deepBox { |
||
913 | ...Y |
||
914 | } |
||
915 | } |
||
916 | } |
||
917 | other: someBox { |
||
918 | ...X |
||
919 | } |
||
920 | other: someBox { |
||
921 | ...Y |
||
922 | } |
||
923 | } |
||
924 | fragment X on SomeBox { |
||
925 | scalar |
||
926 | } |
||
927 | fragment Y on SomeBox { |
||
928 | scalar: unrelatedField |
||
929 | } |
||
930 | ', |
||
931 | [ |
||
932 | FormattedError::create( |
||
933 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
934 | 'other', |
||
935 | [['scalar', 'scalar and unrelatedField are different fields']] |
||
936 | ), |
||
937 | [ |
||
938 | new SourceLocation(31, 11), |
||
939 | new SourceLocation(39, 11), |
||
940 | new SourceLocation(34, 11), |
||
941 | new SourceLocation(42, 11), |
||
942 | ] |
||
943 | ), |
||
944 | ] |
||
945 | ); |
||
946 | } |
||
947 | |||
948 | /** |
||
949 | * @see it('disallows differing return type nullability despite no overlap') |
||
950 | */ |
||
951 | public function testDisallowsDifferingReturnTypeNullabilityDespiteNoOverlap() : void |
||
952 | { |
||
953 | $this->expectFailsRuleWithSchema( |
||
954 | $this->getSchema(), |
||
955 | new OverlappingFieldsCanBeMerged(), |
||
956 | ' |
||
957 | { |
||
958 | someBox { |
||
959 | ... on NonNullStringBox1 { |
||
960 | scalar |
||
961 | } |
||
962 | ... on StringBox { |
||
963 | scalar |
||
964 | } |
||
965 | } |
||
966 | } |
||
967 | ', |
||
968 | [ |
||
969 | FormattedError::create( |
||
970 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
971 | 'scalar', |
||
972 | 'they return conflicting types String! and String' |
||
973 | ), |
||
974 | [ |
||
975 | new SourceLocation(5, 15), |
||
976 | new SourceLocation(8, 15), |
||
977 | ] |
||
978 | ), |
||
979 | ] |
||
980 | ); |
||
981 | } |
||
982 | |||
983 | /** |
||
984 | * @see it('disallows differing return type list despite no overlap') |
||
985 | */ |
||
986 | public function testDisallowsDifferingReturnTypeListDespiteNoOverlap() : void |
||
987 | { |
||
988 | $this->expectFailsRuleWithSchema( |
||
989 | $this->getSchema(), |
||
990 | new OverlappingFieldsCanBeMerged(), |
||
991 | ' |
||
992 | { |
||
993 | someBox { |
||
994 | ... on IntBox { |
||
995 | box: listStringBox { |
||
996 | scalar |
||
997 | } |
||
998 | } |
||
999 | ... on StringBox { |
||
1000 | box: stringBox { |
||
1001 | scalar |
||
1002 | } |
||
1003 | } |
||
1004 | } |
||
1005 | } |
||
1006 | ', |
||
1007 | [ |
||
1008 | FormattedError::create( |
||
1009 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
1010 | 'box', |
||
1011 | 'they return conflicting types [StringBox] and StringBox' |
||
1012 | ), |
||
1013 | [ |
||
1014 | new SourceLocation(5, 15), |
||
1015 | new SourceLocation(10, 15), |
||
1016 | ] |
||
1017 | ), |
||
1018 | ] |
||
1019 | ); |
||
1020 | |||
1021 | $this->expectFailsRuleWithSchema( |
||
1022 | $this->getSchema(), |
||
1023 | new OverlappingFieldsCanBeMerged(), |
||
1024 | ' |
||
1025 | { |
||
1026 | someBox { |
||
1027 | ... on IntBox { |
||
1028 | box: stringBox { |
||
1029 | scalar |
||
1030 | } |
||
1031 | } |
||
1032 | ... on StringBox { |
||
1033 | box: listStringBox { |
||
1034 | scalar |
||
1035 | } |
||
1036 | } |
||
1037 | } |
||
1038 | } |
||
1039 | ', |
||
1040 | [ |
||
1041 | FormattedError::create( |
||
1042 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
1043 | 'box', |
||
1044 | 'they return conflicting types StringBox and [StringBox]' |
||
1045 | ), |
||
1046 | [ |
||
1047 | new SourceLocation(5, 15), |
||
1048 | new SourceLocation(10, 15), |
||
1049 | ] |
||
1050 | ), |
||
1051 | ] |
||
1052 | ); |
||
1053 | } |
||
1054 | |||
1055 | public function testDisallowsDifferingSubfields() : void |
||
1056 | { |
||
1057 | $this->expectFailsRuleWithSchema( |
||
1058 | $this->getSchema(), |
||
1059 | new OverlappingFieldsCanBeMerged(), |
||
1060 | ' |
||
1061 | { |
||
1062 | someBox { |
||
1063 | ... on IntBox { |
||
1064 | box: stringBox { |
||
1065 | val: scalar |
||
1066 | val: unrelatedField |
||
1067 | } |
||
1068 | } |
||
1069 | ... on StringBox { |
||
1070 | box: stringBox { |
||
1071 | val: scalar |
||
1072 | } |
||
1073 | } |
||
1074 | } |
||
1075 | } |
||
1076 | ', |
||
1077 | [ |
||
1078 | |||
1079 | FormattedError::create( |
||
1080 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
1081 | 'val', |
||
1082 | 'scalar and unrelatedField are different fields' |
||
1083 | ), |
||
1084 | [ |
||
1085 | new SourceLocation(6, 17), |
||
1086 | new SourceLocation(7, 17), |
||
1087 | ] |
||
1088 | ), |
||
1089 | ] |
||
1090 | ); |
||
1091 | } |
||
1092 | |||
1093 | /** |
||
1094 | * @see it('disallows differing deep return types despite no overlap') |
||
1095 | */ |
||
1096 | public function testDisallowsDifferingDeepReturnTypesDespiteNoOverlap() : void |
||
1097 | { |
||
1098 | $this->expectFailsRuleWithSchema( |
||
1099 | $this->getSchema(), |
||
1100 | new OverlappingFieldsCanBeMerged(), |
||
1101 | ' |
||
1102 | { |
||
1103 | someBox { |
||
1104 | ... on IntBox { |
||
1105 | box: stringBox { |
||
1106 | scalar |
||
1107 | } |
||
1108 | } |
||
1109 | ... on StringBox { |
||
1110 | box: intBox { |
||
1111 | scalar |
||
1112 | } |
||
1113 | } |
||
1114 | } |
||
1115 | } |
||
1116 | ', |
||
1117 | [ |
||
1118 | FormattedError::create( |
||
1119 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
1120 | 'box', |
||
1121 | [['scalar', 'they return conflicting types String and Int']] |
||
1122 | ), |
||
1123 | [ |
||
1124 | new SourceLocation(5, 15), |
||
1125 | new SourceLocation(6, 17), |
||
1126 | new SourceLocation(10, 15), |
||
1127 | new SourceLocation(11, 17), |
||
1128 | ] |
||
1129 | ), |
||
1130 | ] |
||
1131 | ); |
||
1132 | } |
||
1133 | |||
1134 | /** |
||
1135 | * @see it('allows non-conflicting overlaping types') |
||
1136 | */ |
||
1137 | public function testAllowsNonConflictingOverlapingTypes() : void |
||
1138 | { |
||
1139 | $this->expectPassesRuleWithSchema( |
||
1140 | $this->getSchema(), |
||
1141 | new OverlappingFieldsCanBeMerged(), |
||
1142 | ' |
||
1143 | { |
||
1144 | someBox { |
||
1145 | ... on IntBox { |
||
1146 | scalar: unrelatedField |
||
1147 | } |
||
1148 | ... on StringBox { |
||
1149 | scalar |
||
1150 | } |
||
1151 | } |
||
1152 | } |
||
1153 | ' |
||
1154 | ); |
||
1155 | } |
||
1156 | |||
1157 | /** |
||
1158 | * @see it('same wrapped scalar return types') |
||
1159 | */ |
||
1160 | public function testSameWrappedScalarReturnTypes() : void |
||
1161 | { |
||
1162 | $this->expectPassesRuleWithSchema( |
||
1163 | $this->getSchema(), |
||
1164 | new OverlappingFieldsCanBeMerged(), |
||
1165 | ' |
||
1166 | { |
||
1167 | someBox { |
||
1168 | ...on NonNullStringBox1 { |
||
1169 | scalar |
||
1170 | } |
||
1171 | ...on NonNullStringBox2 { |
||
1172 | scalar |
||
1173 | } |
||
1174 | } |
||
1175 | } |
||
1176 | ' |
||
1177 | ); |
||
1178 | } |
||
1179 | |||
1180 | /** |
||
1181 | * @see it('allows inline typeless fragments') |
||
1182 | */ |
||
1183 | public function testAllowsInlineTypelessFragments() : void |
||
1189 | { |
||
1190 | a |
||
1191 | ... { |
||
1192 | a |
||
1193 | } |
||
1194 | } |
||
1195 | ' |
||
1196 | ); |
||
1197 | } |
||
1198 | |||
1199 | /** |
||
1200 | * @see it('compares deep types including list') |
||
1201 | */ |
||
1202 | public function testComparesDeepTypesIncludingList() : void |
||
1203 | { |
||
1204 | $this->expectFailsRuleWithSchema( |
||
1205 | $this->getSchema(), |
||
1206 | new OverlappingFieldsCanBeMerged(), |
||
1207 | ' |
||
1208 | { |
||
1209 | connection { |
||
1210 | ...edgeID |
||
1211 | edges { |
||
1212 | node { |
||
1213 | id: name |
||
1214 | } |
||
1215 | } |
||
1216 | } |
||
1217 | } |
||
1218 | |||
1219 | fragment edgeID on Connection { |
||
1220 | edges { |
||
1221 | node { |
||
1222 | id |
||
1223 | } |
||
1224 | } |
||
1225 | } |
||
1226 | ', |
||
1227 | [ |
||
1228 | FormattedError::create( |
||
1229 | OverlappingFieldsCanBeMerged::fieldsConflictMessage( |
||
1230 | 'edges', |
||
1231 | [['node', [['id', 'name and id are different fields']]]] |
||
1232 | ), |
||
1233 | [ |
||
1234 | new SourceLocation(5, 13), |
||
1235 | new SourceLocation(6, 15), |
||
1236 | new SourceLocation(7, 17), |
||
1237 | new SourceLocation(14, 11), |
||
1238 | new SourceLocation(15, 13), |
||
1239 | new SourceLocation(16, 15), |
||
1240 | ] |
||
1241 | ), |
||
1242 | ] |
||
1243 | ); |
||
1244 | } |
||
1245 | |||
1246 | /** |
||
1247 | * @see it('ignores unknown types') |
||
1248 | */ |
||
1249 | public function testIgnoresUnknownTypes() : void |
||
1255 | { |
||
1256 | someBox { |
||
1257 | ...on UnknownType { |
||
1258 | scalar |
||
1259 | } |
||
1260 | ...on NonNullStringBox2 { |
||
1261 | scalar |
||
1262 | } |
||
1263 | } |
||
1264 | } |
||
1265 | ' |
||
1266 | ); |
||
1267 | } |
||
1268 | |||
1269 | /** |
||
1270 | * @see it('error message contains hint for alias conflict') |
||
1271 | */ |
||
1272 | public function testErrorMessageContainsHintForAliasConflict() : void |
||
1280 | } |
||
1281 | |||
1282 | /** |
||
1283 | * @see it('does not infinite loop on recursive fragment') |
||
1284 | */ |
||
1285 | public function testDoesNotInfiniteLoopOnRecursiveFragment() : void |
||
1290 | fragment fragA on Human { name, relatives { name, ...fragA } } |
||
1291 | ' |
||
1292 | ); |
||
1293 | } |
||
1294 | |||
1295 | /** |
||
1296 | * @see it('does not infinite loop on immediately recursive fragment') |
||
1297 | */ |
||
1298 | public function testDoesNotInfiniteLoopOnImmeditelyRecursiveFragment() : void |
||
1303 | fragment fragA on Human { name, ...fragA } |
||
1304 | ' |
||
1305 | ); |
||
1306 | } |
||
1307 | |||
1308 | /** |
||
1309 | * @see it('does not infinite loop on transitively recursive fragment') |
||
1310 | */ |
||
1311 | public function testDoesNotInfiniteLoopOnTransitivelyRecursiveFragment() : void |
||
1312 | { |
||
1313 | $this->expectPassesRule( |
||
1314 | new OverlappingFieldsCanBeMerged(), |
||
1315 | ' |
||
1316 | fragment fragA on Human { name, ...fragB } |
||
1317 | fragment fragB on Human { name, ...fragC } |
||
1318 | fragment fragC on Human { name, ...fragA } |
||
1319 | ' |
||
1320 | ); |
||
1321 | } |
||
1322 | |||
1323 | /** |
||
1324 | * @see it('find invalid case even with immediately recursive fragment') |
||
1325 | */ |
||
1326 | public function testFindInvalidCaseEvenWithImmediatelyRecursiveFragment() : void |
||
1346 | ] |
||
1347 | ), |
||
1348 | ] |
||
1349 | ); |
||
1350 | } |
||
1351 | } |
||
1352 |
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.