Unverified Commit 21f22ed3 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Visibility widget (#20365)

* Visibility widget

This attempts to address the confusion around how to hide a widget subtree.

* Apply review comments

* More clarifications
parent 6146c0f1
...@@ -717,16 +717,23 @@ class RenderOpacity extends RenderProxyBox { ...@@ -717,16 +717,23 @@ class RenderOpacity extends RenderProxyBox {
/// Creates a partially transparent render object. /// Creates a partially transparent render object.
/// ///
/// The [opacity] argument must be between 0.0 and 1.0, inclusive. /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
RenderOpacity({ double opacity = 1.0, RenderBox child }) RenderOpacity({
: assert(opacity != null), double opacity = 1.0,
assert(opacity >= 0.0 && opacity <= 1.0), bool alwaysIncludeSemantics = false,
_opacity = opacity, RenderBox child,
_alpha = _getAlphaFromOpacity(opacity), }) : assert(opacity != null),
super(child); assert(opacity >= 0.0 && opacity <= 1.0),
assert(alwaysIncludeSemantics != null),
_opacity = opacity,
_alwaysIncludeSemantics = alwaysIncludeSemantics,
_alpha = _getAlphaFromOpacity(opacity),
super(child);
@override @override
bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255); bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255);
int _alpha;
/// The fraction to scale the child's alpha value. /// The fraction to scale the child's alpha value.
/// ///
/// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
...@@ -755,13 +762,26 @@ class RenderOpacity extends RenderProxyBox { ...@@ -755,13 +762,26 @@ class RenderOpacity extends RenderProxyBox {
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
} }
int _alpha; /// Whether child semantics are included regardless of the opacity.
///
/// If false, semantics are excluded when [opacity] is 0.0.
///
/// Defaults to false.
bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
bool _alwaysIncludeSemantics;
set alwaysIncludeSemantics(bool value) {
if (value == _alwaysIncludeSemantics)
return;
_alwaysIncludeSemantics = value;
markNeedsSemanticsUpdate();
}
@override @override
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
if (child != null) { if (child != null) {
if (_alpha == 0) if (_alpha == 0) {
return; return;
}
if (_alpha == 255) { if (_alpha == 255) {
context.paintChild(child, offset); context.paintChild(child, offset);
return; return;
...@@ -773,7 +793,7 @@ class RenderOpacity extends RenderProxyBox { ...@@ -773,7 +793,7 @@ class RenderOpacity extends RenderProxyBox {
@override @override
void visitChildrenForSemantics(RenderObjectVisitor visitor) { void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (child != null && _alpha != 0) if (child != null && (_alpha != 0 || alwaysIncludeSemantics))
visitor(child); visitor(child);
} }
...@@ -781,6 +801,7 @@ class RenderOpacity extends RenderProxyBox { ...@@ -781,6 +801,7 @@ class RenderOpacity extends RenderProxyBox {
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(new DoubleProperty('opacity', opacity)); properties.add(new DoubleProperty('opacity', opacity));
properties.add(new FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
} }
} }
...@@ -832,6 +853,8 @@ class RenderAnimatedOpacity extends RenderProxyBox { ...@@ -832,6 +853,8 @@ class RenderAnimatedOpacity extends RenderProxyBox {
/// Whether child semantics are included regardless of the opacity. /// Whether child semantics are included regardless of the opacity.
/// ///
/// If false, semantics are excluded when [opacity] is 0.0.
///
/// Defaults to false. /// Defaults to false.
bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
bool _alwaysIncludeSemantics; bool _alwaysIncludeSemantics;
...@@ -893,6 +916,7 @@ class RenderAnimatedOpacity extends RenderProxyBox { ...@@ -893,6 +916,7 @@ class RenderAnimatedOpacity extends RenderProxyBox {
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<Animation<double>>('opacity', opacity)); properties.add(new DiagnosticsProperty<Animation<double>>('opacity', opacity));
properties.add(new FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
} }
} }
......
...@@ -145,15 +145,15 @@ class Directionality extends InheritedWidget { ...@@ -145,15 +145,15 @@ class Directionality extends InheritedWidget {
/// ///
/// ## Opacity Animation /// ## Opacity Animation
/// ///
/// [Opacity] animations should be built using [AnimatedOpacity] rather than
/// manually rebuilding the [Opacity] widget.
///
/// Animating an [Opacity] widget directly causes the widget (and possibly its /// Animating an [Opacity] widget directly causes the widget (and possibly its
/// subtree) to rebuild each frame, which is not very efficient. Consider using /// subtree) to rebuild each frame, which is not very efficient. Consider using
/// an [AnimatedOpacity] instead. /// an [AnimatedOpacity] instead.
/// ///
/// See also: /// See also:
/// ///
/// * [Visibility], which can hide a child more efficiently (albeit less
/// subtly, because it is either visible or hidden, rather than allowing
/// fractional opacity values).
/// * [ShaderMask], which can apply more elaborate effects to its child. /// * [ShaderMask], which can apply more elaborate effects to its child.
/// * [Transform], which applies an arbitrary transform to its child widget at /// * [Transform], which applies an arbitrary transform to its child widget at
/// paint time. /// paint time.
...@@ -169,8 +169,10 @@ class Opacity extends SingleChildRenderObjectWidget { ...@@ -169,8 +169,10 @@ class Opacity extends SingleChildRenderObjectWidget {
const Opacity({ const Opacity({
Key key, Key key,
@required this.opacity, @required this.opacity,
this.alwaysIncludeSemantics = false,
Widget child, Widget child,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
assert(alwaysIncludeSemantics != null),
super(key: key, child: child); super(key: key, child: child);
/// The fraction to scale the child's alpha value. /// The fraction to scale the child's alpha value.
...@@ -185,18 +187,36 @@ class Opacity extends SingleChildRenderObjectWidget { ...@@ -185,18 +187,36 @@ class Opacity extends SingleChildRenderObjectWidget {
/// expensive. /// expensive.
final double opacity; final double opacity;
/// Whether the semantic information of the children is always included.
///
/// Defaults to false.
///
/// When true, regardless of the opacity settings the child semantic
/// information is exposed as if the widget were fully visible. This is
/// useful in cases where labels may be hidden during animations that
/// would otherwise contribute relevant semantics.
final bool alwaysIncludeSemantics;
@override @override
RenderOpacity createRenderObject(BuildContext context) => new RenderOpacity(opacity: opacity); RenderOpacity createRenderObject(BuildContext context) {
return new RenderOpacity(
opacity: opacity,
alwaysIncludeSemantics: alwaysIncludeSemantics,
);
}
@override @override
void updateRenderObject(BuildContext context, RenderOpacity renderObject) { void updateRenderObject(BuildContext context, RenderOpacity renderObject) {
renderObject.opacity = opacity; renderObject
..opacity = opacity
..alwaysIncludeSemantics = alwaysIncludeSemantics;
} }
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(new DoubleProperty('opacity', opacity)); properties.add(new DoubleProperty('opacity', opacity));
properties.add(new FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
} }
} }
...@@ -1710,6 +1730,12 @@ class SizedBox extends SingleChildRenderObjectWidget { ...@@ -1710,6 +1730,12 @@ class SizedBox extends SingleChildRenderObjectWidget {
height = double.infinity, height = double.infinity,
super(key: key, child: child); super(key: key, child: child);
/// Creates a box that will become as small as its parent allows.
const SizedBox.shrink({ Key key, Widget child })
: width = 0.0,
height = 0.0,
super(key: key, child: child);
/// Creates a box with the specified size. /// Creates a box with the specified size.
SizedBox.fromSize({ Key key, Widget child, Size size }) SizedBox.fromSize({ Key key, Widget child, Size size })
: width = size?.width, : width = size?.width,
...@@ -1740,17 +1766,27 @@ class SizedBox extends SingleChildRenderObjectWidget { ...@@ -1740,17 +1766,27 @@ class SizedBox extends SingleChildRenderObjectWidget {
@override @override
String toStringShort() { String toStringShort() {
final String type = (width == double.infinity && height == double.infinity) ? String type;
'$runtimeType.expand' : '$runtimeType'; if (width == double.infinity && height == double.infinity) {
type = '$runtimeType.expand';
} else if (width == 0.0 && height == 0.0) {
type = '$runtimeType.shrink';
} else {
type = '$runtimeType';
}
return key == null ? '$type' : '$type-$key'; return key == null ? '$type' : '$type-$key';
} }
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
final DiagnosticLevel level = (width == double.infinity && height == double.infinity) DiagnosticLevel level;
? DiagnosticLevel.hidden if ((width == double.infinity && height == double.infinity) ||
: DiagnosticLevel.info; (width == 0.0 && height == 0.0)) {
level = DiagnosticLevel.hidden;
} else {
level = DiagnosticLevel.info;
}
properties.add(new DoubleProperty('width', width, defaultValue: null, level: level)); properties.add(new DoubleProperty('width', width, defaultValue: null, level: level));
properties.add(new DoubleProperty('height', height, defaultValue: null, level: level)); properties.add(new DoubleProperty('height', height, defaultValue: null, level: level));
} }
...@@ -2237,9 +2273,9 @@ class SizedOverflowBox extends SingleChildRenderObjectWidget { ...@@ -2237,9 +2273,9 @@ class SizedOverflowBox extends SingleChildRenderObjectWidget {
} }
} }
/// A widget that lays the child out as if it was in the tree, but without painting anything, /// A widget that lays the child out as if it was in the tree, but without
/// without making the child available for hit testing, and without taking any /// painting anything, without making the child available for hit testing, and
/// room in the parent. /// without taking any room in the parent.
/// ///
/// Animations continue to run in offstage children, and therefore use battery /// Animations continue to run in offstage children, and therefore use battery
/// and CPU time, regardless of whether the animations end up being visible. /// and CPU time, regardless of whether the animations end up being visible.
...@@ -2251,8 +2287,10 @@ class SizedOverflowBox extends SingleChildRenderObjectWidget { ...@@ -2251,8 +2287,10 @@ class SizedOverflowBox extends SingleChildRenderObjectWidget {
/// ///
/// See also: /// See also:
/// ///
/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). /// * [Visibility], which can hide a child more efficiently (albeit less
/// subtly).
/// * [TickerMode], which can be used to disable animations in a subtree. /// * [TickerMode], which can be used to disable animations in a subtree.
/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/).
class Offstage extends SingleChildRenderObjectWidget { class Offstage extends SingleChildRenderObjectWidget {
/// Creates a widget that visually hides its child. /// Creates a widget that visually hides its child.
const Offstage({ Key key, this.offstage = true, Widget child }) const Offstage({ Key key, this.offstage = true, Widget child })
......
...@@ -341,8 +341,8 @@ class FadeTransition extends SingleChildRenderObjectWidget { ...@@ -341,8 +341,8 @@ class FadeTransition extends SingleChildRenderObjectWidget {
const FadeTransition({ const FadeTransition({
Key key, Key key,
@required this.opacity, @required this.opacity,
Widget child,
this.alwaysIncludeSemantics = false, this.alwaysIncludeSemantics = false,
Widget child,
}) : super(key: key, child: child); }) : super(key: key, child: child);
/// The animation that controls the opacity of the child. /// The animation that controls the opacity of the child.
...@@ -382,6 +382,7 @@ class FadeTransition extends SingleChildRenderObjectWidget { ...@@ -382,6 +382,7 @@ class FadeTransition extends SingleChildRenderObjectWidget {
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<Animation<double>>('opacity', opacity)); properties.add(new DiagnosticsProperty<Animation<double>>('opacity', opacity));
properties.add(new FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
} }
} }
......
This diff is collapsed.
...@@ -99,5 +99,6 @@ export 'src/widgets/title.dart'; ...@@ -99,5 +99,6 @@ export 'src/widgets/title.dart';
export 'src/widgets/transitions.dart'; export 'src/widgets/transitions.dart';
export 'src/widgets/unique_widget.dart'; export 'src/widgets/unique_widget.dart';
export 'src/widgets/viewport.dart'; export 'src/widgets/viewport.dart';
export 'src/widgets/visibility.dart';
export 'src/widgets/widget_inspector.dart'; export 'src/widgets/widget_inspector.dart';
export 'src/widgets/will_pop_scope.dart'; export 'src/widgets/will_pop_scope.dart';
...@@ -126,6 +126,13 @@ class TestRecordingPaintingContext extends ClipContext implements PaintingContex ...@@ -126,6 +126,13 @@ class TestRecordingPaintingContext extends ClipContext implements PaintingContex
canvas.restore(); canvas.restore();
} }
@override
void pushOpacity(Offset offset, int alpha, PaintingContextCallback painter) {
canvas.saveLayer(null, null); // TODO(ianh): Expose the alpha somewhere.
painter(this, offset);
canvas.restore();
}
@override @override
void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset, {Rect childPaintBounds}) { void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset, {Rect childPaintBounds}) {
painter(this, offset); painter(this, offset);
......
// Copyright 2018 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/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Opacity', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
// Opacity 1.0: Semantics and painting
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 1.0,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0),
label: 'a',
textDirection: TextDirection.rtl,
)
],
),
));
expect(find.byType(Opacity), paints..paragraph());
// Opacity 0.0: Nothing
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 0.0,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(),
));
expect(find.byType(Opacity), paintsNothing);
// Opacity 0.0 with semantics: Just semantics
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 0.0,
alwaysIncludeSemantics: true,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0),
label: 'a',
textDirection: TextDirection.rtl,
)
],
),
));
expect(find.byType(Opacity), paintsNothing);
// Opacity 0.0 without semantics: Nothing
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 0.0,
alwaysIncludeSemantics: false,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(),
));
expect(find.byType(Opacity), paintsNothing);
// Opacity 0.1: Semantics and painting
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 0.1,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0),
label: 'a',
textDirection: TextDirection.rtl,
)
],
),
));
expect(find.byType(Opacity), paints..paragraph());
// Opacity 0.1 without semantics: Still has semantics and painting
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 0.1,
alwaysIncludeSemantics: false,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0),
label: 'a',
textDirection: TextDirection.rtl,
)
],
),
));
expect(find.byType(Opacity), paints..paragraph());
// Opacity 0.1 with semantics: Semantics and painting
await tester.pumpWidget(
const Opacity(
child: Text('a', textDirection: TextDirection.rtl),
opacity: 0.1,
alwaysIncludeSemantics: true,
),
);
expect(semantics, hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: Rect.fromLTRB(0.0, 0.0, 800.0, 600.0),
label: 'a',
textDirection: TextDirection.rtl,
)
],
),
));
expect(find.byType(Opacity), paints..paragraph());
semantics.dispose();
});
}
...@@ -225,8 +225,6 @@ class TestSemantics { ...@@ -225,8 +225,6 @@ class TestSemantics {
DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.inverseHitTest, DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.inverseHitTest,
} }
) { ) {
final SemanticsData nodeData = node.getSemanticsData();
bool fail(String message) { bool fail(String message) {
matchState[TestSemantics] = '$message'; matchState[TestSemantics] = '$message';
return false; return false;
...@@ -237,6 +235,8 @@ class TestSemantics { ...@@ -237,6 +235,8 @@ class TestSemantics {
if (!ignoreId && id != node.id) if (!ignoreId && id != node.id)
return fail('expected node id $id but found id ${node.id}.'); return fail('expected node id $id but found id ${node.id}.');
final SemanticsData nodeData = node.getSemanticsData();
final int flagsBitmask = flags is int final int flagsBitmask = flags is int
? flags ? flags
: flags.fold<int>(0, (int bitmask, SemanticsFlag flag) => bitmask | flag.index); : flags.fold<int>(0, (int bitmask, SemanticsFlag flag) => bitmask | flag.index);
...@@ -372,7 +372,7 @@ class SemanticsTester { ...@@ -372,7 +372,7 @@ class SemanticsTester {
} }
@override @override
String toString() => 'SemanticsTester for ${tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode}'; String toString() => 'SemanticsTester for ${tester.binding.pipelineOwner.semanticsOwner?.rootSemanticsNode}';
/// Returns all semantics nodes in the current semantics tree whose properties /// Returns all semantics nodes in the current semantics tree whose properties
/// match the non-null arguments. /// match the non-null arguments.
...@@ -487,7 +487,7 @@ class SemanticsTester { ...@@ -487,7 +487,7 @@ class SemanticsTester {
/// over-test. Prefer breaking your widgets into smaller widgets and test them /// over-test. Prefer breaking your widgets into smaller widgets and test them
/// individually. /// individually.
String generateTestSemanticsExpressionForCurrentSemanticsTree(DebugSemanticsDumpOrder childOrder) { String generateTestSemanticsExpressionForCurrentSemanticsTree(DebugSemanticsDumpOrder childOrder) {
final SemanticsNode node = tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode; final SemanticsNode node = tester.binding.pipelineOwner.semanticsOwner?.rootSemanticsNode;
return _generateSemanticsTestForNode(node, 0, childOrder); return _generateSemanticsTestForNode(node, 0, childOrder);
} }
...@@ -520,6 +520,8 @@ class SemanticsTester { ...@@ -520,6 +520,8 @@ class SemanticsTester {
/// Recursively generates [TestSemantics] code for [node] and its children, /// Recursively generates [TestSemantics] code for [node] and its children,
/// indenting the expression by `indentAmount`. /// indenting the expression by `indentAmount`.
static String _generateSemanticsTestForNode(SemanticsNode node, int indentAmount, DebugSemanticsDumpOrder childOrder) { static String _generateSemanticsTestForNode(SemanticsNode node, int indentAmount, DebugSemanticsDumpOrder childOrder) {
if (node == null)
return 'null';
final String indent = ' ' * indentAmount; final String indent = ' ' * indentAmount;
final StringBuffer buf = new StringBuffer(); final StringBuffer buf = new StringBuffer();
final SemanticsData nodeData = node.getSemanticsData(); final SemanticsData nodeData = node.getSemanticsData();
...@@ -590,7 +592,7 @@ class _HasSemantics extends Matcher { ...@@ -590,7 +592,7 @@ class _HasSemantics extends Matcher {
@override @override
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) { bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
final bool doesMatch = _semantics._matches( final bool doesMatch = _semantics._matches(
item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, item.tester.binding.pipelineOwner.semanticsOwner?.rootSemanticsNode,
matchState, matchState,
ignoreTransform: ignoreTransform, ignoreTransform: ignoreTransform,
ignoreRect: ignoreRect, ignoreRect: ignoreRect,
...@@ -600,6 +602,9 @@ class _HasSemantics extends Matcher { ...@@ -600,6 +602,9 @@ class _HasSemantics extends Matcher {
if (!doesMatch) { if (!doesMatch) {
matchState['would-match'] = item.generateTestSemanticsExpressionForCurrentSemanticsTree(childOrder); matchState['would-match'] = item.generateTestSemanticsExpressionForCurrentSemanticsTree(childOrder);
} }
if (item.tester.binding.pipelineOwner.semanticsOwner == null) {
matchState['additional-notes'] = '(Check that the SemanticsTester has not been disposed early.)';
}
return doesMatch; return doesMatch;
} }
...@@ -608,14 +613,25 @@ class _HasSemantics extends Matcher { ...@@ -608,14 +613,25 @@ class _HasSemantics extends Matcher {
return description.add('semantics node matching:\n$_semantics'); return description.add('semantics node matching:\n$_semantics');
} }
String _indent(String text) {
return text.toString().trimRight().split('\n').map((String line) => ' $line').join('\n');
}
@override @override
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) { Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
return mismatchDescription Description result = mismatchDescription
.add('${matchState[TestSemantics]}\n') .add('${matchState[TestSemantics]}\n')
.add('Current SemanticsNode tree:\n') .add('Current SemanticsNode tree:\n')
.add(RendererBinding.instance?.renderView?.debugSemantics?.toStringDeep(childOrder: childOrder)) .add(_indent(RendererBinding.instance?.renderView?.debugSemantics?.toStringDeep(childOrder: childOrder)))
.add('The semantics tree would have matched the following configuration:\n') .add('\n')
.add(matchState['would-match']); .add('The semantics tree would have matched the following configuration:\n')
.add(_indent(matchState['would-match']));
if (matchState.containsKey('additional-notes')) {
result = result
.add('\n')
.add(matchState['additional-notes']);
}
return result;
} }
} }
......
...@@ -30,6 +30,10 @@ void main() { ...@@ -30,6 +30,10 @@ void main() {
const SizedBox f = SizedBox.expand(); const SizedBox f = SizedBox.expand();
expect(f.width, double.infinity); expect(f.width, double.infinity);
expect(f.height, double.infinity); expect(f.height, double.infinity);
const SizedBox g = SizedBox.shrink();
expect(g.width, 0.0);
expect(g.height, 0.0);
}); });
testWidgets('SizedBox - no child', (WidgetTester tester) async { testWidgets('SizedBox - no child', (WidgetTester tester) async {
...@@ -95,6 +99,15 @@ void main() { ...@@ -95,6 +99,15 @@ void main() {
) )
); );
expect(patient.currentContext.size, equals(const Size(800.0, 600.0))); expect(patient.currentContext.size, equals(const Size(800.0, 600.0)));
await tester.pumpWidget(
new Center(
child: new SizedBox.shrink(
key: patient,
)
)
);
expect(patient.currentContext.size, equals(const Size(0.0, 0.0)));
}); });
testWidgets('SizedBox - container child', (WidgetTester tester) async { testWidgets('SizedBox - container child', (WidgetTester tester) async {
...@@ -166,5 +179,15 @@ void main() { ...@@ -166,5 +179,15 @@ void main() {
) )
); );
expect(patient.currentContext.size, equals(const Size(800.0, 600.0))); expect(patient.currentContext.size, equals(const Size(800.0, 600.0)));
await tester.pumpWidget(
new Center(
child: new SizedBox.shrink(
key: patient,
child: new Container(),
)
)
);
expect(patient.currentContext.size, equals(const Size(0.0, 0.0)));
}); });
} }
This diff is collapsed.
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