Unverified Commit 3541ad0a authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add an UnconstrainedBox and factor out debug overflow indicator. (#12856)

UnconstrainedBox will allow its child to size itself as if it had no constraints, and then attempt to fit around that object, until its own constraints are exceeded, in which case it will clip and display an overflow warning.

I also factored out DebugOverflowIndicator, which will draw overflow indicators on containers which overflow but aren't expected to.
parent fb86a033
......@@ -36,6 +36,7 @@ export 'src/rendering/binding.dart';
export 'src/rendering/box.dart';
export 'src/rendering/custom_layout.dart';
export 'src/rendering/debug.dart';
export 'src/rendering/debug_overflow_indicator.dart';
export 'src/rendering/editable.dart';
export 'src/rendering/error.dart';
export 'src/rendering/flex.dart';
......
......@@ -23,6 +23,7 @@ typedef void ValueChanged<T>(T value);
/// value, regardless of whether the given value is new or not.
///
/// See also:
///
/// * [ValueGetter], the getter equivalent of this signature.
/// * [AsyncValueSetter], an asynchronous version of this signature.
typedef void ValueSetter<T>(T value);
......@@ -30,6 +31,7 @@ typedef void ValueSetter<T>(T value);
/// Signature for callbacks that are to report a value on demand.
///
/// See also:
///
/// * [ValueSetter], the setter equivalent of this signature.
/// * [AsyncValueGetter], an asynchronous version of this signature.
typedef T ValueGetter<T>();
......@@ -41,6 +43,7 @@ typedef Iterable<T> IterableFilter<T>(Iterable<T> input);
/// return a [Future] to indicate when their work is complete.
///
/// See also:
///
/// * [VoidCallback], a synchronous version of this signature.
/// * [AsyncValueGetter], a signature for asynchronous getters.
/// * [AsyncValueSetter], a signature for asynchronous setters.
......@@ -50,6 +53,7 @@ typedef Future<Null> AsyncCallback();
/// [Future] that completes when the value has been saved.
///
/// See also:
///
/// * [ValueSetter], a synchronous version of this signature.
/// * [AsyncValueGetter], the getter equivalent of this signature.
typedef Future<Null> AsyncValueSetter<T>(T value);
......@@ -57,6 +61,7 @@ typedef Future<Null> AsyncValueSetter<T>(T value);
/// Signature for callbacks that are to asynchronously report a value on demand.
///
/// See also:
///
/// * [ValueGetter], a synchronous version of this signature.
/// * [AsyncValueSetter], the setter equivalent of this signature.
typedef Future<T> AsyncValueGetter<T>();
......
......@@ -232,6 +232,7 @@ class TextField extends StatefulWidget {
/// characters.
///
/// See also:
///
/// * [LengthLimitingTextInputFormatter] for more information on how it
/// counts characters, and how it may differ from the intuitive meaning.
final int maxLength;
......
......@@ -3,11 +3,11 @@
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'box.dart';
import 'debug_overflow_indicator.dart';
import 'object.dart';
/// How the child is inscribed into the available space.
......@@ -262,7 +262,8 @@ typedef double _ChildSizingFunction(RenderBox child, double extent);
/// * [Flex], the widget equivalent.
/// * [Row] and [Column], direction-specific variants of [Flex].
class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData> {
RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>,
DebugOverflowIndicatorMixin {
/// Creates a flex render object.
///
/// By default, the flex layout is horizontal and children are aligned to the
......@@ -467,7 +468,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
return true;
}
/// Set during layout if overflow occurred on the main axis.
// Set during layout if overflow occurred on the main axis.
double _overflow;
@override
......@@ -924,28 +925,6 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
return defaultHitTestChildren(result, position: position);
}
static const Color _black = const Color(0xBF000000);
static const Color _yellow = const Color(0xBFFFFF00);
static const double _kMarkerSize = 0.1;
static const TextStyle _debugMarkerTextStyle = const TextStyle(
color: const Color(0xFF900000),
fontSize: 7.5,
fontWeight: FontWeight.w800,
);
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) {
......@@ -961,111 +940,35 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint);
assert(() {
// In debug mode, if you have overflow, we highlight where the overflow
// would be by painting the edge of that area with a yellow and black
// striped bar.
_debugMarkerPaint ??= new Paint()
..shader = new ui.Gradient.linear(
const Offset(0.0, 0.0),
const Offset(10.0, 10.0),
<Color>[_black, _yellow, _yellow, _black],
<double>[0.25, 0.25, 0.75, 0.75],
TileMode.repeated,
);
String pixels;
if (_overflow > 10.0) {
pixels = _overflow.toStringAsFixed(0);
} else if (_overflow > 1.0) {
pixels = _overflow.toStringAsFixed(1);
} else {
pixels = _overflow.toStringAsPrecision(3);
}
Rect markerRect;
String label;
Offset labelOffset;
double labelAngle;
switch (direction) {
case Axis.horizontal:
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:
markerRect = offset + new Offset(0.0, size.height * (1.0 - _kMarkerSize)) &
new Size(size.width, size.height * _kMarkerSize);
label = 'COLUMN OVERFLOWED BY $pixels PIXELS';
break;
}
context.canvas.drawRect(markerRect, _debugMarkerPaint);
_debugMarkerLabel ??= new TextPainter()
..textDirection = TextDirection.ltr; // This label is in English.
_debugMarkerLabel.text = new TextSpan( // This is a no-op if the label hasn't changed.
text: label,
style: _debugMarkerTextStyle,
);
_debugMarkerLabel.layout(); // This is a no-op if the label hasn't changed.
switch (direction) {
// Only set this if it's null to save work. It gets reset to null if the
// _direction changes.
final String debugOverflowHints =
'The overflowing $runtimeType has an orientation of $_direction.\n'
'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.\n'
'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 ClipRect widget '
'before putting it in the flex, or using a scrollable container rather '
'than a Flex, like a ListView.';
// Simulate a child rect that overflows by the right amount. This child
// rect is never used for drawing, just for determining the overflow
// location and amount.
Rect overflowChildRect;
switch (_direction) {
case Axis.horizontal:
context.canvas.save();
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();
overflowChildRect = new Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0);
break;
case Axis.vertical:
_debugMarkerLabel.paint(context.canvas, markerRect.bottomCenter - new Offset(_debugMarkerLabel.width / 2.0, 0.0));
overflowChildRect = new Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow);
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 ClipRect 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));
}
));
}
paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints);
return true;
}());
}
......@@ -1092,5 +995,4 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
description.add(new EnumProperty<VerticalDirection>('verticalDirection', verticalDirection, defaultValue: null));
description.add(new EnumProperty<TextBaseline>('textBaseline', textBaseline, defaultValue: null));
}
}
......@@ -2970,6 +2970,7 @@ abstract class _SemanticsFragment {
/// previously painted [RenderObject]s unreachable for accessibility purposes.
///
/// See also:
///
/// * [SemanticsConfiguration.isBlockingSemanticsOfPreviouslyPaintedNodes]
/// describes what semantics are dropped in more detail.
final bool dropsSemanticsOfPreviousSiblings;
......
......@@ -27,7 +27,7 @@ export 'package:flutter/gestures.dart' show
///
/// A proxy box has a single child and simply mimics all the properties of that
/// child by calling through to the child for each function in the render box
/// protocol. For example, a proxy box determines its size by askings its child
/// protocol. For example, a proxy box determines its size by asking its child
/// to layout with the same constraints and then matching the size.
///
/// A proxy box isn't useful on its own because you might as well just replace
......
......@@ -8,7 +8,9 @@ import 'package:flutter/foundation.dart';
import 'box.dart';
import 'debug.dart';
import 'debug_overflow_indicator.dart';
import 'object.dart';
import 'stack.dart' show RelativeRect;
/// Abstract class for one-child-layout render boxes that provide control over
/// the child's position.
......@@ -236,6 +238,12 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox {
_textDirection = textDirection,
super(child);
/// A constructor to be used only when the extending class also has a mixin.
// TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/15101 is fixed.
@protected
RenderAligningShiftedBox.mixin(AlignmentGeometry alignment,TextDirection textDirection, RenderBox child)
: this(alignment: alignment, textDirection: textDirection, child: child);
Alignment _resolvedAlignment;
void _resolve() {
......@@ -467,6 +475,16 @@ class RenderPositionedBox extends RenderAligningShiftedBox {
/// The child is positioned according to [alignment]. To position a smaller
/// child inside a larger parent, use [RenderPositionedBox] and
/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
///
/// See also:
///
/// * [RenderUnconstrainedBox] for a render object that allows its children
/// to render themselves unconstrained, expands to fit them, and considers
/// overflow to be an error.
/// * [RenderSizedOverflowBox], a render object that is a specific size but
/// passes its original constraints through to its child, which it allows to
/// overflow.
class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
/// Creates a render object that lets its child overflow itself.
RenderConstrainedOverflowBox({
......@@ -562,8 +580,110 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
}
}
/// A render box that is a specific size but passes its original constraints
/// through to its child, which will probably overflow.
/// Renders a box, imposing no constraints on its child, allowing the child to
/// render at its "natural" size.
///
/// This allows a child to render at the size it would render if it were alone
/// on an infinite canvas with no constraints. This box will then expand
/// as much as it can within its own constraints and align the child based on
/// [alignment]. If the box cannot expand enough to accommodate the entire
/// child, the child will be clipped.
///
/// In debug mode, if the child overflows the box, a warning will be printed on
/// the console, and black and yellow striped areas will appear where theR
/// overflow occurs.
///
/// See also:
///
/// * [RenderConstrainedBox] renders a box which imposes constraints on its
/// child.
/// * [RenderConstrainedOverflowBox], renders a box that imposes different
/// constraints on its child than it gets from its parent, possibly allowing
/// the child to overflow the parent.
/// * [RenderSizedOverflowBox], a render object that is a specific size but
/// passes its original constraints through to its child, which it allows to
/// overflow.
class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
RenderUnconstrainedBox({
@required AlignmentGeometry alignment,
@required TextDirection textDirection,
RenderBox child,
}) : assert(alignment != null),
super.mixin(alignment, textDirection, child);
Rect _overflowContainerRect = Rect.zero;
Rect _overflowChildRect = Rect.zero;
bool _isOverflowing = false;
@override
void performLayout() {
if (child != null) {
// Let the child lay itself out at it's "natural" size.
child.layout(const BoxConstraints(), parentUsesSize: true);
size = constraints.constrain(child.size);
alignChild();
final BoxParentData childParentData = child.parentData;
_overflowContainerRect = Offset.zero & size;
_overflowChildRect = childParentData.offset & child.size;
} else {
size = constraints.constrain(Size.zero);
_overflowContainerRect = Rect.zero;
_overflowChildRect = Rect.zero;
}
final RelativeRect overflow = new RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect);
_isOverflowing = overflow.left > 0.0 ||
overflow.right > 0.0 ||
overflow.top > 0.0 ||
overflow.bottom > 0.0;
}
@override
void paint(PaintingContext context, Offset offset) {
// There's no point in drawing the child if we're empty, or there is no
// child.
if (child == null || size.isEmpty)
return;
if (!_isOverflowing) {
super.paint(context, offset);
return;
}
// We have overflow. Clip it.
context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint);
// Display the overflow indicator.
assert(() {
paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
return true;
}());
}
@override
Rect describeApproximatePaintClip(RenderObject child) {
return _isOverflowing ? Offset.zero & size : null;
}
@override
String toStringShort() {
String header = super.toStringShort();
if (_isOverflowing)
header += ' OVERFLOWING';
return header;
}
}
/// A render object that is a specific size but passes its original constraints
/// through to its child, which it allows to overflow.
///
/// See also:
/// * [RenderUnconstrainedBox] for a render object that allows its children
/// to render themselves unconstrained, expands to fit them, and considers
/// overflow to be an error.
/// * [RenderConstrainedOverflowBox] for a render object that imposes
/// different constraints on its child than it gets from its parent,
/// possibly allowing the child to overflow the parent.
class RenderSizedOverflowBox extends RenderAligningShiftedBox {
/// Creates a render box of a given size that lets its child overflow.
///
......
......@@ -17,12 +17,14 @@ import 'object.dart';
/// width or height of the rectangle, convert it to a [Rect] using [toRect()]
/// (passing the container's own Rect), and then examine that object.
///
/// If you create the RelativeRect with null values, the methods on
/// RelativeRect will not work usefully (or at all).
/// The fields [left], [right], [bottom], and [top] must not be null.
@immutable
class RelativeRect {
/// Creates a RelativeRect with the given values.
const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom);
///
/// The arguments must not be null.
const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom)
: assert(left != null && top != null && right != null && bottom != null);
/// Creates a RelativeRect from a Rect and a Size. The Rect (first argument)
/// and the RelativeRect (the output) are in the coordinate space of the
......@@ -56,15 +58,23 @@ class RelativeRect {
static final RelativeRect fill = const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0);
/// Distance from the left side of the container to the left side of this rectangle.
///
/// May be negative if the left side of the rectangle is outside of the container.
final double left;
/// Distance from the top side of the container to the top side of this rectangle.
///
/// May be negative if the top side of the rectangle is outside of the container.
final double top;
/// Distance from the right side of the container to the right side of this rectangle.
///
/// May be negative if the right side of the rectangle is outside of the container.
final double right;
/// Distance from the bottom side of the container to the bottom side of this rectangle.
///
/// May be negative if the bottom side of the rectangle is outside of the container.
final double bottom;
/// Returns a new rectangle object translated by the given offset.
......
......@@ -1157,7 +1157,7 @@ class SemanticsConfiguration {
/// The reading direction is given by [textDirection].
///
/// See also:
///
///
/// * [decreasedValue], describes what [value] will be after performing
/// [SemanticsAction.decrease]
/// * [increasedValue], describes what [value] will be after performing
......
This diff is collapsed.
......@@ -224,6 +224,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
/// * [AnimatedContainer], a variant that smoothly animates the properties when
/// they change.
/// * [Border], which has a sample which uses [Container] heavily.
/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/).
class Container extends StatelessWidget {
/// Creates a widget that combines common painting, positioning, and sizing widgets.
///
......
......@@ -41,6 +41,7 @@ class BoxConstraintsTween extends Tween<BoxConstraints> {
/// interpolation between decorations.
///
/// See also:
///
/// * [Tween] for a discussion on how to use interpolation objects.
/// * [ShapeDecoration], [RoundedRectangleBorder], [CircleBorder], and
/// [StadiumBorder] for examples of shape borders that can be smoothly
......@@ -346,6 +347,7 @@ abstract class AnimatedWidgetBaseState<T extends ImplicitlyAnimatedWidget> exten
///
/// * [AnimatedPadding], which is a subset of this widget that only
/// supports animating the [padding].
/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/).
class AnimatedContainer extends ImplicitlyAnimatedWidget {
/// Creates a container that animates its parameters implicitly.
///
......
......@@ -152,7 +152,7 @@ void main() {
));
// the column overflows because we're forcing it to 600 pixels high
expect(tester.takeException(), contains('A vertical RenderFlex overflowed by'));
expect(tester.takeException(), contains('A RenderFlex overflowed by'));
expect(find.text('Gingerbread (0)'), findsOneWidget);
expect(find.text('Gingerbread (1)'), findsNothing);
......
......@@ -128,4 +128,78 @@ void main() {
layout(constraintedBox);
expect(coloredBox.parentData?.runtimeType, ParentData);
});
test('UnconstrainedBox expands to fit children', () {
final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox(
textDirection: TextDirection.ltr,
child: new RenderConstrainedBox(
additionalConstraints: const BoxConstraints.tightFor(width: 200.0, height: 200.0),
),
alignment: Alignment.center,
);
layout(
unconstrained,
constraints: const BoxConstraints(
minWidth: 200.0,
maxWidth: 200.0,
minHeight: 200.0,
maxHeight: 200.0,
),
);
expect(unconstrained.size.width, equals(200.0), reason: 'unconstrained width');
expect(unconstrained.size.height, equals(200.0), reason: 'unconstrained height');
});
test('UnconstrainedBox handles vertical overflow', () {
final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox(
textDirection: TextDirection.ltr,
child: new RenderConstrainedBox(
additionalConstraints: const BoxConstraints.tightFor(height: 200.0),
),
alignment: Alignment.center,
);
final BoxConstraints viewport = const BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
layout(unconstrained, constraints: viewport);
expect(unconstrained.getMinIntrinsicHeight(100.0), equals(200.0));
expect(unconstrained.getMaxIntrinsicHeight(100.0), equals(200.0));
expect(unconstrained.getMinIntrinsicWidth(100.0), equals(0.0));
expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(0.0));
});
test('UnconstrainedBox handles horizontal overflow', () {
final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox(
textDirection: TextDirection.ltr,
child: new RenderConstrainedBox(
additionalConstraints: const BoxConstraints.tightFor(width: 200.0),
),
alignment: Alignment.center,
);
final BoxConstraints viewport = const BoxConstraints(maxHeight: 100.0, maxWidth: 100.0);
layout(unconstrained, constraints: viewport);
expect(unconstrained.getMinIntrinsicHeight(100.0), equals(0.0));
expect(unconstrained.getMaxIntrinsicHeight(100.0), equals(0.0));
expect(unconstrained.getMinIntrinsicWidth(100.0), equals(200.0));
expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(200.0));
});
test('UnconstrainedBox.toStringDeep returns useful information', () {
final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox(
textDirection: TextDirection.ltr,
alignment: Alignment.center,
);
expect(unconstrained.alignment, Alignment.center);
expect(unconstrained.textDirection, TextDirection.ltr);
expect(unconstrained, hasAGoodToStringDeep);
expect(
unconstrained.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes(
'RenderUnconstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n'
' parentData: MISSING\n'
' constraints: MISSING\n'
' size: MISSING\n'
' alignment: Alignment.center\n'
' textDirection: ltr\n'),
);
});
}
// Copyright 2017 The Chromium 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/widgets.dart';
import '../rendering/mock_canvas.dart';
void main() {
testWidgets('overflow indicator is not shown when not overflowing', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
child: const UnconstrainedBox(
child: const SizedBox(width: 200.0, height: 200.0),
),
),
);
expect(find.byType(UnconstrainedBox), isNot(paints..rect()));
});
testWidgets('overflow indicator is shown when overflowing', (WidgetTester tester) async {
final UnconstrainedBox box = const UnconstrainedBox(
child: const SizedBox(width: 200.0, height: 200.0),
);
await tester.pumpWidget(
new Center(
child: new SizedBox(
height: 100.0,
child: box,
),
),
);
expect(tester.takeException(), contains('A RenderUnconstrainedBox overflowed by'));
expect(find.byType(UnconstrainedBox), paints..rect());
await tester.pumpWidget(
new Center(
child: new SizedBox(
height: 100.0,
child: box,
),
),
);
// Doesn't throw the exception a second time, because we didn't reset
// overflowReportNeeded.
expect(tester.takeException(), isNull);
expect(find.byType(UnconstrainedBox), paints..rect());
});
testWidgets('overflow indicator is not shown when constraint size is zero.', (WidgetTester tester) async {
await tester.pumpWidget(
const Center(
child: const SizedBox(
height: 0.0,
child: const UnconstrainedBox(
child: const SizedBox(width: 200.0, height: 200.0),
),
),
),
);
expect(find.byType(UnconstrainedBox), isNot(paints..rect()));
});
}
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