Commit 4e48a737 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix flex floating point error causing unnecessary striped warnings (#12424)

Also:

 * Provide a better message when you lerp from infinity to finity
   constraints.

 * Make the striped marker support RTL.

 * By popular demand, dump a warning to the console the first time
   a particular Flex overflows. (Resets on hot reload.)
parent 4c23397e
......@@ -16,6 +16,7 @@ Exception handling in test harness - string
════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)?
Test failed\. See exception logs above\.
The test description was: Exception handling in test harness - string
*
[^═]*(this line contains the test framework's output with the clock and so forth)?
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
......@@ -35,6 +36,7 @@ Exception handling in test harness - FlutterError
════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)?
Test failed\. See exception logs above\.
The test description was: Exception handling in test harness - FlutterError
*
[^═]*(this line contains the test framework's output with the clock and so forth)?
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
......@@ -54,5 +56,6 @@ Exception handling in test harness - uncaught Future error
════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)?
Test failed\. See exception logs above\.
The test description was: Exception handling in test harness - uncaught Future error
*
.*..:.. \+0 -3: Some tests failed\. *
......@@ -20,5 +20,6 @@ TestAsyncUtils - custom guarded sections
════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)?
Test failed\. See exception logs above\.
The test description was: TestAsyncUtils - custom guarded sections
*
.*..:.. \+0 -1: Some tests failed\. *
......@@ -18,6 +18,7 @@ The test description was:
TestAsyncUtils - handling unguarded async helper functions
════════════════════════════════════════════════════════════════════════════════════════════════════
.*..:.. \+0 -1: - TestAsyncUtils - handling unguarded async helper functions *
Test failed. See exception logs above.
Test failed\. See exception logs above\.
The test description was: TestAsyncUtils - handling unguarded async helper functions
*
.*..:.. \+0 -1: Some tests failed\. *
......@@ -200,7 +200,11 @@ class FlutterError extends AssertionError {
_errorCount = 0;
}
static const int _kWrapWidth = 100;
/// The width to which [dumpErrorToConsole] will wrap lines.
///
/// This can be used to ensure strings will not exceed the length at which
/// they will wrap, e.g. when placing ASCII art diagrams in messages.
static const int wrapWidth = 100;
/// Prints the given exception details to the console.
///
......@@ -227,13 +231,13 @@ class FlutterError extends AssertionError {
return;
if (_errorCount == 0 || forceReport) {
final String header = '\u2550\u2550\u2561 EXCEPTION CAUGHT BY ${details.library} \u255E'.toUpperCase();
final String footer = '\u2550' * _kWrapWidth;
final String footer = '\u2550' * wrapWidth;
debugPrint('$header${"\u2550" * (footer.length - header.length)}');
final String verb = 'thrown${ details.context != null ? " ${details.context}" : ""}';
if (details.exception is NullThrownError) {
debugPrint('The null value was $verb.', wrapWidth: _kWrapWidth);
debugPrint('The null value was $verb.', wrapWidth: wrapWidth);
} else if (details.exception is num) {
debugPrint('The number ${details.exception} was $verb.', wrapWidth: _kWrapWidth);
debugPrint('The number ${details.exception} was $verb.', wrapWidth: wrapWidth);
} else {
String errorName;
if (details.exception is AssertionError) {
......@@ -252,7 +256,7 @@ class FlutterError extends AssertionError {
String message = details.exceptionAsString();
if (message.startsWith(prefix))
message = message.substring(prefix.length);
debugPrint('The following $errorName was $verb:\n$message', wrapWidth: _kWrapWidth);
debugPrint('The following $errorName was $verb:\n$message', wrapWidth: wrapWidth);
}
Iterable<String> stackLines = (details.stack != null) ? details.stack.toString().trimRight().split('\n') : null;
if ((details.exception is AssertionError) && (details.exception is! FlutterError)) {
......@@ -276,25 +280,25 @@ class FlutterError extends AssertionError {
if (ourFault) {
debugPrint('\nEither the assertion indicates an error in the framework itself, or we should '
'provide substantially more information in this error message to help you determine '
'and fix the underlying cause.', wrapWidth: _kWrapWidth);
debugPrint('In either case, please report this assertion by filing a bug on GitHub:', wrapWidth: _kWrapWidth);
'and fix the underlying cause.', wrapWidth: wrapWidth);
debugPrint('In either case, please report this assertion by filing a bug on GitHub:', wrapWidth: wrapWidth);
debugPrint(' https://github.com/flutter/flutter/issues/new');
}
}
if (details.stack != null) {
debugPrint('\nWhen the exception was thrown, this was the stack:', wrapWidth: _kWrapWidth);
debugPrint('\nWhen the exception was thrown, this was the stack:', wrapWidth: wrapWidth);
if (details.stackFilter != null) {
stackLines = details.stackFilter(stackLines);
} else {
stackLines = defaultStackFilter(stackLines);
}
for (String line in stackLines)
debugPrint(line, wrapWidth: _kWrapWidth);
debugPrint(line, wrapWidth: wrapWidth);
}
if (details.informationCollector != null) {
final StringBuffer information = new StringBuffer();
details.informationCollector(information);
debugPrint('\n${information.toString().trimRight()}', wrapWidth: _kWrapWidth);
debugPrint('\n${information.toString().trimRight()}', wrapWidth: wrapWidth);
}
debugPrint(footer);
} else {
......
......@@ -411,11 +411,15 @@ class BoxConstraints extends Constraints {
return a * (1.0 - t);
assert(a.debugAssertIsValid());
assert(b.debugAssertIsValid());
assert((a.minWidth.isFinite && b.minWidth.isFinite) || (a.minWidth == double.INFINITY && b.minWidth == double.INFINITY), 'Cannot interpolate between finite constraints and unbounded constraints.');
assert((a.maxWidth.isFinite && b.maxWidth.isFinite) || (a.maxWidth == double.INFINITY && b.maxWidth == double.INFINITY), 'Cannot interpolate between finite constraints and unbounded constraints.');
assert((a.minHeight.isFinite && b.minHeight.isFinite) || (a.minHeight == double.INFINITY && b.minHeight == double.INFINITY), 'Cannot interpolate between finite constraints and unbounded constraints.');
assert((a.maxHeight.isFinite && b.maxHeight.isFinite) || (a.maxHeight == double.INFINITY && b.maxHeight == double.INFINITY), 'Cannot interpolate between finite constraints and unbounded constraints.');
return new BoxConstraints(
minWidth: ui.lerpDouble(a.minWidth, b.minWidth, t),
maxWidth: ui.lerpDouble(a.maxWidth, b.maxWidth, t),
minHeight: ui.lerpDouble(a.minHeight, b.minHeight, t),
maxHeight: ui.lerpDouble(a.maxHeight, b.maxHeight, t)
minWidth: a.minWidth.isFinite ? ui.lerpDouble(a.minWidth, b.minWidth, t) : double.INFINITY,
maxWidth: a.maxWidth.isFinite ? ui.lerpDouble(a.maxWidth, b.maxWidth, t) : double.INFINITY,
minHeight: a.minHeight.isFinite ? ui.lerpDouble(a.minHeight, b.minHeight, t) : double.INFINITY,
maxHeight: a.maxHeight.isFinite ? ui.lerpDouble(a.maxHeight, b.maxHeight, t) : double.INFINITY,
);
}
......
......@@ -5,6 +5,8 @@
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'box.dart';
import 'object.dart';
......@@ -641,6 +643,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
double crossSize = 0.0;
double allocatedSize = 0.0; // Sum of the sizes of the the non-flexible children.
RenderBox child = firstChild;
RenderBox lastFlexChild;
while (child != null) {
final FlexParentData childParentData = child.parentData;
totalChildren++;
......@@ -707,6 +710,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
);
}());
totalFlex += childParentData.flex;
lastFlexChild = child;
} else {
BoxConstraints innerConstraints;
if (crossAxisAlignment == CrossAxisAlignment.stretch) {
......@@ -740,6 +744,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
// Distribute free space to flexible children, and determine baseline.
final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
double allocatedFlexSpace = 0.0;
double maxBaselineDistance = 0.0;
if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.NAN;
......@@ -747,7 +752,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
while (child != null) {
final int flex = _getFlex(child);
if (flex > 0) {
final double maxChildExtent = canFlex ? spacePerFlex * flex : double.INFINITY;
final double maxChildExtent = canFlex ? (child == lastFlexChild ? (freeSpace - allocatedFlexSpace) : spacePerFlex * flex) : double.INFINITY;
double minChildExtent;
switch (_getFit(child)) {
case FlexFit.tight:
......@@ -790,7 +795,10 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
}
}
child.layout(innerConstraints, parentUsesSize: true);
allocatedSize += _getMainSize(child);
final double childSize = _getMainSize(child);
assert(childSize <= maxChildExtent);
allocatedSize += childSize;
allocatedFlexSpace += maxChildExtent;
crossSize = math.max(crossSize, _getCrossSize(child));
}
if (crossAxisAlignment == CrossAxisAlignment.baseline) {
......@@ -945,6 +953,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
static Paint _debugMarkerPaint;
TextPainter _debugMarkerLabel;
bool _debugReportOverflow = true;
@override
void reassemble() {
super.reassemble();
assert(() {
_debugReportOverflow = true;
return true;
}());
}
@override
void paint(PaintingContext context, Offset offset) {
if (_overflow <= 0.0) {
......@@ -977,12 +996,31 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
pixels = _overflow.toStringAsPrecision(3);
}
String label;
Rect markerRect;
String label;
Offset labelOffset;
double labelAngle;
switch (direction) {
case Axis.horizontal:
markerRect = offset + new Offset(size.width * (1.0 - _kMarkerSize), 0.0) &
new Size(size.width * _kMarkerSize, size.height);
if (textDirection != null) {
final Size markerSize = new Size(size.width * _kMarkerSize, size.height);
switch (textDirection) {
case TextDirection.rtl:
labelAngle = math.PI / 2.0;
markerRect = offset + new Offset(-size.width * _kMarkerSize, 0.0) & markerSize;
labelOffset = markerRect.centerLeft;
break;
case TextDirection.ltr:
labelAngle = -math.PI / 2.0;
markerRect = offset + new Offset(size.width * (1.0 - _kMarkerSize), 0.0) & markerSize;
labelOffset = markerRect.centerRight;
break;
}
} else {
markerRect = (offset & size).deflate(size.shortestSide * _kMarkerSize);
labelOffset = markerRect.center;
labelAngle = 0.0;
}
label = 'ROW OVERFLOWED BY $pixels PIXELS';
break;
case Axis.vertical:
......@@ -1001,13 +1039,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
);
_debugMarkerLabel.layout(); // This is a no-op if the label hasn't changed.
// TODO(ianh): RTL support
switch (direction) {
case Axis.horizontal:
context.canvas.save();
final Offset offset = markerRect.centerRight;
context.canvas.translate(offset.dx, offset.dy);
context.canvas.rotate(-math.PI / 2.0);
context.canvas.translate(labelOffset.dx, labelOffset.dy);
context.canvas.rotate(labelAngle);
_debugMarkerLabel.paint(context.canvas, new Offset(-_debugMarkerLabel.width / 2.0, 0.0));
context.canvas.restore();
break;
......@@ -1016,6 +1052,34 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
break;
}
if (_debugReportOverflow) {
_debugReportOverflow = false;
FlutterError.reportError(new FlutterErrorDetailsForRendering(
exception: 'A ${describeEnum(direction)} $runtimeType overflowed by $pixels pixels.',
library: 'rendering library',
context: 'during layout',
renderObject: this,
informationCollector: (StringBuffer information) {
information.writeln(
'The edge of the $runtimeType that is overflowing has been marked in the rendering '
'with a yellow and black striped pattern. This is usually caused by the contents '
'being too big for the $runtimeType. Consider applying a flex factor (e.g. using '
'an Expanded widget) to force the children of the $runtimeType to fit within the '
'available space instead of being sized to their natural size.'
);
information.writeln(
'This is considered an error condition because it indicates that there is content '
'that cannot be seen. If the content is legitimately bigger than the available '
'space, consider clipping it with a RectClip widget before putting it in the flex, '
'or using a scrollable container rather than a Flex, for example using ListView.'
);
information.writeln('The specific $runtimeType in question is:');
information.writeln(' ${toStringShallow(joiner: '\n ')}');
information.writeln('◢◤' * (FlutterError.wrapWidth ~/ 2));
}
));
}
return true;
}());
}
......
......@@ -971,9 +971,9 @@ void main() {
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
],
),
),
......@@ -992,9 +992,9 @@ void main() {
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
],
flexibleSpace: new DecoratedBox(
decoration: new BoxDecoration(
......@@ -1022,9 +1022,9 @@ void main() {
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
],
flexibleSpace: new DecoratedBox(
decoration: new BoxDecoration(
......@@ -1063,9 +1063,9 @@ void main() {
leading: new Placeholder(key: key),
title: const Text('Abc'),
actions: <Widget>[
const Placeholder(),
const Placeholder(),
const Placeholder(),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
const Placeholder(fallbackWidth: 10.0),
],
bottom: new PreferredSize(
preferredSize: const Size(0.0, kToolbarHeight),
......
......@@ -151,6 +151,9 @@ void main() {
home: buildTable(source),
));
// the column overflows because we're forcing it to 600 pixels high
expect(tester.takeException(), contains('A vertical RenderFlex overflowed by'));
expect(find.text('Gingerbread (0)'), findsOneWidget);
expect(find.text('Gingerbread (1)'), findsNothing);
expect(find.text('42'), findsNWidgets(10));
......
......@@ -90,6 +90,74 @@ void main() {
expect(copy.maxHeight, 97.0);
});
test('BoxConstraints lerp with unbounded width', () {
final BoxConstraints constraints1 = const BoxConstraints(
minWidth: double.INFINITY,
maxWidth: double.INFINITY,
minHeight: 10.0,
maxHeight: 20.0,
);
final BoxConstraints constraints2 = const BoxConstraints(
minWidth: double.INFINITY,
maxWidth: double.INFINITY,
minHeight: 20.0,
maxHeight: 30.0,
);
final BoxConstraints constraints3 = const BoxConstraints(
minWidth: double.INFINITY,
maxWidth: double.INFINITY,
minHeight: 15.0,
maxHeight: 25.0,
);
expect(BoxConstraints.lerp(constraints1, constraints2, 0.5), constraints3);
});
test('BoxConstraints lerp with unbounded height', () {
final BoxConstraints constraints1 = const BoxConstraints(
minWidth: 10.0,
maxWidth: 20.0,
minHeight: double.INFINITY,
maxHeight: double.INFINITY,
);
final BoxConstraints constraints2 = const BoxConstraints(
minWidth: 20.0,
maxWidth: 30.0,
minHeight: double.INFINITY,
maxHeight: double.INFINITY,
);
final BoxConstraints constraints3 = const BoxConstraints(
minWidth: 15.0,
maxWidth: 25.0,
minHeight: double.INFINITY,
maxHeight: double.INFINITY,
);
expect(BoxConstraints.lerp(constraints1, constraints2, 0.5), constraints3);
});
test('BoxConstraints lerp from bounded to unbounded', () {
final BoxConstraints constraints1 = const BoxConstraints(
minWidth: double.INFINITY,
maxWidth: double.INFINITY,
minHeight: double.INFINITY,
maxHeight: double.INFINITY,
);
final BoxConstraints constraints2 = const BoxConstraints(
minWidth: 20.0,
maxWidth: 30.0,
minHeight: double.INFINITY,
maxHeight: double.INFINITY,
);
final BoxConstraints constraints3 = const BoxConstraints(
minWidth: double.INFINITY,
maxWidth: double.INFINITY,
minHeight: 20.0,
maxHeight: 30.0,
);
expect(() => BoxConstraints.lerp(constraints1, constraints2, 0.5), throwsA(const isInstanceOf<AssertionError>()));
expect(() => BoxConstraints.lerp(constraints1, constraints3, 0.5), throwsA(const isInstanceOf<AssertionError>()));
expect(() => BoxConstraints.lerp(constraints2, constraints3, 0.5), throwsA(const isInstanceOf<AssertionError>()));
});
test('BoxConstraints normalize', () {
final BoxConstraints constraints = const BoxConstraints(
minWidth: 3.0,
......
......@@ -221,7 +221,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
]),
......@@ -297,7 +297,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
]),
......@@ -306,7 +306,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
]),
......@@ -315,7 +315,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
]),
......@@ -349,7 +349,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
]),
),
......@@ -357,7 +357,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
]),
......@@ -366,7 +366,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
......@@ -408,7 +408,7 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
]),
......@@ -417,14 +417,14 @@ void main() {
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
]),
),
),
new AutomaticKeepAlive(
child: new Container(
height: 400.0,
child: new Row(children: <Widget>[
child: new Stack(children: <Widget>[
new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
......
......@@ -384,6 +384,8 @@ void main() {
)
));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(0.0));
expect(renderBox.size.height, equals(100.0));
......@@ -777,6 +779,8 @@ void main() {
)
));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(0.0));
expect(renderBox.size.height, equals(100.0));
......
......@@ -72,4 +72,42 @@ void main() {
),
);
});
testWidgets('Doesn\'t overflow because of floating point accumulated error', (WidgetTester tester) async {
// both of these cases have failed in the past due to floating point issues
await tester.pumpWidget(
new Center(
child: new Container(
height: 400.0,
child: new Column(
children: <Widget>[
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
],
),
),
),
);
await tester.pumpWidget(
new Center(
child: new Container(
height: 199.0,
child: new Column(
children: <Widget>[
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
],
),
),
),
);
});
}
......@@ -12,19 +12,19 @@ void main() {
testWidgets('GlobalKey children of one node', (WidgetTester tester) async {
// This is actually a test of the regular duplicate key logic, which
// happens before the duplicate GlobalKey logic.
await tester.pumpWidget(new Row(children: <Widget>[
await tester.pumpWidget(new Stack(children: <Widget>[
new Container(key: const GlobalObjectKey(0)),
new Container(key: const GlobalObjectKey(0)),
]));
final dynamic error = tester.takeException();
expect(error, isFlutterError);
expect(error.toString(), startsWith('Duplicate keys found.\n'));
expect(error.toString(), contains('Row'));
expect(error.toString(), contains('Stack'));
expect(error.toString(), contains('[GlobalObjectKey ${describeIdentity(0)}]'));
});
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row(
await tester.pumpWidget(new Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
......@@ -41,7 +41,7 @@ void main() {
});
testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row(
await tester.pumpWidget(new Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
......@@ -61,7 +61,7 @@ void main() {
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
StateSetter nestedSetState;
bool flag = false;
await tester.pumpWidget(new Row(
await tester.pumpWidget(new Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))),
......
......@@ -302,6 +302,8 @@ void main() {
),
));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0));
......@@ -725,6 +727,8 @@ void main() {
),
));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0));
......@@ -1148,6 +1152,8 @@ void main() {
),
));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0));
......
......@@ -39,6 +39,8 @@ void main() {
),
));
expect(tester.takeException(), contains('overflowed'));
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
......@@ -109,6 +111,8 @@ void main() {
),
));
expect(tester.takeException(), contains('overflowed'));
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
......
......@@ -319,6 +319,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
Zone _parentZone;
Completer<Null> _currentTestCompleter;
String _currentTestDescription; // set from _runTest to _testCompletionHandler
void _testCompletionHandler() {
// This can get called twice, in the case of a Future without listeners failing, and then
......@@ -333,15 +334,21 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// but the test package does, that's how the test package tracks errors. So really we could
// get the same effect here by calling that error handler directly or indeed just throwing.
// However, we call registerException because that's the semantically correct thing...
test_package.registerException('Test failed. See exception logs above.', _EmptyStack.instance);
String additional = '';
if (_currentTestDescription != '')
additional = '\nThe test description was: $_currentTestDescription';
test_package.registerException('Test failed. See exception logs above.$additional', _EmptyStack.instance);
_pendingExceptionDetails = null;
}
_currentTestDescription = null;
if (!_currentTestCompleter.isCompleted)
_currentTestCompleter.complete(null);
}
Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester, String description) {
assert(description != null);
assert(_currentTestDescription == null);
_currentTestDescription = description; // cleared by _testCompletionHandler
assert(inTest);
_oldExceptionHandler = FlutterError.onError;
int _exceptionCount = 0; // number of un-taken exceptions
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment