Unverified Commit 8fd20b5d authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Text Painting Fuzzer (#12813)

Various improvements (in particular a new painting fuzzer) to the text manual test.

Some additional documentation.

A fix to Stack to remove an LTR bias: make unpositioned children apply "alignment".

Some more debugging information on RichText and Text.

A fix to the flutter tool to not crash when an RPC call throws an exception.
parent f4b0ccd9
This diff is collapsed.
...@@ -187,6 +187,20 @@ class DecorationImagePainter { ...@@ -187,6 +187,20 @@ class DecorationImagePainter {
ImageStream _imageStream; ImageStream _imageStream;
ImageInfo _image; ImageInfo _image;
/// Draw the image onto the given canvas.
///
/// The image is drawn at the position and size given by the `rect` argument.
///
/// The image is clipped to the given `clipPath`, if any.
///
/// The `configuration` object is used to resolve the image (e.g. to pick
/// resolution-specific assets), and to implement the
/// [DecorationImage.matchTextDirection] feature.
///
/// If the image needs to be painted again, e.g. because it is animated or
/// because it had not yet been loaded the first time this method was called,
/// then the `onChanged` callback passed to [DecorationImage.createPainter]
/// will be called.
void paint(Canvas canvas, Rect rect, Path clipPath, ImageConfiguration configuration) { void paint(Canvas canvas, Rect rect, Path clipPath, ImageConfiguration configuration) {
if (_details == null) if (_details == null)
return; return;
......
...@@ -43,6 +43,10 @@ class RenderListBody extends RenderBox ...@@ -43,6 +43,10 @@ class RenderListBody extends RenderBox
child.parentData = new ListBodyParentData(); child.parentData = new ListBodyParentData();
} }
/// The direction in which the children are laid out.
///
/// For example, if the [axisDirection] is [AxisDirection.down], each child
/// will be laid out below the next, vertically.
AxisDirection get axisDirection => _axisDirection; AxisDirection get axisDirection => _axisDirection;
AxisDirection _axisDirection; AxisDirection _axisDirection;
set axisDirection(AxisDirection value) { set axisDirection(AxisDirection value) {
...@@ -53,6 +57,8 @@ class RenderListBody extends RenderBox ...@@ -53,6 +57,8 @@ class RenderListBody extends RenderBox
markNeedsLayout(); markNeedsLayout();
} }
/// The axis (horizontal or vertical) corresponding to the current
/// [axisDirection].
Axis get mainAxis => axisDirectionToAxis(axisDirection); Axis get mainAxis => axisDirectionToAxis(axisDirection);
@override @override
......
...@@ -2114,6 +2114,17 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2114,6 +2114,17 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
owner.requestVisualUpdate(); owner.requestVisualUpdate();
} }
/// Report the semantics of this node, for example for accessibility purposes.
///
/// This method should be overridden by subclasses that have interesting
/// semantic information.
///
/// The given [SemanticsConfiguration] object is mutable and should be
/// annotated in a manner that describes the current state. No reference
/// should be kept to that object; mutating it outside of the context of the
/// [describeSemanticsConfiguration] call (for example as a result of
/// asynchronous computation) will at best have no useful effect and at worse
/// will cause crashes as the data will be in an inconsistent state.
@protected @protected
void describeSemanticsConfiguration(SemanticsConfiguration config) { void describeSemanticsConfiguration(SemanticsConfiguration config) {
// Nothing to do by default. // Nothing to do by default.
......
...@@ -403,6 +403,20 @@ class RenderParagraph extends RenderBox { ...@@ -403,6 +403,20 @@ class RenderParagraph extends RenderBox {
return _textPainter.getWordBoundary(position); return _textPainter.getWordBoundary(position);
} }
/// Returns the size of the text as laid out.
///
/// This can differ from [size] if the text overflowed or if the [constraints]
/// provided by the parent [RenderObject] forced the layout to be bigger than
/// necessary for the given [text].
///
/// This returns the [TextPainter.size] of the underlying [TextPainter].
///
/// Valid only after [layout].
Size get textSize {
assert(!debugNeedsLayout);
return _textPainter.size;
}
@override @override
void describeSemanticsConfiguration(SemanticsConfiguration config) { void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config); super.describeSemanticsConfiguration(config);
......
...@@ -596,6 +596,15 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -596,6 +596,15 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration(); static final SemanticsConfiguration _kEmptyConfig = new SemanticsConfiguration();
/// Reconfigures the properties of this object to describe the configuration
/// provided in the `config` argument and the children listen in the
/// `childrenInInversePaintOrder` argument.
///
/// The arguments may be null; this represents an empty configuration (all
/// values at their defaults, no children).
///
/// No reference is kept to the [SemanticsConfiguration] object, but the child
/// list is used as-is and should therefore not be changed after this call.
void updateWith({ void updateWith({
@required SemanticsConfiguration config, @required SemanticsConfiguration config,
@required List<SemanticsNode> childrenInInversePaintOrder, @required List<SemanticsNode> childrenInInversePaintOrder,
......
...@@ -332,14 +332,20 @@ class RenderStack extends RenderBox ...@@ -332,14 +332,20 @@ class RenderStack extends RenderBox
markNeedsLayout(); markNeedsLayout();
} }
/// How to align the non-positioned children in the stack. /// How to align the non-positioned or partially-positioned children in the
/// stack.
/// ///
/// The non-positioned children are placed relative to each other such that /// The non-positioned children are placed relative to each other such that
/// the points determined by [alignment] are co-located. For example, if the /// the points determined by [alignment] are co-located. For example, if the
/// [alignment] is [Alignment.topLeft], then the top left corner of /// [alignment] is [Alignment.topLeft], then the top left corner of
/// each non-positioned child will be located at the same global coordinate. /// each non-positioned child will be located at the same global coordinate.
/// ///
/// If this is set to a [AlignmentDirectional] object, then [textDirection] /// Partially-positioned children, those that do not specify an alignment in a
/// particular axis (e.g. that have neither `top` nor `bottom` set), use the
/// alignment to determine how they should be positioned in that
/// under-specified axis.
///
/// If this is set to an [AlignmentDirectional] object, then [textDirection]
/// must not be null. /// must not be null.
AlignmentGeometry get alignment => _alignment; AlignmentGeometry get alignment => _alignment;
AlignmentGeometry _alignment; AlignmentGeometry _alignment;
...@@ -504,20 +510,26 @@ class RenderStack extends RenderBox ...@@ -504,20 +510,26 @@ class RenderStack extends RenderBox
child.layout(childConstraints, parentUsesSize: true); child.layout(childConstraints, parentUsesSize: true);
double x = 0.0; double x;
if (childParentData.left != null) if (childParentData.left != null) {
x = childParentData.left; x = childParentData.left;
else if (childParentData.right != null) } else if (childParentData.right != null) {
x = size.width - childParentData.right - child.size.width; x = size.width - childParentData.right - child.size.width;
} else {
x = _resolvedAlignment.alongOffset(size - child.size).dx;
}
if (x < 0.0 || x + child.size.width > size.width) if (x < 0.0 || x + child.size.width > size.width)
_hasVisualOverflow = true; _hasVisualOverflow = true;
double y = 0.0; double y;
if (childParentData.top != null) if (childParentData.top != null) {
y = childParentData.top; y = childParentData.top;
else if (childParentData.bottom != null) } else if (childParentData.bottom != null) {
y = size.height - childParentData.bottom - child.size.height; y = size.height - childParentData.bottom - child.size.height;
} else {
y = _resolvedAlignment.alongOffset(size - child.size).dy;
}
if (y < 0.0 || y + child.size.height > size.height) if (y < 0.0 || y + child.size.height > size.height)
_hasVisualOverflow = true; _hasVisualOverflow = true;
......
...@@ -2239,9 +2239,10 @@ class ListBody extends MultiChildRenderObjectWidget { ...@@ -2239,9 +2239,10 @@ class ListBody extends MultiChildRenderObjectWidget {
/// Positioned children are those wrapped in a [Positioned] widget that has at /// Positioned children are those wrapped in a [Positioned] widget that has at
/// least one non-null property. The stack sizes itself to contain all the /// least one non-null property. The stack sizes itself to contain all the
/// non-positioned children, which are positioned according to [alignment] /// non-positioned children, which are positioned according to [alignment]
/// (which defaults to the top-left corner). The positioned children are then /// (which defaults to the top-left corner in left-to-right environments and the
/// placed relative to the stack according to their top, right, bottom, and left /// top-right corner in right-to-left environments). The positioned children are
/// properties. /// then placed relative to the stack according to their top, right, bottom, and
/// left properties.
/// ///
/// The stack paints its children in order with the first child being at the /// The stack paints its children in order with the first child being at the
/// bottom. If you want to change the order in which the children paint, you /// bottom. If you want to change the order in which the children paint, you
...@@ -2282,12 +2283,18 @@ class Stack extends MultiChildRenderObjectWidget { ...@@ -2282,12 +2283,18 @@ class Stack extends MultiChildRenderObjectWidget {
List<Widget> children: const <Widget>[], List<Widget> children: const <Widget>[],
}) : super(key: key, children: children); }) : super(key: key, children: children);
/// How to align the non-positioned children in the stack. /// How to align the non-positioned and partially-positioned children in the
/// stack.
/// ///
/// The non-positioned children are placed relative to each other such that /// The non-positioned children are placed relative to each other such that
/// the points determined by [alignment] are co-located. For example, if the /// the points determined by [alignment] are co-located. For example, if the
/// [alignment] is [Alignment.topLeft], then the top left corner of /// [alignment] is [Alignment.topLeft], then the top left corner of
/// each non-positioned child will be located at the same global coordinate. /// each non-positioned child will be located at the same global coordinate.
///
/// Partially-positioned children, those that do not specify an alignment in a
/// particular axis (e.g. that have neither `top` nor `bottom` set), use the
/// alignment to determine how they should be positioned in that
/// under-specified axis.
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
/// The text direction with which to resolve [alignment]. /// The text direction with which to resolve [alignment].
...@@ -2398,6 +2405,12 @@ class IndexedStack extends Stack { ...@@ -2398,6 +2405,12 @@ class IndexedStack extends Stack {
/// [height] properties can be used to give the dimensions, with one /// [height] properties can be used to give the dimensions, with one
/// corresponding position property (e.g. [top] and [height]). /// corresponding position property (e.g. [top] and [height]).
/// ///
/// If all three values on a particular axis are null, then the
/// [Stack.alignment] property is used to position the child.
///
/// If all six values are null, the child is a non-positioned child. The [Stack]
/// uses only the non-positioned children to size itself.
///
/// See also: /// See also:
/// ///
/// * [PositionedDirectional], which adapts to the ambient [Directionality]. /// * [PositionedDirectional], which adapts to the ambient [Directionality].
...@@ -2413,6 +2426,8 @@ class Positioned extends ParentDataWidget<Stack> { ...@@ -2413,6 +2426,8 @@ class Positioned extends ParentDataWidget<Stack> {
/// ///
/// * [Positioned.directional], which specifies the widget's horizontal /// * [Positioned.directional], which specifies the widget's horizontal
/// position using `start` and `end` rather than `left` and `right`. /// position using `start` and `end` rather than `left` and `right`.
/// * [PositionedDirectional], which is similar to [Positioned.directional]
/// but adapts to the ambient [Directionality].
const Positioned({ const Positioned({
Key key, Key key,
this.left, this.left,
...@@ -2530,36 +2545,54 @@ class Positioned extends ParentDataWidget<Stack> { ...@@ -2530,36 +2545,54 @@ class Positioned extends ParentDataWidget<Stack> {
/// ///
/// Only two out of the three horizontal values ([left], [right], [width]) can be /// Only two out of the three horizontal values ([left], [right], [width]) can be
/// set. The third must be null. /// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// horizontally.
final double left; final double left;
/// The distance that the child's top edge is inset from the top of the stack. /// The distance that the child's top edge is inset from the top of the stack.
/// ///
/// Only two out of the three vertical values ([top], [bottom], [height]) can be /// Only two out of the three vertical values ([top], [bottom], [height]) can be
/// set. The third must be null. /// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// vertically.
final double top; final double top;
/// The distance that the child's right edge is inset from the right of the stack. /// The distance that the child's right edge is inset from the right of the stack.
/// ///
/// Only two out of the three horizontal values ([left], [right], [width]) can be /// Only two out of the three horizontal values ([left], [right], [width]) can be
/// set. The third must be null. /// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// horizontally.
final double right; final double right;
/// The distance that the child's bottom edge is inset from the bottom of the stack. /// The distance that the child's bottom edge is inset from the bottom of the stack.
/// ///
/// Only two out of the three vertical values ([top], [bottom], [height]) can be /// Only two out of the three vertical values ([top], [bottom], [height]) can be
/// set. The third must be null. /// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// vertically.
final double bottom; final double bottom;
/// The child's width. /// The child's width.
/// ///
/// Only two out of the three horizontal values ([left], [right], [width]) can be /// Only two out of the three horizontal values ([left], [right], [width]) can be
/// set. The third must be null. /// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// horizontally.
final double width; final double width;
/// The child's height. /// The child's height.
/// ///
/// Only two out of the three vertical values ([top], [bottom], [height]) can be /// Only two out of the three vertical values ([top], [bottom], [height]) can be
/// set. The third must be null. /// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// vertically.
final double height; final double height;
@override @override
...@@ -3897,6 +3930,18 @@ class RichText extends LeafRenderObjectWidget { ...@@ -3897,6 +3930,18 @@ class RichText extends LeafRenderObjectWidget {
..textScaleFactor = textScaleFactor ..textScaleFactor = textScaleFactor
..maxLines = maxLines; ..maxLines = maxLines;
} }
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
description.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
description.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: TextOverflow.clip));
description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: 1.0));
description.add(new IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
description.add(new StringProperty('text', text.toPlainText()));
}
} }
/// A widget that displays a [dart:ui.Image] directly. /// A widget that displays a [dart:ui.Image] directly.
......
...@@ -4519,6 +4519,11 @@ class LeafRenderObjectElement extends RenderObjectElement { ...@@ -4519,6 +4519,11 @@ class LeafRenderObjectElement extends RenderObjectElement {
void removeChildRenderObject(RenderObject child) { void removeChildRenderObject(RenderObject child) {
assert(false); assert(false);
} }
@override
List<DiagnosticsNode> debugDescribeChildren() {
return widget.debugDescribeChildren();
}
} }
/// An [Element] that uses a [SingleChildRenderObjectWidget] as its configuration. /// An [Element] that uses a [SingleChildRenderObjectWidget] as its configuration.
......
...@@ -29,7 +29,14 @@ class IconData { ...@@ -29,7 +29,14 @@ class IconData {
/// The font family from which the glyph for the [codePoint] will be selected. /// The font family from which the glyph for the [codePoint] will be selected.
final String fontFamily; final String fontFamily;
// The name of the package from which the font family is included. /// The name of the package from which the font family is included.
///
/// The name is used by the [Icon] widget when configuring the [TextStyle] so
/// that the given [fontFamily] is obtained from the appropriate asset.
///
/// See also:
///
/// * [TextStyle], which describes how to use fonts from other packages.
final String fontPackage; final String fontPackage;
@override @override
......
...@@ -292,5 +292,11 @@ class Text extends StatelessWidget { ...@@ -292,5 +292,11 @@ class Text extends StatelessWidget {
super.debugFillProperties(description); super.debugFillProperties(description);
description.add(new StringProperty('data', data, showName: false)); description.add(new StringProperty('data', data, showName: false));
style?.debugFillProperties(description); style?.debugFillProperties(description);
description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
description.add(new FlagProperty('softWrap', value: softWrap, ifTrue: 'wrapping at box width', ifFalse: 'no wrapping except at line break characters', showName: true));
description.add(new EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null));
description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
description.add(new IntProperty('maxLines', maxLines, defaultValue: null));
} }
} }
...@@ -46,6 +46,9 @@ void main() { ...@@ -46,6 +46,9 @@ void main() {
expect(painter.text.text.length, 28); expect(painter.text.text.length, 28);
painter.layout(); painter.layout();
// The skips here are because the old rendering code considers the bidi formatting characters
// to be part of the word sometimes and not others, which is fine, but we'd mildly prefer if
// we were consistently considering them part of words always.
final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4, affinity: TextAffinity.downstream)); final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4, affinity: TextAffinity.downstream));
expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs); expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs);
final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14, affinity: TextAffinity.downstream)); final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14, affinity: TextAffinity.downstream));
......
...@@ -629,4 +629,122 @@ void main() { ...@@ -629,4 +629,122 @@ void main() {
), ),
); );
}); });
testWidgets('Alignment with partially-positioned children', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.rtl,
child: new Stack(
alignment: Alignment.center,
children: <Widget>[
const SizedBox(width: 100.0, height: 100.0),
const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
],
),
),
);
expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(350.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(350.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(0.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(350.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Stack(
alignment: Alignment.center,
children: <Widget>[
const SizedBox(width: 100.0, height: 100.0),
const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
],
),
),
);
expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(350.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(350.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(0.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(700.0, 250.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(350.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(350.0, 500.0, 100.0, 100.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
const SizedBox(width: 100.0, height: 100.0),
const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
],
),
),
);
expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(700.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(0.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(700.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(700.0, 500.0, 100.0, 100.0));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Stack(
alignment: Alignment.topLeft,
children: <Widget>[
const SizedBox(width: 100.0, height: 100.0),
const Positioned(left: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(right: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const Positioned(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(start: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(end: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(top: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
const PositionedDirectional(bottom: 0.0, child: const SizedBox(width: 100.0, height: 100.0)),
],
),
),
);
expect(tester.getRect(find.byType(SizedBox).at(0)), new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(1)), new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(2)), new Rect.fromLTWH(700.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(3)), new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(4)), new Rect.fromLTWH(0.0, 500.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(5)), new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(6)), new Rect.fromLTWH(700.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(7)), new Rect.fromLTWH(0.0, 0.0, 100.0, 100.0));
expect(tester.getRect(find.byType(SizedBox).at(8)), new Rect.fromLTWH(0.0, 500.0, 100.0, 100.0));
});
} }
...@@ -765,23 +765,23 @@ void main() { ...@@ -765,23 +765,23 @@ void main() {
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'Table-[GlobalKey#00000](renderObject: RenderTable#00000)\n' 'Table-[GlobalKey#00000](renderObject: RenderTable#00000)\n'
'├Text("A")\n' '├Text("A")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "A", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("B")\n' '├Text("B")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "B", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("C")\n' '├Text("C")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "C", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("D")\n' '├Text("D")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "D", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("EEE")\n' '├Text("EEE")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "EEE", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("F")\n' '├Text("F")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "F", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("G")\n' '├Text("G")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "G", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'├Text("H")\n' '├Text("H")\n'
'│└RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n' '│└RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "H", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
'└Text("III")\n' '└Text("III")\n'
' └RichText(renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n', ' └RichText(softWrap: wrapping at box width, maxLines: unlimited, text: "III", renderObject: RenderParagraph#00000 relayoutBoundary=up1)\n'
), ),
); );
}); });
......
...@@ -52,7 +52,7 @@ void main() { ...@@ -52,7 +52,7 @@ void main() {
final String message = failure.message; final String message = failure.message;
expect(message, contains('Expected: no matching nodes in the widget tree\n')); expect(message, contains('Expected: no matching nodes in the widget tree\n'));
expect(message, contains('Actual: ?:<exactly one widget with text "foo": Text("foo")>\n')); expect(message, contains('Actual: ?:<exactly one widget with text "foo": Text("foo", textDirection: ltr)>\n'));
expect(message, contains('Which: means one was found but none were expected\n')); expect(message, contains('Which: means one was found but none were expected\n'));
}); });
...@@ -70,7 +70,7 @@ void main() { ...@@ -70,7 +70,7 @@ void main() {
final String message = failure.message; final String message = failure.message;
expect(message, contains('Expected: no matching nodes in the widget tree\n')); expect(message, contains('Expected: no matching nodes in the widget tree\n'));
expect(message, contains('Actual: ?:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo")>\n')); expect(message, contains('Actual: ?:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo", textDirection: ltr)>\n'));
expect(message, contains('Which: means one was found but none were expected\n')); expect(message, contains('Which: means one was found but none were expected\n'));
}); });
......
...@@ -738,6 +738,10 @@ class VM extends ServiceObjectOwner { ...@@ -738,6 +738,10 @@ class VM extends ServiceObjectOwner {
} on WebSocketChannelException catch (error) { } on WebSocketChannelException catch (error) {
throwToolExit('Error connecting to observatory: $error'); throwToolExit('Error connecting to observatory: $error');
return null; return null;
} on rpc.RpcException catch (error) {
printError('Error ${error.code} received from application: ${error.message}');
printTrace('${error.data}');
return null;
} }
} }
......
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