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 ...@@ -16,6 +16,7 @@ Exception handling in test harness - string
════════════════════════════════════════════════════════════════════════════════════════════════════ ════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)? .*(this line has more of the test framework's output)?
Test failed\. See exception logs above\. 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)? [^═]*(this line contains the test framework's output with the clock and so forth)?
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
...@@ -35,6 +36,7 @@ Exception handling in test harness - FlutterError ...@@ -35,6 +36,7 @@ Exception handling in test harness - FlutterError
════════════════════════════════════════════════════════════════════════════════════════════════════ ════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)? .*(this line has more of the test framework's output)?
Test failed\. See exception logs above\. 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)? [^═]*(this line contains the test framework's output with the clock and so forth)?
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
...@@ -54,5 +56,6 @@ Exception handling in test harness - uncaught Future error ...@@ -54,5 +56,6 @@ Exception handling in test harness - uncaught Future error
════════════════════════════════════════════════════════════════════════════════════════════════════ ════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)? .*(this line has more of the test framework's output)?
Test failed\. See exception logs above\. Test failed\. See exception logs above\.
The test description was: Exception handling in test harness - uncaught Future error
* *
.*..:.. \+0 -3: Some tests failed\. * .*..:.. \+0 -3: Some tests failed\. *
...@@ -20,5 +20,6 @@ TestAsyncUtils - custom guarded sections ...@@ -20,5 +20,6 @@ TestAsyncUtils - custom guarded sections
════════════════════════════════════════════════════════════════════════════════════════════════════ ════════════════════════════════════════════════════════════════════════════════════════════════════
.*(this line has more of the test framework's output)? .*(this line has more of the test framework's output)?
Test failed\. See exception logs above\. Test failed\. See exception logs above\.
The test description was: TestAsyncUtils - custom guarded sections
* *
.*..:.. \+0 -1: Some tests failed\. * .*..:.. \+0 -1: Some tests failed\. *
...@@ -18,6 +18,7 @@ The test description was: ...@@ -18,6 +18,7 @@ The test description was:
TestAsyncUtils - handling unguarded async helper functions TestAsyncUtils - handling unguarded async helper functions
════════════════════════════════════════════════════════════════════════════════════════════════════ ════════════════════════════════════════════════════════════════════════════════════════════════════
.*..:.. \+0 -1: - 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\. * .*..:.. \+0 -1: Some tests failed\. *
...@@ -200,7 +200,11 @@ class FlutterError extends AssertionError { ...@@ -200,7 +200,11 @@ class FlutterError extends AssertionError {
_errorCount = 0; _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. /// Prints the given exception details to the console.
/// ///
...@@ -227,13 +231,13 @@ class FlutterError extends AssertionError { ...@@ -227,13 +231,13 @@ class FlutterError extends AssertionError {
return; return;
if (_errorCount == 0 || forceReport) { if (_errorCount == 0 || forceReport) {
final String header = '\u2550\u2550\u2561 EXCEPTION CAUGHT BY ${details.library} \u255E'.toUpperCase(); 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)}'); debugPrint('$header${"\u2550" * (footer.length - header.length)}');
final String verb = 'thrown${ details.context != null ? " ${details.context}" : ""}'; final String verb = 'thrown${ details.context != null ? " ${details.context}" : ""}';
if (details.exception is NullThrownError) { 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) { } 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 { } else {
String errorName; String errorName;
if (details.exception is AssertionError) { if (details.exception is AssertionError) {
...@@ -252,7 +256,7 @@ class FlutterError extends AssertionError { ...@@ -252,7 +256,7 @@ class FlutterError extends AssertionError {
String message = details.exceptionAsString(); String message = details.exceptionAsString();
if (message.startsWith(prefix)) if (message.startsWith(prefix))
message = message.substring(prefix.length); 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; Iterable<String> stackLines = (details.stack != null) ? details.stack.toString().trimRight().split('\n') : null;
if ((details.exception is AssertionError) && (details.exception is! FlutterError)) { if ((details.exception is AssertionError) && (details.exception is! FlutterError)) {
...@@ -276,25 +280,25 @@ class FlutterError extends AssertionError { ...@@ -276,25 +280,25 @@ class FlutterError extends AssertionError {
if (ourFault) { if (ourFault) {
debugPrint('\nEither the assertion indicates an error in the framework itself, or we should ' 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 ' 'provide substantially more information in this error message to help you determine '
'and fix the underlying cause.', wrapWidth: _kWrapWidth); 'and fix the underlying cause.', wrapWidth: wrapWidth);
debugPrint('In either case, please report this assertion by filing a bug on GitHub:', wrapWidth: _kWrapWidth); debugPrint('In either case, please report this assertion by filing a bug on GitHub:', wrapWidth: wrapWidth);
debugPrint(' https://github.com/flutter/flutter/issues/new'); debugPrint(' https://github.com/flutter/flutter/issues/new');
} }
} }
if (details.stack != null) { 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) { if (details.stackFilter != null) {
stackLines = details.stackFilter(stackLines); stackLines = details.stackFilter(stackLines);
} else { } else {
stackLines = defaultStackFilter(stackLines); stackLines = defaultStackFilter(stackLines);
} }
for (String line in stackLines) for (String line in stackLines)
debugPrint(line, wrapWidth: _kWrapWidth); debugPrint(line, wrapWidth: wrapWidth);
} }
if (details.informationCollector != null) { if (details.informationCollector != null) {
final StringBuffer information = new StringBuffer(); final StringBuffer information = new StringBuffer();
details.informationCollector(information); details.informationCollector(information);
debugPrint('\n${information.toString().trimRight()}', wrapWidth: _kWrapWidth); debugPrint('\n${information.toString().trimRight()}', wrapWidth: wrapWidth);
} }
debugPrint(footer); debugPrint(footer);
} else { } else {
......
...@@ -411,11 +411,15 @@ class BoxConstraints extends Constraints { ...@@ -411,11 +411,15 @@ class BoxConstraints extends Constraints {
return a * (1.0 - t); return a * (1.0 - t);
assert(a.debugAssertIsValid()); assert(a.debugAssertIsValid());
assert(b.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( return new BoxConstraints(
minWidth: ui.lerpDouble(a.minWidth, b.minWidth, t), minWidth: a.minWidth.isFinite ? ui.lerpDouble(a.minWidth, b.minWidth, t) : double.INFINITY,
maxWidth: ui.lerpDouble(a.maxWidth, b.maxWidth, t), maxWidth: a.maxWidth.isFinite ? ui.lerpDouble(a.maxWidth, b.maxWidth, t) : double.INFINITY,
minHeight: ui.lerpDouble(a.minHeight, b.minHeight, t), minHeight: a.minHeight.isFinite ? ui.lerpDouble(a.minHeight, b.minHeight, t) : double.INFINITY,
maxHeight: ui.lerpDouble(a.maxHeight, b.maxHeight, t) maxHeight: a.maxHeight.isFinite ? ui.lerpDouble(a.maxHeight, b.maxHeight, t) : double.INFINITY,
); );
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'box.dart'; import 'box.dart';
import 'object.dart'; import 'object.dart';
...@@ -641,6 +643,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -641,6 +643,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
double crossSize = 0.0; double crossSize = 0.0;
double allocatedSize = 0.0; // Sum of the sizes of the the non-flexible children. double allocatedSize = 0.0; // Sum of the sizes of the the non-flexible children.
RenderBox child = firstChild; RenderBox child = firstChild;
RenderBox lastFlexChild;
while (child != null) { while (child != null) {
final FlexParentData childParentData = child.parentData; final FlexParentData childParentData = child.parentData;
totalChildren++; totalChildren++;
...@@ -707,6 +710,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -707,6 +710,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
); );
}()); }());
totalFlex += childParentData.flex; totalFlex += childParentData.flex;
lastFlexChild = child;
} else { } else {
BoxConstraints innerConstraints; BoxConstraints innerConstraints;
if (crossAxisAlignment == CrossAxisAlignment.stretch) { if (crossAxisAlignment == CrossAxisAlignment.stretch) {
...@@ -740,6 +744,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -740,6 +744,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
// Distribute free space to flexible children, and determine baseline. // Distribute free space to flexible children, and determine baseline.
final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize); final double freeSpace = math.max(0.0, (canFlex ? maxMainSize : 0.0) - allocatedSize);
double allocatedFlexSpace = 0.0;
double maxBaselineDistance = 0.0; double maxBaselineDistance = 0.0;
if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) { if (totalFlex > 0 || crossAxisAlignment == CrossAxisAlignment.baseline) {
final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.NAN; final double spacePerFlex = canFlex && totalFlex > 0 ? (freeSpace / totalFlex) : double.NAN;
...@@ -747,7 +752,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -747,7 +752,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
while (child != null) { while (child != null) {
final int flex = _getFlex(child); final int flex = _getFlex(child);
if (flex > 0) { 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; double minChildExtent;
switch (_getFit(child)) { switch (_getFit(child)) {
case FlexFit.tight: case FlexFit.tight:
...@@ -790,7 +795,10 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -790,7 +795,10 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
} }
} }
child.layout(innerConstraints, parentUsesSize: true); 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)); crossSize = math.max(crossSize, _getCrossSize(child));
} }
if (crossAxisAlignment == CrossAxisAlignment.baseline) { if (crossAxisAlignment == CrossAxisAlignment.baseline) {
...@@ -945,6 +953,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -945,6 +953,17 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
static Paint _debugMarkerPaint; static Paint _debugMarkerPaint;
TextPainter _debugMarkerLabel; TextPainter _debugMarkerLabel;
bool _debugReportOverflow = true;
@override
void reassemble() {
super.reassemble();
assert(() {
_debugReportOverflow = true;
return true;
}());
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (_overflow <= 0.0) { if (_overflow <= 0.0) {
...@@ -977,12 +996,31 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -977,12 +996,31 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
pixels = _overflow.toStringAsPrecision(3); pixels = _overflow.toStringAsPrecision(3);
} }
String label;
Rect markerRect; Rect markerRect;
String label;
Offset labelOffset;
double labelAngle;
switch (direction) { switch (direction) {
case Axis.horizontal: case Axis.horizontal:
markerRect = offset + new Offset(size.width * (1.0 - _kMarkerSize), 0.0) & if (textDirection != null) {
new Size(size.width * _kMarkerSize, size.height); 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'; label = 'ROW OVERFLOWED BY $pixels PIXELS';
break; break;
case Axis.vertical: case Axis.vertical:
...@@ -1001,13 +1039,11 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -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. _debugMarkerLabel.layout(); // This is a no-op if the label hasn't changed.
// TODO(ianh): RTL support
switch (direction) { switch (direction) {
case Axis.horizontal: case Axis.horizontal:
context.canvas.save(); context.canvas.save();
final Offset offset = markerRect.centerRight; context.canvas.translate(labelOffset.dx, labelOffset.dy);
context.canvas.translate(offset.dx, offset.dy); context.canvas.rotate(labelAngle);
context.canvas.rotate(-math.PI / 2.0);
_debugMarkerLabel.paint(context.canvas, new Offset(-_debugMarkerLabel.width / 2.0, 0.0)); _debugMarkerLabel.paint(context.canvas, new Offset(-_debugMarkerLabel.width / 2.0, 0.0));
context.canvas.restore(); context.canvas.restore();
break; break;
...@@ -1016,6 +1052,34 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl ...@@ -1016,6 +1052,34 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
break; 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; return true;
}()); }());
} }
......
...@@ -971,9 +971,9 @@ void main() { ...@@ -971,9 +971,9 @@ void main() {
leading: new Placeholder(key: key), leading: new Placeholder(key: key),
title: const Text('Abc'), title: const Text('Abc'),
actions: <Widget>[ actions: <Widget>[
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
], ],
), ),
), ),
...@@ -992,9 +992,9 @@ void main() { ...@@ -992,9 +992,9 @@ void main() {
leading: new Placeholder(key: key), leading: new Placeholder(key: key),
title: const Text('Abc'), title: const Text('Abc'),
actions: <Widget>[ actions: <Widget>[
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
], ],
flexibleSpace: new DecoratedBox( flexibleSpace: new DecoratedBox(
decoration: new BoxDecoration( decoration: new BoxDecoration(
...@@ -1022,9 +1022,9 @@ void main() { ...@@ -1022,9 +1022,9 @@ void main() {
leading: new Placeholder(key: key), leading: new Placeholder(key: key),
title: const Text('Abc'), title: const Text('Abc'),
actions: <Widget>[ actions: <Widget>[
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
], ],
flexibleSpace: new DecoratedBox( flexibleSpace: new DecoratedBox(
decoration: new BoxDecoration( decoration: new BoxDecoration(
...@@ -1063,9 +1063,9 @@ void main() { ...@@ -1063,9 +1063,9 @@ void main() {
leading: new Placeholder(key: key), leading: new Placeholder(key: key),
title: const Text('Abc'), title: const Text('Abc'),
actions: <Widget>[ actions: <Widget>[
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
const Placeholder(), const Placeholder(fallbackWidth: 10.0),
], ],
bottom: new PreferredSize( bottom: new PreferredSize(
preferredSize: const Size(0.0, kToolbarHeight), preferredSize: const Size(0.0, kToolbarHeight),
......
...@@ -151,6 +151,9 @@ void main() { ...@@ -151,6 +151,9 @@ void main() {
home: buildTable(source), 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 (0)'), findsOneWidget);
expect(find.text('Gingerbread (1)'), findsNothing); expect(find.text('Gingerbread (1)'), findsNothing);
expect(find.text('42'), findsNWidgets(10)); expect(find.text('42'), findsNWidgets(10));
......
...@@ -90,6 +90,74 @@ void main() { ...@@ -90,6 +90,74 @@ void main() {
expect(copy.maxHeight, 97.0); 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', () { test('BoxConstraints normalize', () {
final BoxConstraints constraints = const BoxConstraints( final BoxConstraints constraints = const BoxConstraints(
minWidth: 3.0, minWidth: 3.0,
......
...@@ -221,7 +221,7 @@ void main() { ...@@ -221,7 +221,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(0), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
]), ]),
...@@ -297,7 +297,7 @@ void main() { ...@@ -297,7 +297,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(0), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(1), child: const Placeholder()),
]), ]),
...@@ -306,7 +306,7 @@ void main() { ...@@ -306,7 +306,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(2), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
]), ]),
...@@ -315,7 +315,7 @@ void main() { ...@@ -315,7 +315,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(4), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
]), ]),
...@@ -349,7 +349,7 @@ void main() { ...@@ -349,7 +349,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(1), child: const Placeholder()),
]), ]),
), ),
...@@ -357,7 +357,7 @@ void main() { ...@@ -357,7 +357,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(2), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(3), child: const Placeholder()),
]), ]),
...@@ -366,7 +366,7 @@ void main() { ...@@ -366,7 +366,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(4), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(5), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(0), child: const Placeholder()),
...@@ -408,7 +408,7 @@ void main() { ...@@ -408,7 +408,7 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(1), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()), new Leaf(key: const GlobalObjectKey<_LeafState>(2), child: const Placeholder()),
]), ]),
...@@ -417,14 +417,14 @@ void main() { ...@@ -417,14 +417,14 @@ void main() {
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, height: 400.0,
child: new Row(children: <Widget>[ child: new Stack(children: <Widget>[
]), ]),
), ),
), ),
new AutomaticKeepAlive( new AutomaticKeepAlive(
child: new Container( child: new Container(
height: 400.0, 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>(3), child: const Placeholder()),
new Leaf(key: const GlobalObjectKey<_LeafState>(4), child: const Placeholder()), 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>(5), child: const Placeholder()),
......
...@@ -384,6 +384,8 @@ void main() { ...@@ -384,6 +384,8 @@ void main() {
) )
)); ));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey)); final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(0.0)); expect(renderBox.size.width, equals(0.0));
expect(renderBox.size.height, equals(100.0)); expect(renderBox.size.height, equals(100.0));
...@@ -777,6 +779,8 @@ void main() { ...@@ -777,6 +779,8 @@ void main() {
) )
)); ));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey)); final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(0.0)); expect(renderBox.size.width, equals(0.0));
expect(renderBox.size.height, equals(100.0)); expect(renderBox.size.height, equals(100.0));
......
...@@ -72,4 +72,42 @@ void main() { ...@@ -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() { ...@@ -12,19 +12,19 @@ void main() {
testWidgets('GlobalKey children of one node', (WidgetTester tester) async { testWidgets('GlobalKey children of one node', (WidgetTester tester) async {
// This is actually a test of the regular duplicate key logic, which // This is actually a test of the regular duplicate key logic, which
// happens before the duplicate GlobalKey logic. // 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)),
new Container(key: const GlobalObjectKey(0)), new Container(key: const GlobalObjectKey(0)),
])); ]));
final dynamic error = tester.takeException(); final dynamic error = tester.takeException();
expect(error, isFlutterError); expect(error, isFlutterError);
expect(error.toString(), startsWith('Duplicate keys found.\n')); 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)}]')); expect(error.toString(), contains('[GlobalObjectKey ${describeIdentity(0)}]'));
}); });
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async { testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row( await tester.pumpWidget(new Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))), new Container(child: new Container(key: const GlobalObjectKey(0))),
...@@ -41,7 +41,7 @@ void main() { ...@@ -41,7 +41,7 @@ void main() {
}); });
testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async { testWidgets('GlobalKey children of two different nodes', (WidgetTester tester) async {
await tester.pumpWidget(new Row( await tester.pumpWidget(new Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))), new Container(child: new Container(key: const GlobalObjectKey(0))),
...@@ -61,7 +61,7 @@ void main() { ...@@ -61,7 +61,7 @@ void main() {
testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async { testWidgets('GlobalKey children of two nodes', (WidgetTester tester) async {
StateSetter nestedSetState; StateSetter nestedSetState;
bool flag = false; bool flag = false;
await tester.pumpWidget(new Row( await tester.pumpWidget(new Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: <Widget>[ children: <Widget>[
new Container(child: new Container(key: const GlobalObjectKey(0))), new Container(child: new Container(key: const GlobalObjectKey(0))),
......
...@@ -302,6 +302,8 @@ void main() { ...@@ -302,6 +302,8 @@ void main() {
), ),
)); ));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey)); final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0)); expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0)); expect(renderBox.size.height, equals(0.0));
...@@ -725,6 +727,8 @@ void main() { ...@@ -725,6 +727,8 @@ void main() {
), ),
)); ));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey)); final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0)); expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0)); expect(renderBox.size.height, equals(0.0));
...@@ -1148,6 +1152,8 @@ void main() { ...@@ -1148,6 +1152,8 @@ void main() {
), ),
)); ));
expect(tester.takeException(), contains('overflowed'));
final RenderBox renderBox = tester.renderObject(find.byKey(childKey)); final RenderBox renderBox = tester.renderObject(find.byKey(childKey));
expect(renderBox.size.width, equals(100.0)); expect(renderBox.size.width, equals(100.0));
expect(renderBox.size.height, equals(0.0)); expect(renderBox.size.height, equals(0.0));
......
...@@ -39,6 +39,8 @@ void main() { ...@@ -39,6 +39,8 @@ void main() {
), ),
)); ));
expect(tester.takeException(), contains('overflowed'));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
...@@ -109,6 +111,8 @@ void main() { ...@@ -109,6 +111,8 @@ void main() {
), ),
)); ));
expect(tester.takeException(), contains('overflowed'));
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
new TestSemantics.root( new TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
......
...@@ -319,6 +319,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -319,6 +319,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
Zone _parentZone; Zone _parentZone;
Completer<Null> _currentTestCompleter; Completer<Null> _currentTestCompleter;
String _currentTestDescription; // set from _runTest to _testCompletionHandler
void _testCompletionHandler() { void _testCompletionHandler() {
// This can get called twice, in the case of a Future without listeners failing, and then // 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 ...@@ -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 // 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. // 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... // 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; _pendingExceptionDetails = null;
} }
_currentTestDescription = null;
if (!_currentTestCompleter.isCompleted) if (!_currentTestCompleter.isCompleted)
_currentTestCompleter.complete(null); _currentTestCompleter.complete(null);
} }
Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester, String description) { Future<Null> _runTest(Future<Null> testBody(), VoidCallback invariantTester, String description) {
assert(description != null); assert(description != null);
assert(_currentTestDescription == null);
_currentTestDescription = description; // cleared by _testCompletionHandler
assert(inTest); assert(inTest);
_oldExceptionHandler = FlutterError.onError; _oldExceptionHandler = FlutterError.onError;
int _exceptionCount = 0; // number of un-taken exceptions 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