framework_test.dart 26.3 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter_test/flutter_test.dart';
6
import 'package:flutter/foundation.dart';
7 8
import 'package:flutter/widgets.dart';

9 10 11 12 13
class TestState extends State<StatefulWidget> {
  @override
  Widget build(BuildContext context) => null;
}

14 15 16 17 18
@optionalTypeArgs
class _MyGlobalObjectKey<T extends State<StatefulWidget>> extends GlobalObjectKey<T> {
  const _MyGlobalObjectKey(Object value) : super(value);
}

19 20
void main() {
  testWidgets('UniqueKey control test', (WidgetTester tester) async {
21
    final Key key = UniqueKey();
22
    expect(key, hasOneLineDescription);
23
    expect(key, isNot(equals(UniqueKey())));
24 25 26
  });

  testWidgets('ObjectKey control test', (WidgetTester tester) async {
27 28 29 30 31
    final Object a = Object();
    final Object b = Object();
    final Key keyA = ObjectKey(a);
    final Key keyA2 = ObjectKey(a);
    final Key keyB = ObjectKey(b);
32 33 34 35 36 37 38

    expect(keyA, hasOneLineDescription);
    expect(keyA, equals(keyA2));
    expect(keyA.hashCode, equals(keyA2.hashCode));
    expect(keyA, isNot(equals(keyB)));
  });

39
  testWidgets('GlobalObjectKey toString test', (WidgetTester tester) async {
40 41 42 43
    const GlobalObjectKey one = GlobalObjectKey(1);
    const GlobalObjectKey<TestState> two = GlobalObjectKey<TestState>(2);
    const GlobalObjectKey three = _MyGlobalObjectKey(3);
    const GlobalObjectKey<TestState> four = _MyGlobalObjectKey<TestState>(4);
44 45 46 47 48 49 50

    expect(one.toString(), equals('[GlobalObjectKey ${describeIdentity(1)}]'));
    expect(two.toString(), equals('[GlobalObjectKey<TestState> ${describeIdentity(2)}]'));
    expect(three.toString(), equals('[_MyGlobalObjectKey ${describeIdentity(3)}]'));
    expect(four.toString(), equals('[_MyGlobalObjectKey<TestState> ${describeIdentity(4)}]'));
  });

51
  testWidgets('GlobalObjectKey control test', (WidgetTester tester) async {
52 53 54 55 56
    final Object a = Object();
    final Object b = Object();
    final Key keyA = GlobalObjectKey(a);
    final Key keyA2 = GlobalObjectKey(a);
    final Key keyB = GlobalObjectKey(b);
57 58 59 60 61 62 63

    expect(keyA, hasOneLineDescription);
    expect(keyA, equals(keyA2));
    expect(keyA.hashCode, equals(keyA2.hashCode));
    expect(keyA, isNot(equals(keyB)));
  });

64
  testWidgets('GlobalKey duplication 1 - double appearance', (WidgetTester tester) async {
65 66
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
67
      textDirection: TextDirection.ltr,
68
      children: <Widget>[
69
        Container(
70
          key: const ValueKey<int>(1),
71
          child: SizedBox(key: key),
72
        ),
73
        Container(
74
          key: const ValueKey<int>(2),
75
          child: Placeholder(key: key),
76 77 78
        ),
      ],
    ));
79 80 81 82 83 84 85 86 87 88 89 90
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Multiple widgets used the same GlobalKey.\n'
        'The key [GlobalKey#00000 problematic] was used by multiple widgets. The parents of those widgets were:\n'
        '- Container-[<1>]\n'
        '- Container-[<2>]\n'
        'A GlobalKey can only be specified on one widget at a time in the widget tree.'
      ),
    );
91 92 93
  });

  testWidgets('GlobalKey duplication 2 - splitting and changing type', (WidgetTester tester) async {
94
    final Key key = GlobalKey(debugLabel: 'problematic');
95

96
    await tester.pumpWidget(Stack(
97
      textDirection: TextDirection.ltr,
98
      children: <Widget>[
99
        Container(
100 101
          key: const ValueKey<int>(1),
        ),
102
        Container(
103 104
          key: const ValueKey<int>(2),
        ),
105
        Container(
106 107 108 109 110
          key: key
        ),
      ],
    ));

111
    await tester.pumpWidget(Stack(
112
      textDirection: TextDirection.ltr,
113
      children: <Widget>[
114
        Container(
115
          key: const ValueKey<int>(1),
116
          child: SizedBox(key: key),
117
        ),
118
        Container(
119
          key: const ValueKey<int>(2),
120
          child: Placeholder(key: key),
121 122 123 124
        ),
      ],
    ));

125 126 127 128 129 130 131 132 133 134
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Multiple widgets used the same GlobalKey.\n'
        'The key [GlobalKey#00000 problematic] was used by multiple widgets. The parents of those widgets were:\n'
        '- Container-[<1>]\n'
        '- Container-[<2>]\n'
        'A GlobalKey can only be specified on one widget at a time in the widget tree.'
135
      ),
136
    );
137 138
  });

139
  testWidgets('GlobalKey duplication 3 - splitting and changing type', (WidgetTester tester) async {
140 141
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
142
      textDirection: TextDirection.ltr,
143
      children: <Widget>[
144
        Container(key: key),
145 146
      ],
    ));
147
    await tester.pumpWidget(Stack(
148
      textDirection: TextDirection.ltr,
149
      children: <Widget>[
150 151
        SizedBox(key: key),
        Placeholder(key: key),
152 153
      ],
    ));
154 155 156 157 158 159 160 161 162 163
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Multiple widgets used the same GlobalKey.\n'
        'The key [GlobalKey#00000 problematic] was used by 2 widgets:\n'
        '  SizedBox-[GlobalKey#00000 problematic]\n'
        '  Placeholder-[GlobalKey#00000 problematic]\n'
        'A GlobalKey can only be specified on one widget at a time in the widget tree.'
164
      ),
165
    );
166 167 168
  });

  testWidgets('GlobalKey duplication 4 - splitting and half changing type', (WidgetTester tester) async {
169 170
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
171
      textDirection: TextDirection.ltr,
172
      children: <Widget>[
173
        Container(key: key),
174 175
      ],
    ));
176
    await tester.pumpWidget(Stack(
177
      textDirection: TextDirection.ltr,
178
      children: <Widget>[
179 180
        Container(key: key),
        Placeholder(key: key),
181 182
      ],
    ));
183 184 185 186 187 188 189 190 191 192
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Multiple widgets used the same GlobalKey.\n'
        'The key [GlobalKey#00000 problematic] was used by 2 widgets:\n'
        '  Container-[GlobalKey#00000 problematic]\n'
        '  Placeholder-[GlobalKey#00000 problematic]\n'
        'A GlobalKey can only be specified on one widget at a time in the widget tree.'
193
      ),
194
    );
195 196 197
  });

  testWidgets('GlobalKey duplication 5 - splitting and half changing type', (WidgetTester tester) async {
198 199
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
200
      textDirection: TextDirection.ltr,
201
      children: <Widget>[
202
        Container(key: key),
203 204
      ],
    ));
205
    await tester.pumpWidget(Stack(
206
      textDirection: TextDirection.ltr,
207
      children: <Widget>[
208 209
        Placeholder(key: key),
        Container(key: key),
210 211 212 213 214 215
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 6 - splitting and not changing type', (WidgetTester tester) async {
216 217
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
218
      textDirection: TextDirection.ltr,
219
      children: <Widget>[
220
        Container(key: key),
221 222
      ],
    ));
223
    await tester.pumpWidget(Stack(
224
      textDirection: TextDirection.ltr,
225
      children: <Widget>[
226 227
        Container(key: key),
        Container(key: key),
228 229 230 231 232 233
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 7 - appearing later', (WidgetTester tester) async {
234 235
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
236
      textDirection: TextDirection.ltr,
237
      children: <Widget>[
238 239
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
        Container(key: const ValueKey<int>(2)),
240 241
      ],
    ));
242
    await tester.pumpWidget(Stack(
243
      textDirection: TextDirection.ltr,
244
      children: <Widget>[
245 246
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
        Container(key: const ValueKey<int>(2), child: Container(key: key)),
247 248 249 250 251 252
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 8 - appearing earlier', (WidgetTester tester) async {
253 254
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
255
      textDirection: TextDirection.ltr,
256
      children: <Widget>[
257 258
        Container(key: const ValueKey<int>(1)),
        Container(key: const ValueKey<int>(2), child: Container(key: key)),
259 260
      ],
    ));
261
    await tester.pumpWidget(Stack(
262
      textDirection: TextDirection.ltr,
263
      children: <Widget>[
264 265
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
        Container(key: const ValueKey<int>(2), child: Container(key: key)),
266 267 268 269 270 271
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 9 - moving and appearing later', (WidgetTester tester) async {
272 273
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
274
      textDirection: TextDirection.ltr,
275
      children: <Widget>[
276 277 278
        Container(key: const ValueKey<int>(0), child: Container(key: key)),
        Container(key: const ValueKey<int>(1)),
        Container(key: const ValueKey<int>(2)),
279 280
      ],
    ));
281
    await tester.pumpWidget(Stack(
282
      textDirection: TextDirection.ltr,
283
      children: <Widget>[
284 285 286
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
        Container(key: const ValueKey<int>(2), child: Container(key: key)),
287 288 289 290 291 292
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 10 - moving and appearing earlier', (WidgetTester tester) async {
293 294
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
295
      textDirection: TextDirection.ltr,
296
      children: <Widget>[
297 298 299
        Container(key: const ValueKey<int>(1)),
        Container(key: const ValueKey<int>(2)),
        Container(key: const ValueKey<int>(3), child: Container(key: key)),
300 301
      ],
    ));
302
    await tester.pumpWidget(Stack(
303
      textDirection: TextDirection.ltr,
304
      children: <Widget>[
305 306 307
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
        Container(key: const ValueKey<int>(2), child: Container(key: key)),
        Container(key: const ValueKey<int>(3)),
308 309 310 311 312 313
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 11 - double sibling appearance', (WidgetTester tester) async {
314 315
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
316
      textDirection: TextDirection.ltr,
317
      children: <Widget>[
318 319
        Container(key: key),
        Container(key: key),
320 321 322 323 324 325
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 12 - all kinds of badness at once', (WidgetTester tester) async {
326 327 328 329
    final Key key1 = GlobalKey(debugLabel: 'problematic');
    final Key key2 = GlobalKey(debugLabel: 'problematic'); // intentionally the same label
    final Key key3 = GlobalKey(debugLabel: 'also problematic');
    await tester.pumpWidget(Stack(
330
      textDirection: TextDirection.ltr,
331
      children: <Widget>[
332 333 334 335 336 337 338 339 340
        Container(key: key1),
        Container(key: key1),
        Container(key: key2),
        Container(key: key1),
        Container(key: key1),
        Container(key: key2),
        Container(key: key1),
        Container(key: key1),
        Row(
341
          children: <Widget>[
342 343 344 345 346 347 348
            Container(key: key1),
            Container(key: key1),
            Container(key: key2),
            Container(key: key2),
            Container(key: key2),
            Container(key: key3),
            Container(key: key2),
349 350
          ],
        ),
351
        Row(
352
          children: <Widget>[
353 354 355
            Container(key: key1),
            Container(key: key1),
            Container(key: key3),
356 357
          ],
        ),
358
        Container(key: key3),
359 360
      ],
    ));
361 362 363 364 365 366 367 368 369 370
    final dynamic exception = tester.takeException();
    expect(exception, isFlutterError);
    expect(
      exception.toString(),
      equalsIgnoringHashCodes(
        'Duplicate keys found.\n'
        'If multiple keyed nodes exist as children of another node, they must have unique keys.\n'
        'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, overflow: clip) has multiple children with key [GlobalKey#00000 problematic].'
      ),
    );
371 372 373
  });

  testWidgets('GlobalKey duplication 13 - all kinds of badness at once', (WidgetTester tester) async {
374 375 376 377
    final Key key1 = GlobalKey(debugLabel: 'problematic');
    final Key key2 = GlobalKey(debugLabel: 'problematic'); // intentionally the same label
    final Key key3 = GlobalKey(debugLabel: 'also problematic');
    await tester.pumpWidget(Stack(
378
      textDirection: TextDirection.ltr,
379
      children: <Widget>[
380 381 382
        Container(key: key1),
        Container(key: key2),
        Container(key: key3),
383 384
      ]),
    );
385
    await tester.pumpWidget(Stack(
386
      textDirection: TextDirection.ltr,
387
      children: <Widget>[
388 389 390 391 392 393 394 395 396
        Container(key: key1),
        Container(key: key1),
        Container(key: key2),
        Container(key: key1),
        Container(key: key1),
        Container(key: key2),
        Container(key: key1),
        Container(key: key1),
        Row(
397
          children: <Widget>[
398 399 400 401 402 403 404
            Container(key: key1),
            Container(key: key1),
            Container(key: key2),
            Container(key: key2),
            Container(key: key2),
            Container(key: key3),
            Container(key: key2),
405 406
          ],
        ),
407
        Row(
408
          children: <Widget>[
409 410 411
            Container(key: key1),
            Container(key: key1),
            Container(key: key3),
412 413
          ],
        ),
414
        Container(key: key3),
415 416 417 418 419 420
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 14 - moving during build - before', (WidgetTester tester) async {
421 422
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
423
      textDirection: TextDirection.ltr,
424
      children: <Widget>[
425 426 427
        Container(key: key),
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1)),
428 429
      ],
    ));
430
    await tester.pumpWidget(Stack(
431
      textDirection: TextDirection.ltr,
432
      children: <Widget>[
433 434
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
435 436 437 438 439
      ],
    ));
  });

  testWidgets('GlobalKey duplication 15 - duplicating during build - before', (WidgetTester tester) async {
440 441
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
442
      textDirection: TextDirection.ltr,
443
      children: <Widget>[
444 445 446
        Container(key: key),
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1)),
447 448
      ],
    ));
449
    await tester.pumpWidget(Stack(
450
      textDirection: TextDirection.ltr,
451
      children: <Widget>[
452 453 454
        Container(key: key),
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
455 456 457 458 459 460
      ],
    ));
    expect(tester.takeException(), isFlutterError);
  });

  testWidgets('GlobalKey duplication 16 - moving during build - after', (WidgetTester tester) async {
461 462
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
463
      textDirection: TextDirection.ltr,
464
      children: <Widget>[
465 466 467
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1)),
        Container(key: key),
468 469
      ],
    ));
470
    await tester.pumpWidget(Stack(
471
      textDirection: TextDirection.ltr,
472
      children: <Widget>[
473 474
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
475 476 477 478 479
      ],
    ));
  });

  testWidgets('GlobalKey duplication 17 - duplicating during build - after', (WidgetTester tester) async {
480 481
    final Key key = GlobalKey(debugLabel: 'problematic');
    await tester.pumpWidget(Stack(
482
      textDirection: TextDirection.ltr,
483
      children: <Widget>[
484 485 486
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1)),
        Container(key: key),
487 488 489 490 491 492 493 494
      ],
    ));
    int count = 0;
    final FlutterExceptionHandler oldHandler = FlutterError.onError;
    FlutterError.onError = (FlutterErrorDetails details) {
      expect(details.exception, isFlutterError);
      count += 1;
    };
495
    await tester.pumpWidget(Stack(
496
      textDirection: TextDirection.ltr,
497
      children: <Widget>[
498 499 500
        Container(key: const ValueKey<int>(0)),
        Container(key: const ValueKey<int>(1), child: Container(key: key)),
        Container(key: key),
501 502 503 504 505 506
      ],
    ));
    FlutterError.onError = oldHandler;
    expect(count, 2);
  });

507 508 509 510 511 512 513 514 515 516 517 518 519
  testWidgets('GlobalKey - dettach and re-attach child to different parents', (WidgetTester tester) async {
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: Center(
        child: Container(
          height: 100,
          child: CustomScrollView(
            controller: ScrollController(),
            slivers: <Widget>[
              SliverList(
                delegate: SliverChildListDelegate(<Widget>[
                  Text('child', key: GlobalKey()),
                ]),
520
              ),
521 522 523 524 525 526 527 528 529 530 531 532
            ],
          ),
        ),
      ),
    ));
    final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList));
    Element childElement;
    // Removing and recreating child with same Global Key should not trigger
    // duplicate key error.
    element.visitChildren((Element e) {
      childElement = e;
    });
533
    element.removeChild(childElement.renderObject as RenderBox);
534 535 536 537
    element.createChild(0, after: null);
    element.visitChildren((Element e) {
      childElement = e;
    });
538
    element.removeChild(childElement.renderObject as RenderBox);
539 540 541
    element.createChild(0, after: null);
  });

542 543 544
  testWidgets('Defunct setState throws exception', (WidgetTester tester) async {
    StateSetter setState;

545
    await tester.pumpWidget(StatefulBuilder(
546 547
      builder: (BuildContext context, StateSetter setter) {
        setState = setter;
548
        return Container();
549 550 551 552 553 554
      },
    ));

    // Control check that setState doesn't throw an exception.
    setState(() { });

555
    await tester.pumpWidget(Container());
556 557 558 559 560

    expect(() { setState(() { }); }, throwsFlutterError);
  });

  testWidgets('State toString', (WidgetTester tester) async {
561
    final TestState state = TestState();
562
    expect(state.toString(), contains('no widget'));
563 564 565 566 567 568 569 570
  });

  testWidgets('debugPrintGlobalKeyedWidgetLifecycle control test', (WidgetTester tester) async {
    expect(debugPrintGlobalKeyedWidgetLifecycle, isFalse);

    final DebugPrintCallback oldCallback = debugPrint;
    debugPrintGlobalKeyedWidgetLifecycle = true;

571
    final List<String> log = <String>[];
572 573 574 575
    debugPrint = (String message, { int wrapWidth }) {
      log.add(message);
    };

576 577
    final GlobalKey key = GlobalKey();
    await tester.pumpWidget(Container(key: key));
578
    expect(log, isEmpty);
579
    await tester.pumpWidget(const Placeholder());
580 581 582 583 584 585 586
    debugPrint = oldCallback;
    debugPrintGlobalKeyedWidgetLifecycle = false;

    expect(log.length, equals(2));
    expect(log[0], matches('Deactivated'));
    expect(log[1], matches('Discarding .+ from inactive elements list.'));
  });
587 588 589

  testWidgets('MultiChildRenderObjectElement.children', (WidgetTester tester) async {
    GlobalKey key0, key1, key2;
590 591
    await tester.pumpWidget(Column(
      key: key0 = GlobalKey(),
592
      children: <Widget>[
593 594 595 596 597
        Container(),
        Container(key: key1 = GlobalKey()),
        Container(child: Container()),
        Container(key: key2 = GlobalKey()),
        Container(),
598 599
      ],
    ));
600
    final MultiChildRenderObjectElement element = key0.currentContext as MultiChildRenderObjectElement;
601
    expect(
602
      element.children.map((Element element) => element.widget.key),
603 604 605
      <Key>[null, key1, null, key2, null],
    );
  });
606 607 608

  testWidgets('Element diagnostics', (WidgetTester tester) async {
    GlobalKey key0;
609 610
    await tester.pumpWidget(Column(
      key: key0 = GlobalKey(),
611
      children: <Widget>[
612 613 614 615 616
        Container(),
        Container(key: GlobalKey()),
        Container(child: Container()),
        Container(key: GlobalKey()),
        Container(),
617 618
      ],
    ));
619
    final MultiChildRenderObjectElement element = key0.currentContext as MultiChildRenderObjectElement;
620

621 622 623 624
    expect(element, hasAGoodToStringDeep);
    expect(
      element.toStringDeep(),
      equalsIgnoringHashCodes(
625
        'Column-[GlobalKey#00000](direction: vertical, mainAxisAlignment: start, crossAxisAlignment: center, renderObject: RenderFlex#00000)\n'
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
        '├Container\n'
        '│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
        '│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
        '├Container-[GlobalKey#00000]\n'
        '│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
        '│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
        '├Container\n'
        '│└Container\n'
        '│ └LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
        '│  └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
        '├Container-[GlobalKey#00000]\n'
        '│└LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
        '│ └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n'
        '└Container\n'
        ' └LimitedBox(maxWidth: 0.0, maxHeight: 0.0, renderObject: RenderLimitedBox#00000 relayoutBoundary=up1)\n'
        '  └ConstrainedBox(BoxConstraints(biggest), renderObject: RenderConstrainedBox#00000 relayoutBoundary=up2)\n',
642 643
      ),
    );
644
  });
645 646

  testWidgets('Element diagnostics with null child', (WidgetTester tester) async {
647
    await tester.pumpWidget(const NullChildTest());
648 649 650 651 652 653 654 655 656 657 658 659
    final NullChildElement test = tester.element<NullChildElement>(find.byType(NullChildTest));
    test.includeChild = true;
    expect(
      tester.binding.renderViewElement.toStringDeep(),
      equalsIgnoringHashCodes(
        '[root](renderObject: RenderView#4a0f0)\n'
        '└NullChildTest(dirty)\n'
        ' └<null child>\n',
      ),
    );
    test.includeChild = false;
  });
660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712

  testWidgets('scheduleBuild while debugBuildingDirtyElements is true', (WidgetTester tester) async {
    /// ignore here is required for testing purpose because changing the flag properly is hard
    // ignore: invalid_use_of_protected_member
    tester.binding.debugBuildingDirtyElements = true;
    FlutterError error;
    try {
      tester.binding.buildOwner.scheduleBuildFor(
        DirtyElementWithCustomBuildOwner(tester.binding.buildOwner, Container()));
    } on FlutterError catch (e) {
      error = e;
    } finally {
      expect(error, isNotNull);
      expect(error.diagnostics.length, 3);
      expect(error.diagnostics.last.level, DiagnosticLevel.hint);
      expect(
        error.diagnostics.last.toStringDeep(),
        equalsIgnoringHashCodes(
          'This might be because setState() was called from a layout or\n'
          'paint callback. If a change is needed to the widget tree, it\n'
          'should be applied as the tree is being built. Scheduling a change\n'
          'for the subsequent frame instead results in an interface that\n'
          'lags behind by one frame. If this was done to make your build\n'
          'dependent on a size measured at layout time, consider using a\n'
          'LayoutBuilder, CustomSingleChildLayout, or\n'
          'CustomMultiChildLayout. If, on the other hand, the one frame\n'
          'delay is the desired effect, for example because this is an\n'
          'animation, consider scheduling the frame in a post-frame callback\n'
          'using SchedulerBinding.addPostFrameCallback or using an\n'
          'AnimationController to trigger the animation.\n',
        ),
      );
      expect(
        error.toStringDeep(),
        'FlutterError\n'
        '   Build scheduled during frame.\n'
        '   While the widget tree was being built, laid out, and painted, a\n'
        '   new frame was scheduled to rebuild the widget tree.\n'
        '   This might be because setState() was called from a layout or\n'
        '   paint callback. If a change is needed to the widget tree, it\n'
        '   should be applied as the tree is being built. Scheduling a change\n'
        '   for the subsequent frame instead results in an interface that\n'
        '   lags behind by one frame. If this was done to make your build\n'
        '   dependent on a size measured at layout time, consider using a\n'
        '   LayoutBuilder, CustomSingleChildLayout, or\n'
        '   CustomMultiChildLayout. If, on the other hand, the one frame\n'
        '   delay is the desired effect, for example because this is an\n'
        '   animation, consider scheduling the frame in a post-frame callback\n'
        '   using SchedulerBinding.addPostFrameCallback or using an\n'
        '   AnimationController to trigger the animation.\n',
      );
    }
  });
713 714 715
}

class NullChildTest extends Widget {
716
  const NullChildTest({ Key key }) : super(key: key);
717
  @override
718
  Element createElement() => NullChildElement(this);
719
}
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

class NullChildElement extends Element {
  NullChildElement(Widget widget) : super(widget);

  bool includeChild = false;

  @override
  void visitChildren(ElementVisitor visitor) {
    if (includeChild)
      visitor(null);
  }

  @override
  void forgetChild(Element child) { }

  @override
  void performRebuild() { }
737
}
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757


class DirtyElementWithCustomBuildOwner extends Element {
  DirtyElementWithCustomBuildOwner(BuildOwner buildOwner, Widget widget)
    : _owner = buildOwner, super(widget);

  final BuildOwner _owner;

  @override
  void forgetChild(Element child) {}

  @override
  void performRebuild() {}

  @override
  BuildOwner get owner => _owner;

  @override
  bool get dirty => true;
}