mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* Update project.pbxproj files to say Flutter rather than Chromium Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright. * Update the copyright notice checker to require a standard notice on all files * Update copyrights on Dart files. (This was a mechanical commit.) * Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine. Some were already marked "The Flutter Authors", not clear why. Their dates have been normalized. Some were missing the blank line after the license. Some were randomly different in trivial ways for no apparent reason (e.g. missing the trailing period). * Clean up the copyrights in non-Dart files. (Manual edits.) Also, make sure templates don't have copyrights. * Fix some more ORGANIZATIONNAMEs
757 lines
26 KiB
Dart
757 lines
26 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// 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';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
class TestState extends State<StatefulWidget> {
|
|
@override
|
|
Widget build(BuildContext context) => null;
|
|
}
|
|
|
|
@optionalTypeArgs
|
|
class _MyGlobalObjectKey<T extends State<StatefulWidget>> extends GlobalObjectKey<T> {
|
|
const _MyGlobalObjectKey(Object value) : super(value);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('UniqueKey control test', (WidgetTester tester) async {
|
|
final Key key = UniqueKey();
|
|
expect(key, hasOneLineDescription);
|
|
expect(key, isNot(equals(UniqueKey())));
|
|
});
|
|
|
|
testWidgets('ObjectKey control test', (WidgetTester tester) async {
|
|
final Object a = Object();
|
|
final Object b = Object();
|
|
final Key keyA = ObjectKey(a);
|
|
final Key keyA2 = ObjectKey(a);
|
|
final Key keyB = ObjectKey(b);
|
|
|
|
expect(keyA, hasOneLineDescription);
|
|
expect(keyA, equals(keyA2));
|
|
expect(keyA.hashCode, equals(keyA2.hashCode));
|
|
expect(keyA, isNot(equals(keyB)));
|
|
});
|
|
|
|
testWidgets('GlobalObjectKey toString test', (WidgetTester tester) async {
|
|
const GlobalObjectKey one = GlobalObjectKey(1);
|
|
const GlobalObjectKey<TestState> two = GlobalObjectKey<TestState>(2);
|
|
const GlobalObjectKey three = _MyGlobalObjectKey(3);
|
|
const GlobalObjectKey<TestState> four = _MyGlobalObjectKey<TestState>(4);
|
|
|
|
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)}]'));
|
|
});
|
|
|
|
testWidgets('GlobalObjectKey control test', (WidgetTester tester) async {
|
|
final Object a = Object();
|
|
final Object b = Object();
|
|
final Key keyA = GlobalObjectKey(a);
|
|
final Key keyA2 = GlobalObjectKey(a);
|
|
final Key keyB = GlobalObjectKey(b);
|
|
|
|
expect(keyA, hasOneLineDescription);
|
|
expect(keyA, equals(keyA2));
|
|
expect(keyA.hashCode, equals(keyA2.hashCode));
|
|
expect(keyA, isNot(equals(keyB)));
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 1 - double appearance', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(
|
|
key: const ValueKey<int>(1),
|
|
child: SizedBox(key: key),
|
|
),
|
|
Container(
|
|
key: const ValueKey<int>(2),
|
|
child: Placeholder(key: key),
|
|
),
|
|
],
|
|
));
|
|
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.'
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 2 - splitting and changing type', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(
|
|
key: const ValueKey<int>(1),
|
|
),
|
|
Container(
|
|
key: const ValueKey<int>(2),
|
|
),
|
|
Container(
|
|
key: key
|
|
),
|
|
],
|
|
));
|
|
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(
|
|
key: const ValueKey<int>(1),
|
|
child: SizedBox(key: key),
|
|
),
|
|
Container(
|
|
key: const ValueKey<int>(2),
|
|
child: Placeholder(key: key),
|
|
),
|
|
],
|
|
));
|
|
|
|
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.'
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 3 - splitting and changing type', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
SizedBox(key: key),
|
|
Placeholder(key: key),
|
|
],
|
|
));
|
|
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.'
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 4 - splitting and half changing type', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
Placeholder(key: key),
|
|
],
|
|
));
|
|
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.'
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 5 - splitting and half changing type', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Placeholder(key: key),
|
|
Container(key: key),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 6 - splitting and not changing type', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
Container(key: key),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 7 - appearing later', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
Container(key: const ValueKey<int>(2)),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
Container(key: const ValueKey<int>(2), child: Container(key: key)),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 8 - appearing earlier', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(1)),
|
|
Container(key: const ValueKey<int>(2), child: Container(key: key)),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
Container(key: const ValueKey<int>(2), child: Container(key: key)),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 9 - moving and appearing later', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(0), child: Container(key: key)),
|
|
Container(key: const ValueKey<int>(1)),
|
|
Container(key: const ValueKey<int>(2)),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
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)),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 10 - moving and appearing earlier', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(1)),
|
|
Container(key: const ValueKey<int>(2)),
|
|
Container(key: const ValueKey<int>(3), child: Container(key: key)),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
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)),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 11 - double sibling appearance', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
Container(key: key),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 12 - all kinds of badness at once', (WidgetTester tester) async {
|
|
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(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
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(
|
|
children: <Widget>[
|
|
Container(key: key1),
|
|
Container(key: key1),
|
|
Container(key: key2),
|
|
Container(key: key2),
|
|
Container(key: key2),
|
|
Container(key: key3),
|
|
Container(key: key2),
|
|
],
|
|
),
|
|
Row(
|
|
children: <Widget>[
|
|
Container(key: key1),
|
|
Container(key: key1),
|
|
Container(key: key3),
|
|
],
|
|
),
|
|
Container(key: key3),
|
|
],
|
|
));
|
|
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].'
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 13 - all kinds of badness at once', (WidgetTester tester) async {
|
|
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(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key1),
|
|
Container(key: key2),
|
|
Container(key: key3),
|
|
]),
|
|
);
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
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(
|
|
children: <Widget>[
|
|
Container(key: key1),
|
|
Container(key: key1),
|
|
Container(key: key2),
|
|
Container(key: key2),
|
|
Container(key: key2),
|
|
Container(key: key3),
|
|
Container(key: key2),
|
|
],
|
|
),
|
|
Row(
|
|
children: <Widget>[
|
|
Container(key: key1),
|
|
Container(key: key1),
|
|
Container(key: key3),
|
|
],
|
|
),
|
|
Container(key: key3),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 14 - moving during build - before', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1)),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
],
|
|
));
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 15 - duplicating during build - before', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1)),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: key),
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
],
|
|
));
|
|
expect(tester.takeException(), isFlutterError);
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 16 - moving during build - after', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1)),
|
|
Container(key: key),
|
|
],
|
|
));
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
],
|
|
));
|
|
});
|
|
|
|
testWidgets('GlobalKey duplication 17 - duplicating during build - after', (WidgetTester tester) async {
|
|
final Key key = GlobalKey(debugLabel: 'problematic');
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1)),
|
|
Container(key: key),
|
|
],
|
|
));
|
|
int count = 0;
|
|
final FlutterExceptionHandler oldHandler = FlutterError.onError;
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
expect(details.exception, isFlutterError);
|
|
count += 1;
|
|
};
|
|
await tester.pumpWidget(Stack(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Container(key: const ValueKey<int>(0)),
|
|
Container(key: const ValueKey<int>(1), child: Container(key: key)),
|
|
Container(key: key),
|
|
],
|
|
));
|
|
FlutterError.onError = oldHandler;
|
|
expect(count, 2);
|
|
});
|
|
|
|
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()),
|
|
]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
));
|
|
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;
|
|
});
|
|
element.removeChild(childElement.renderObject);
|
|
element.createChild(0, after: null);
|
|
element.visitChildren((Element e) {
|
|
childElement = e;
|
|
});
|
|
element.removeChild(childElement.renderObject);
|
|
element.createChild(0, after: null);
|
|
});
|
|
|
|
testWidgets('Defunct setState throws exception', (WidgetTester tester) async {
|
|
StateSetter setState;
|
|
|
|
await tester.pumpWidget(StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setter) {
|
|
setState = setter;
|
|
return Container();
|
|
},
|
|
));
|
|
|
|
// Control check that setState doesn't throw an exception.
|
|
setState(() { });
|
|
|
|
await tester.pumpWidget(Container());
|
|
|
|
expect(() { setState(() { }); }, throwsFlutterError);
|
|
});
|
|
|
|
testWidgets('State toString', (WidgetTester tester) async {
|
|
final TestState state = TestState();
|
|
expect(state.toString(), contains('no widget'));
|
|
});
|
|
|
|
testWidgets('debugPrintGlobalKeyedWidgetLifecycle control test', (WidgetTester tester) async {
|
|
expect(debugPrintGlobalKeyedWidgetLifecycle, isFalse);
|
|
|
|
final DebugPrintCallback oldCallback = debugPrint;
|
|
debugPrintGlobalKeyedWidgetLifecycle = true;
|
|
|
|
final List<String> log = <String>[];
|
|
debugPrint = (String message, { int wrapWidth }) {
|
|
log.add(message);
|
|
};
|
|
|
|
final GlobalKey key = GlobalKey();
|
|
await tester.pumpWidget(Container(key: key));
|
|
expect(log, isEmpty);
|
|
await tester.pumpWidget(const Placeholder());
|
|
debugPrint = oldCallback;
|
|
debugPrintGlobalKeyedWidgetLifecycle = false;
|
|
|
|
expect(log.length, equals(2));
|
|
expect(log[0], matches('Deactivated'));
|
|
expect(log[1], matches('Discarding .+ from inactive elements list.'));
|
|
});
|
|
|
|
testWidgets('MultiChildRenderObjectElement.children', (WidgetTester tester) async {
|
|
GlobalKey key0, key1, key2;
|
|
await tester.pumpWidget(Column(
|
|
key: key0 = GlobalKey(),
|
|
children: <Widget>[
|
|
Container(),
|
|
Container(key: key1 = GlobalKey()),
|
|
Container(child: Container()),
|
|
Container(key: key2 = GlobalKey()),
|
|
Container(),
|
|
],
|
|
));
|
|
final MultiChildRenderObjectElement element = key0.currentContext;
|
|
expect(
|
|
element.children.map((Element element) => element.widget.key),
|
|
<Key>[null, key1, null, key2, null],
|
|
);
|
|
});
|
|
|
|
testWidgets('Element diagnostics', (WidgetTester tester) async {
|
|
GlobalKey key0;
|
|
await tester.pumpWidget(Column(
|
|
key: key0 = GlobalKey(),
|
|
children: <Widget>[
|
|
Container(),
|
|
Container(key: GlobalKey()),
|
|
Container(child: Container()),
|
|
Container(key: GlobalKey()),
|
|
Container(),
|
|
],
|
|
));
|
|
final MultiChildRenderObjectElement element = key0.currentContext;
|
|
|
|
expect(element, hasAGoodToStringDeep);
|
|
expect(
|
|
element.toStringDeep(),
|
|
equalsIgnoringHashCodes(
|
|
'Column-[GlobalKey#00000](direction: vertical, mainAxisAlignment: start, crossAxisAlignment: center, renderObject: RenderFlex#00000)\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'
|
|
'│└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',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Element diagnostics with null child', (WidgetTester tester) async {
|
|
await tester.pumpWidget(NullChildTest());
|
|
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;
|
|
});
|
|
|
|
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',
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
class NullChildTest extends Widget {
|
|
@override
|
|
Element createElement() => NullChildElement(this);
|
|
}
|
|
|
|
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() { }
|
|
}
|
|
|
|
|
|
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;
|
|
}
|