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 {
ImageStream _imageStream;
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) {
if (_details == null)
return;
......
......@@ -43,6 +43,10 @@ class RenderListBody extends RenderBox
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 _axisDirection;
set axisDirection(AxisDirection value) {
......@@ -53,6 +57,8 @@ class RenderListBody extends RenderBox
markNeedsLayout();
}
/// The axis (horizontal or vertical) corresponding to the current
/// [axisDirection].
Axis get mainAxis => axisDirectionToAxis(axisDirection);
@override
......
......@@ -2114,6 +2114,17 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
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
void describeSemanticsConfiguration(SemanticsConfiguration config) {
// Nothing to do by default.
......
......@@ -403,6 +403,20 @@ class RenderParagraph extends RenderBox {
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
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
......
......@@ -596,9 +596,18 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
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({
@required SemanticsConfiguration config,
@required List<SemanticsNode> childrenInInversePaintOrder,
@required List<SemanticsNode> childrenInInversePaintOrder,
}) {
config ??= _kEmptyConfig;
if (_isDifferentFromCurrentSemanticAnnotation(config))
......
......@@ -332,14 +332,20 @@ class RenderStack extends RenderBox
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 points determined by [alignment] are co-located. For example, if the
/// [alignment] is [Alignment.topLeft], then the top left corner of
/// 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.
AlignmentGeometry get alignment => _alignment;
AlignmentGeometry _alignment;
......@@ -504,20 +510,26 @@ class RenderStack extends RenderBox
child.layout(childConstraints, parentUsesSize: true);
double x = 0.0;
if (childParentData.left != null)
double x;
if (childParentData.left != null) {
x = childParentData.left;
else if (childParentData.right != null)
} else if (childParentData.right != null) {
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)
_hasVisualOverflow = true;
double y = 0.0;
if (childParentData.top != null)
double y;
if (childParentData.top != null) {
y = childParentData.top;
else if (childParentData.bottom != null)
} else if (childParentData.bottom != null) {
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)
_hasVisualOverflow = true;
......
......@@ -2239,9 +2239,10 @@ class ListBody extends MultiChildRenderObjectWidget {
/// 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
/// non-positioned children, which are positioned according to [alignment]
/// (which defaults to the top-left corner). The positioned children are then
/// placed relative to the stack according to their top, right, bottom, and left
/// properties.
/// (which defaults to the top-left corner in left-to-right environments and the
/// top-right corner in right-to-left environments). The positioned children are
/// 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
/// bottom. If you want to change the order in which the children paint, you
......@@ -2282,12 +2283,18 @@ class Stack extends MultiChildRenderObjectWidget {
List<Widget> children: const <Widget>[],
}) : 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 points determined by [alignment] are co-located. For example, if the
/// [alignment] is [Alignment.topLeft], then the top left corner of
/// 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;
/// The text direction with which to resolve [alignment].
......@@ -2398,6 +2405,12 @@ class IndexedStack extends Stack {
/// [height] properties can be used to give the dimensions, with one
/// 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:
///
/// * [PositionedDirectional], which adapts to the ambient [Directionality].
......@@ -2413,6 +2426,8 @@ class Positioned extends ParentDataWidget<Stack> {
///
/// * [Positioned.directional], which specifies the widget's horizontal
/// 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({
Key key,
this.left,
......@@ -2530,36 +2545,54 @@ class Positioned extends ParentDataWidget<Stack> {
///
/// Only two out of the three horizontal values ([left], [right], [width]) can be
/// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// horizontally.
final double left;
/// 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
/// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// vertically.
final double top;
/// 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
/// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// horizontally.
final double right;
/// 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
/// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// vertically.
final double bottom;
/// The child's width.
///
/// Only two out of the three horizontal values ([left], [right], [width]) can be
/// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// horizontally.
final double width;
/// The child's height.
///
/// Only two out of the three vertical values ([top], [bottom], [height]) can be
/// set. The third must be null.
///
/// If all three are null, the [Stack.alignment] is used to position the child
/// vertically.
final double height;
@override
......@@ -3897,6 +3930,18 @@ class RichText extends LeafRenderObjectWidget {
..textScaleFactor = textScaleFactor
..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.
......
......@@ -4519,6 +4519,11 @@ class LeafRenderObjectElement extends RenderObjectElement {
void removeChildRenderObject(RenderObject child) {
assert(false);
}
@override
List<DiagnosticsNode> debugDescribeChildren() {
return widget.debugDescribeChildren();
}
}
/// An [Element] that uses a [SingleChildRenderObjectWidget] as its configuration.
......
......@@ -29,7 +29,14 @@ class IconData {
/// The font family from which the glyph for the [codePoint] will be selected.
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;
@override
......
......@@ -292,5 +292,11 @@ class Text extends StatelessWidget {
super.debugFillProperties(description);
description.add(new StringProperty('data', data, showName: false));
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() {
expect(painter.text.text.length, 28);
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));
expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs);
final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14, affinity: TextAffinity.downstream));
......
......@@ -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() {
equalsIgnoringHashCodes(
'Table-[GlobalKey#00000](renderObject: RenderTable#00000)\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'
'│└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'
'│└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'
'│└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'
'│└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'
'│└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'
'│└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'
'│└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'
' └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() {
final String message = failure.message;
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'));
});
......@@ -70,7 +70,7 @@ void main() {
final String message = failure.message;
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'));
});
......
......@@ -738,6 +738,10 @@ class VM extends ServiceObjectOwner {
} on WebSocketChannelException catch (error) {
throwToolExit('Error connecting to observatory: $error');
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