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