1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:ui';
6
7import 'package:flutter/material.dart';
8import 'package:flutter/rendering.dart';
9import 'package:flutter_test/flutter_test.dart';
10import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
11
12import 'multi_view_testing.dart';
13
14void main() {
15 testWidgets('Providing a RenderObjectWidget directly to the RootWidget fails', (
16 WidgetTester tester,
17 ) async {
18 // No render tree exists to attach the RenderObjectWidget to.
19 await tester.pumpWidget(wrapWithView: false, const ColoredBox(color: Colors.red));
20
21 expect(
22 tester.takeException(),
23 isFlutterError.having(
24 (FlutterError error) => error.message,
25 'message',
26 startsWith(
27 'The render object for ColoredBox cannot find ancestor render object to attach to.',
28 ),
29 ),
30 );
31 });
32
33 testWidgets('Moving a RenderObjectWidget to the RootWidget via GlobalKey fails', (
34 WidgetTester tester,
35 ) async {
36 final Widget globalKeyedWidget = ColoredBox(key: GlobalKey(), color: Colors.red);
37
38 await tester.pumpWidget(wrapWithView: false, View(view: tester.view, child: globalKeyedWidget));
39 expect(tester.takeException(), isNull);
40
41 await tester.pumpWidget(wrapWithView: false, globalKeyedWidget);
42
43 expect(
44 tester.takeException(),
45 isFlutterError.having(
46 (FlutterError error) => error.message,
47 'message',
48 contains('cannot find ancestor render object to attach to.'),
49 ),
50 );
51 });
52
53 testWidgets(
54 'A View cannot be a child of a render object widget',
55 experimentalLeakTesting: LeakTesting.settings
56 .withIgnoredAll(), // leaking by design because of exception
57 (WidgetTester tester) async {
58 await tester.pumpWidget(
59 Center(
60 child: View(view: FakeView(tester.view), child: Container()),
61 ),
62 );
63
64 expect(
65 tester.takeException(),
66 isFlutterError.having(
67 (FlutterError error) => error.message,
68 'message',
69 contains('cannot maintain an independent render tree at its current location.'),
70 ),
71 );
72 },
73 );
74
75 testWidgets(
76 'The child of a ViewAnchor cannot be a View',
77 experimentalLeakTesting: LeakTesting.settings
78 .withIgnoredAll(), // leaking by design because of exception
79 (WidgetTester tester) async {
80 await tester.pumpWidget(
81 ViewAnchor(
82 child: View(view: FakeView(tester.view), child: Container()),
83 ),
84 );
85
86 expect(
87 tester.takeException(),
88 isFlutterError.having(
89 (FlutterError error) => error.message,
90 'message',
91 contains('cannot maintain an independent render tree at its current location.'),
92 ),
93 );
94 },
95 );
96
97 testWidgets('A View can not be moved via GlobalKey to be a child of a RenderObject', (
98 WidgetTester tester,
99 ) async {
100 final Widget globalKeyedView = View(
101 key: GlobalKey(),
102 view: FakeView(tester.view),
103 child: const ColoredBox(color: Colors.red),
104 );
105
106 await tester.pumpWidget(wrapWithView: false, globalKeyedView);
107 expect(tester.takeException(), isNull);
108
109 await tester.pumpWidget(wrapWithView: false, View(view: tester.view, child: globalKeyedView));
110
111 expect(
112 tester.takeException(),
113 isFlutterError.having(
114 (FlutterError error) => error.message,
115 'message',
116 contains('cannot maintain an independent render tree at its current location.'),
117 ),
118 );
119 });
120
121 testWidgets('The view property of a ViewAnchor cannot be a render object widget', (
122 WidgetTester tester,
123 ) async {
124 await tester.pumpWidget(
125 ViewAnchor(
126 view: const ColoredBox(color: Colors.red),
127 child: Container(),
128 ),
129 );
130
131 expect(
132 tester.takeException(),
133 isFlutterError.having(
134 (FlutterError error) => error.message,
135 'message',
136 startsWith(
137 'The render object for ColoredBox cannot find ancestor render object to attach to.',
138 ),
139 ),
140 );
141 });
142
143 testWidgets(
144 'A RenderObject cannot be moved into the view property of a ViewAnchor via GlobalKey',
145 (WidgetTester tester) async {
146 final Widget globalKeyedWidget = ColoredBox(key: GlobalKey(), color: Colors.red);
147
148 await tester.pumpWidget(ViewAnchor(child: globalKeyedWidget));
149 expect(tester.takeException(), isNull);
150
151 await tester.pumpWidget(ViewAnchor(view: globalKeyedWidget, child: const SizedBox()));
152
153 expect(
154 tester.takeException(),
155 isFlutterError.having(
156 (FlutterError error) => error.message,
157 'message',
158 contains('cannot find ancestor render object to attach to.'),
159 ),
160 );
161 },
162 );
163
164 testWidgets('ViewAnchor cannot be used at the top of the widget tree (outside of View)', (
165 WidgetTester tester,
166 ) async {
167 await tester.pumpWidget(wrapWithView: false, const ViewAnchor(child: SizedBox()));
168
169 expect(
170 tester.takeException(),
171 isFlutterError.having(
172 (FlutterError error) => error.message,
173 'message',
174 startsWith(
175 'The render object for SizedBox cannot find ancestor render object to attach to.',
176 ),
177 ),
178 );
179 });
180
181 testWidgets(
182 'ViewAnchor cannot be moved to the top of the widget tree (outside of View) via GlobalKey',
183 (WidgetTester tester) async {
184 final Widget globalKeyedViewAnchor = ViewAnchor(key: GlobalKey(), child: const SizedBox());
185
186 await tester.pumpWidget(
187 wrapWithView: false,
188 View(view: tester.view, child: globalKeyedViewAnchor),
189 );
190 expect(tester.takeException(), isNull);
191
192 await tester.pumpWidget(wrapWithView: false, globalKeyedViewAnchor);
193
194 expect(
195 tester.takeException(),
196 isFlutterError.having(
197 (FlutterError error) => error.message,
198 'message',
199 contains('cannot find ancestor render object to attach to.'),
200 ),
201 );
202 },
203 );
204
205 testWidgets('View can be used at the top of the widget tree', (WidgetTester tester) async {
206 await tester.pumpWidget(wrapWithView: false, View(view: tester.view, child: Container()));
207
208 expect(tester.takeException(), isNull);
209 });
210
211 testWidgets('View can be moved to the top of the widget tree view GlobalKey', (
212 WidgetTester tester,
213 ) async {
214 final Widget globalKeyView = View(
215 view: FakeView(tester.view),
216 child: const ColoredBox(color: Colors.red),
217 );
218
219 await tester.pumpWidget(
220 wrapWithView: false,
221 View(
222 view: tester.view,
223 child: ViewAnchor(
224 view: globalKeyView, // This one has trouble when deactivating
225 child: const SizedBox(),
226 ),
227 ),
228 );
229 expect(tester.takeException(), isNull);
230 expect(find.byType(SizedBox), findsOneWidget);
231 expect(find.byType(ColoredBox), findsOneWidget);
232
233 await tester.pumpWidget(wrapWithView: false, globalKeyView);
234 expect(tester.takeException(), isNull);
235 expect(find.byType(SizedBox), findsNothing);
236 expect(find.byType(ColoredBox), findsOneWidget);
237 });
238
239 testWidgets('ViewCollection can be used at the top of the widget tree', (
240 WidgetTester tester,
241 ) async {
242 await tester.pumpWidget(
243 wrapWithView: false,
244 ViewCollection(
245 views: <Widget>[View(view: tester.view, child: Container())],
246 ),
247 );
248
249 expect(tester.takeException(), isNull);
250 });
251
252 testWidgets('ViewCollection cannot be used inside a View', (WidgetTester tester) async {
253 await tester.pumpWidget(
254 ViewCollection(
255 views: <Widget>[View(view: FakeView(tester.view), child: Container())],
256 ),
257 );
258
259 expect(
260 tester.takeException(),
261 isFlutterError.having(
262 (FlutterError error) => error.message,
263 'message',
264 startsWith(
265 'The Element for ViewCollection cannot be inserted into slot "null" of its ancestor.',
266 ),
267 ),
268 );
269 });
270
271 testWidgets('ViewCollection can be used as ViewAnchor.view', (WidgetTester tester) async {
272 await tester.pumpWidget(
273 ViewAnchor(
274 view: ViewCollection(
275 views: <Widget>[View(view: FakeView(tester.view), child: Container())],
276 ),
277 child: Container(),
278 ),
279 );
280
281 expect(tester.takeException(), isNull);
282 });
283
284 testWidgets('ViewCollection cannot have render object widgets as children', (
285 WidgetTester tester,
286 ) async {
287 await tester.pumpWidget(
288 wrapWithView: false,
289 const ViewCollection(views: <Widget>[ColoredBox(color: Colors.red)]),
290 );
291
292 expect(
293 tester.takeException(),
294 isFlutterError.having(
295 (FlutterError error) => error.message,
296 'message',
297 startsWith(
298 'The render object for ColoredBox cannot find ancestor render object to attach to.',
299 ),
300 ),
301 );
302 });
303
304 testWidgets('Views can be moved in and out of ViewCollections via GlobalKey', (
305 WidgetTester tester,
306 ) async {
307 final Widget greenView = View(
308 key: GlobalKey(debugLabel: 'green'),
309 view: tester.view,
310 child: const ColoredBox(color: Colors.green),
311 );
312 final Widget redView = View(
313 key: GlobalKey(debugLabel: 'red'),
314 view: FakeView(tester.view),
315 child: const ColoredBox(color: Colors.red),
316 );
317
318 await tester.pumpWidget(
319 wrapWithView: false,
320 ViewCollection(
321 views: <Widget>[
322 greenView,
323 ViewCollection(views: <Widget>[redView]),
324 ],
325 ),
326 );
327 expect(tester.takeException(), isNull);
328 expect(find.byType(ColoredBox), findsNWidgets(2));
329
330 await tester.pumpWidget(
331 wrapWithView: false,
332 ViewCollection(
333 views: <Widget>[
334 redView,
335 ViewCollection(views: <Widget>[greenView]),
336 ],
337 ),
338 );
339 expect(tester.takeException(), isNull);
340 expect(find.byType(ColoredBox), findsNWidgets(2));
341 });
342
343 testWidgets('Can move stuff between views via global key: viewA -> viewB', (
344 WidgetTester tester,
345 ) async {
346 final FlutterView greenView = tester.view;
347 final FlutterView redView = FakeView(tester.view);
348 final Widget globalKeyChild = SizedBox(key: GlobalKey());
349
350 Map<int, RenderObject> collectLeafRenderObjects() {
351 final Map<int, RenderObject> result = <int, RenderObject>{};
352 for (final RenderView renderView in RendererBinding.instance.renderViews) {
353 void visit(RenderObject object) {
354 result[renderView.flutterView.viewId] = object;
355 object.visitChildren(visit);
356 }
357
358 visit(renderView);
359 }
360 return result;
361 }
362
363 await tester.pumpWidget(
364 wrapWithView: false,
365 ViewCollection(
366 views: <Widget>[
367 View(
368 view: greenView,
369 child: ColoredBox(color: Colors.green, child: globalKeyChild),
370 ),
371 View(
372 view: redView,
373 child: const ColoredBox(color: Colors.red),
374 ),
375 ],
376 ),
377 );
378 expect(
379 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
380 findsOneWidget,
381 );
382 expect(
383 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
384 findsNothing,
385 );
386 final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
387
388 Map<int, RenderObject> leafRenderObject = collectLeafRenderObjects();
389 expect(leafRenderObject[greenView.viewId], isA<RenderConstrainedBox>());
390 expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>()));
391
392 // Move the child.
393 await tester.pumpWidget(
394 wrapWithView: false,
395 ViewCollection(
396 views: <Widget>[
397 View(
398 view: greenView,
399 child: const ColoredBox(color: Colors.green),
400 ),
401 View(
402 view: redView,
403 child: ColoredBox(color: Colors.red, child: globalKeyChild),
404 ),
405 ],
406 ),
407 );
408
409 expect(
410 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
411 findsNothing,
412 );
413 expect(
414 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
415 findsOneWidget,
416 );
417 expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
418
419 leafRenderObject = collectLeafRenderObjects();
420 expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>()));
421 expect(leafRenderObject[redView.viewId], isA<RenderConstrainedBox>());
422 });
423
424 testWidgets('Can move stuff between views via global key: viewB -> viewA', (
425 WidgetTester tester,
426 ) async {
427 final FlutterView greenView = tester.view;
428 final FlutterView redView = FakeView(tester.view);
429 final Widget globalKeyChild = SizedBox(key: GlobalKey());
430
431 Map<int, RenderObject> collectLeafRenderObjects() {
432 final Map<int, RenderObject> result = <int, RenderObject>{};
433 for (final RenderView renderView in RendererBinding.instance.renderViews) {
434 void visit(RenderObject object) {
435 result[renderView.flutterView.viewId] = object;
436 object.visitChildren(visit);
437 }
438
439 visit(renderView);
440 }
441 return result;
442 }
443
444 await tester.pumpWidget(
445 wrapWithView: false,
446 ViewCollection(
447 views: <Widget>[
448 View(
449 view: greenView,
450 child: const ColoredBox(color: Colors.green),
451 ),
452 View(
453 view: redView,
454 child: ColoredBox(color: Colors.red, child: globalKeyChild),
455 ),
456 ],
457 ),
458 );
459 expect(
460 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
461 findsOneWidget,
462 );
463 expect(
464 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
465 findsNothing,
466 );
467 final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
468
469 Map<int, RenderObject> leafRenderObject = collectLeafRenderObjects();
470 expect(leafRenderObject[redView.viewId], isA<RenderConstrainedBox>());
471 expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>()));
472
473 // Move the child.
474 await tester.pumpWidget(
475 wrapWithView: false,
476 ViewCollection(
477 views: <Widget>[
478 View(
479 view: greenView,
480 child: ColoredBox(color: Colors.green, child: globalKeyChild),
481 ),
482 View(
483 view: redView,
484 child: const ColoredBox(color: Colors.red),
485 ),
486 ],
487 ),
488 );
489
490 expect(
491 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
492 findsNothing,
493 );
494 expect(
495 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
496 findsOneWidget,
497 );
498 expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
499
500 leafRenderObject = collectLeafRenderObjects();
501 expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>()));
502 expect(leafRenderObject[greenView.viewId], isA<RenderConstrainedBox>());
503 });
504
505 testWidgets('Can move stuff out of a view that is going away, viewA -> ViewB', (
506 WidgetTester tester,
507 ) async {
508 final FlutterView greenView = tester.view;
509 final Key greenKey = UniqueKey();
510 final FlutterView redView = FakeView(tester.view);
511 final Key redKey = UniqueKey();
512 final Widget globalKeyChild = SizedBox(key: GlobalKey());
513
514 await tester.pumpWidget(
515 wrapWithView: false,
516 ViewCollection(
517 views: <Widget>[
518 View(
519 key: greenKey,
520 view: greenView,
521 child: const ColoredBox(color: Colors.green),
522 ),
523 View(
524 key: redKey,
525 view: redView,
526 child: ColoredBox(color: Colors.red, child: globalKeyChild),
527 ),
528 ],
529 ),
530 );
531 expect(
532 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
533 findsOneWidget,
534 );
535 expect(
536 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
537 findsNothing,
538 );
539 final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
540
541 // Move the child and remove its view.
542 await tester.pumpWidget(
543 wrapWithView: false,
544 ViewCollection(
545 views: <Widget>[
546 View(
547 key: greenKey,
548 view: greenView,
549 child: ColoredBox(color: Colors.green, child: globalKeyChild),
550 ),
551 ],
552 ),
553 );
554
555 expect(findsColoredBox(Colors.red), findsNothing);
556 expect(
557 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
558 findsOneWidget,
559 );
560 expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
561 });
562
563 testWidgets('Can move stuff out of a view that is going away, viewB -> ViewA', (
564 WidgetTester tester,
565 ) async {
566 final FlutterView greenView = tester.view;
567 final Key greenKey = UniqueKey();
568 final FlutterView redView = FakeView(tester.view);
569 final Key redKey = UniqueKey();
570 final Widget globalKeyChild = SizedBox(key: GlobalKey());
571
572 await tester.pumpWidget(
573 wrapWithView: false,
574 ViewCollection(
575 views: <Widget>[
576 View(
577 key: greenKey,
578 view: greenView,
579 child: ColoredBox(color: Colors.green, child: globalKeyChild),
580 ),
581 View(
582 key: redKey,
583 view: redView,
584 child: const ColoredBox(color: Colors.red),
585 ),
586 ],
587 ),
588 );
589 expect(
590 find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
591 findsOneWidget,
592 );
593 expect(
594 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
595 findsNothing,
596 );
597 final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
598
599 // Move the child and remove its view.
600 await tester.pumpWidget(
601 wrapWithView: false,
602 ViewCollection(
603 views: <Widget>[
604 View(
605 key: redKey,
606 view: redView,
607 child: ColoredBox(color: Colors.red, child: globalKeyChild),
608 ),
609 ],
610 ),
611 );
612
613 expect(findsColoredBox(Colors.green), findsNothing);
614 expect(
615 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
616 findsOneWidget,
617 );
618 expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
619 });
620
621 testWidgets('Can move stuff out of a view that is moving itself, stuff ends up before view', (
622 WidgetTester tester,
623 ) async {
624 final Key key1 = UniqueKey();
625 final Key key2 = UniqueKey();
626 final Key key3 = UniqueKey();
627 final Key key4 = UniqueKey();
628
629 final GlobalKey viewKey = GlobalKey();
630 final GlobalKey childKey = GlobalKey();
631
632 await tester.pumpWidget(
633 Column(
634 children: <Widget>[
635 SizedBox(key: key1),
636 ViewAnchor(
637 key: key2,
638 view: View(
639 key: viewKey,
640 view: FakeView(tester.view),
641 child: SizedBox(
642 child: ColoredBox(key: childKey, color: Colors.green),
643 ),
644 ),
645 child: const SizedBox(),
646 ),
647 ViewAnchor(key: key3, child: const SizedBox()),
648 SizedBox(key: key4),
649 ],
650 ),
651 );
652
653 await tester.pumpWidget(
654 Column(
655 children: <Widget>[
656 SizedBox(
657 key: key1,
658 child: ColoredBox(key: childKey, color: Colors.green),
659 ),
660 ViewAnchor(key: key2, child: const SizedBox()),
661 ViewAnchor(
662 key: key3,
663 view: View(key: viewKey, view: FakeView(tester.view), child: const SizedBox()),
664 child: const SizedBox(),
665 ),
666 SizedBox(key: key4),
667 ],
668 ),
669 );
670
671 await tester.pumpWidget(
672 Column(
673 children: <Widget>[
674 SizedBox(key: key1),
675 ViewAnchor(
676 key: key2,
677 view: View(
678 key: viewKey,
679 view: FakeView(tester.view),
680 child: SizedBox(
681 child: ColoredBox(key: childKey, color: Colors.green),
682 ),
683 ),
684 child: const SizedBox(),
685 ),
686 ViewAnchor(key: key3, child: const SizedBox()),
687 SizedBox(key: key4),
688 ],
689 ),
690 );
691 });
692
693 testWidgets('Can move stuff out of a view that is moving itself, stuff ends up after view', (
694 WidgetTester tester,
695 ) async {
696 final Key key1 = UniqueKey();
697 final Key key2 = UniqueKey();
698 final Key key3 = UniqueKey();
699 final Key key4 = UniqueKey();
700
701 final GlobalKey viewKey = GlobalKey();
702 final GlobalKey childKey = GlobalKey();
703
704 await tester.pumpWidget(
705 Column(
706 children: <Widget>[
707 SizedBox(key: key1),
708 ViewAnchor(
709 key: key2,
710 view: View(
711 key: viewKey,
712 view: FakeView(tester.view),
713 child: SizedBox(
714 child: ColoredBox(key: childKey, color: Colors.green),
715 ),
716 ),
717 child: const SizedBox(),
718 ),
719 ViewAnchor(key: key3, child: const SizedBox()),
720 SizedBox(key: key4),
721 ],
722 ),
723 );
724
725 await tester.pumpWidget(
726 Column(
727 children: <Widget>[
728 SizedBox(key: key1),
729 ViewAnchor(key: key2, child: const SizedBox()),
730 ViewAnchor(
731 key: key3,
732 view: View(key: viewKey, view: FakeView(tester.view), child: const SizedBox()),
733 child: const SizedBox(),
734 ),
735 SizedBox(
736 key: key4,
737 child: ColoredBox(key: childKey, color: Colors.green),
738 ),
739 ],
740 ),
741 );
742
743 await tester.pumpWidget(
744 Column(
745 children: <Widget>[
746 SizedBox(key: key1),
747 ViewAnchor(
748 key: key2,
749 view: View(
750 key: viewKey,
751 view: FakeView(tester.view),
752 child: SizedBox(
753 child: ColoredBox(key: childKey, color: Colors.green),
754 ),
755 ),
756 child: const SizedBox(),
757 ),
758 ViewAnchor(key: key3, child: const SizedBox()),
759 SizedBox(key: key4),
760 ],
761 ),
762 );
763 });
764
765 testWidgets('Can globalkey move down the tree from a view that is going away', (
766 WidgetTester tester,
767 ) async {
768 final FlutterView anchorView = FakeView(tester.view);
769 final Widget globalKeyChild = SizedBox(key: GlobalKey());
770
771 await tester.pumpWidget(
772 ColoredBox(
773 color: Colors.green,
774 child: ViewAnchor(
775 view: View(
776 view: anchorView,
777 child: ColoredBox(color: Colors.yellow, child: globalKeyChild),
778 ),
779 child: const ColoredBox(color: Colors.red),
780 ),
781 ),
782 );
783
784 expect(findsColoredBox(Colors.green), findsOneWidget);
785 expect(findsColoredBox(Colors.yellow), findsOneWidget);
786 expect(
787 find.descendant(of: findsColoredBox(Colors.yellow), matching: find.byType(SizedBox)),
788 findsOneWidget,
789 );
790 expect(findsColoredBox(Colors.red), findsOneWidget);
791 expect(
792 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
793 findsNothing,
794 );
795 expect(find.byType(SizedBox), findsOneWidget);
796 final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
797
798 await tester.pumpWidget(
799 ColoredBox(
800 color: Colors.green,
801 child: ViewAnchor(
802 child: ColoredBox(color: Colors.red, child: globalKeyChild),
803 ),
804 ),
805 );
806 expect(findsColoredBox(Colors.green), findsOneWidget);
807 expect(findsColoredBox(Colors.yellow), findsNothing);
808 expect(
809 find.descendant(of: findsColoredBox(Colors.yellow), matching: find.byType(SizedBox)),
810 findsNothing,
811 );
812 expect(findsColoredBox(Colors.red), findsOneWidget);
813 expect(
814 find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
815 findsOneWidget,
816 );
817 expect(find.byType(SizedBox), findsOneWidget);
818 expect(tester.renderObject(find.byKey(globalKeyChild.key!)), boxWithGlobalKey);
819 });
820
821 testWidgets('RenderObjects are disposed when a view goes away from a ViewAnchor', (
822 WidgetTester tester,
823 ) async {
824 final FlutterView anchorView = FakeView(tester.view);
825
826 await tester.pumpWidget(
827 ColoredBox(
828 color: Colors.green,
829 child: ViewAnchor(
830 view: View(
831 view: anchorView,
832 child: const ColoredBox(color: Colors.yellow),
833 ),
834 child: const ColoredBox(color: Colors.red),
835 ),
836 ),
837 );
838
839 final RenderObject box = tester.renderObject(findsColoredBox(Colors.yellow));
840
841 await tester.pumpWidget(
842 const ColoredBox(
843 color: Colors.green,
844 child: ViewAnchor(child: ColoredBox(color: Colors.red)),
845 ),
846 );
847
848 expect(box.debugDisposed, isTrue);
849 });
850
851 testWidgets('RenderObjects are disposed when a view goes away from a ViewCollection', (
852 WidgetTester tester,
853 ) async {
854 final FlutterView redView = tester.view;
855 final FlutterView greenView = FakeView(tester.view);
856
857 await tester.pumpWidget(
858 wrapWithView: false,
859 ViewCollection(
860 views: <Widget>[
861 View(
862 view: redView,
863 child: const ColoredBox(color: Colors.red),
864 ),
865 View(
866 view: greenView,
867 child: const ColoredBox(color: Colors.green),
868 ),
869 ],
870 ),
871 );
872
873 expect(findsColoredBox(Colors.green), findsOneWidget);
874 expect(findsColoredBox(Colors.red), findsOneWidget);
875 final RenderObject box = tester.renderObject(findsColoredBox(Colors.green));
876
877 await tester.pumpWidget(
878 wrapWithView: false,
879 ViewCollection(
880 views: <Widget>[
881 View(
882 view: redView,
883 child: const ColoredBox(color: Colors.red),
884 ),
885 ],
886 ),
887 );
888
889 expect(findsColoredBox(Colors.green), findsNothing);
890 expect(findsColoredBox(Colors.red), findsOneWidget);
891 expect(box.debugDisposed, isTrue);
892 });
893
894 testWidgets('View can be wrapped and unwrapped', (WidgetTester tester) async {
895 final Widget view = View(view: tester.view, child: const SizedBox());
896
897 await tester.pumpWidget(wrapWithView: false, view);
898
899 final RenderObject renderView = tester.renderObject(find.byType(View));
900 final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox));
901
902 await tester.pumpWidget(wrapWithView: false, ViewCollection(views: <Widget>[view]));
903
904 expect(tester.renderObject(find.byType(View)), same(renderView));
905 expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
906
907 await tester.pumpWidget(wrapWithView: false, view);
908
909 expect(tester.renderObject(find.byType(View)), same(renderView));
910 expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
911 });
912
913 testWidgets('ViewAnchor with View can be wrapped and unwrapped', (WidgetTester tester) async {
914 final Widget viewAnchor = ViewAnchor(
915 view: View(view: FakeView(tester.view), child: const SizedBox()),
916 child: const ColoredBox(color: Colors.green),
917 );
918
919 await tester.pumpWidget(viewAnchor);
920
921 final List<RenderObject> renderViews = tester.renderObjectList(find.byType(View)).toList();
922 final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox));
923
924 await tester.pumpWidget(ColoredBox(color: Colors.yellow, child: viewAnchor));
925
926 expect(tester.renderObjectList(find.byType(View)), renderViews);
927 expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
928
929 await tester.pumpWidget(viewAnchor);
930
931 expect(tester.renderObjectList(find.byType(View)), renderViews);
932 expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
933 });
934
935 testWidgets('Moving a View keeps its semantics tree stable', (WidgetTester tester) async {
936 final Widget view = View(
937 // No explicit key, we rely on the implicit key of the underlying RawView.
938 view: tester.view,
939 child: Semantics(textDirection: TextDirection.ltr, label: 'Hello', child: const SizedBox()),
940 );
941 await tester.pumpWidget(wrapWithView: false, view);
942
943 final RenderObject renderSemantics = tester.renderObject(find.bySemanticsLabel('Hello'));
944 final SemanticsNode semantics = tester.getSemantics(find.bySemanticsLabel('Hello'));
945 expect(semantics.id, 1);
946 expect(renderSemantics.debugSemantics, same(semantics));
947
948 await tester.pumpWidget(wrapWithView: false, ViewCollection(views: <Widget>[view]));
949
950 final RenderObject renderSemanticsAfterMove = tester.renderObject(
951 find.bySemanticsLabel('Hello'),
952 );
953 final SemanticsNode semanticsAfterMove = tester.getSemantics(find.bySemanticsLabel('Hello'));
954 expect(renderSemanticsAfterMove, same(renderSemantics));
955 expect(semanticsAfterMove.id, 1);
956 expect(semanticsAfterMove, same(semantics));
957 });
958}
959
960Finder findsColoredBox(Color color) {
961 return find.byWidgetPredicate((Widget widget) => widget is ColoredBox && widget.color == color);
962}
963