Commit 78a29260 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Some more tests for AnimatedPositionedDirectional and more debugging checks (#12231)

parent 824db696
...@@ -7,6 +7,7 @@ import 'dart:math' as math; ...@@ -7,6 +7,7 @@ import 'dart:math' as math;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
const double _kOffset = 40.0; // distance to bottom of banner, at a 45 degree angle inwards const double _kOffset = 40.0; // distance to bottom of banner, at a 45 degree angle inwards
...@@ -294,6 +295,7 @@ class Banner extends StatelessWidget { ...@@ -294,6 +295,7 @@ class Banner extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert((textDirection != null && layoutDirection != null) || debugCheckHasDirectionality(context));
return new CustomPaint( return new CustomPaint(
foregroundPainter: new BannerPainter( foregroundPainter: new BannerPainter(
message: message, message: message,
......
...@@ -3792,11 +3792,10 @@ class RichText extends LeafRenderObjectWidget { ...@@ -3792,11 +3792,10 @@ class RichText extends LeafRenderObjectWidget {
@override @override
RenderParagraph createRenderObject(BuildContext context) { RenderParagraph createRenderObject(BuildContext context) {
final TextDirection direction = textDirection ?? Directionality.of(context); assert(textDirection != null || debugCheckHasDirectionality(context));
assert(direction != null, 'A RichText was created with no textDirection and no ambient Directionality widget.');
return new RenderParagraph(text, return new RenderParagraph(text,
textAlign: textAlign, textAlign: textAlign,
textDirection: direction, textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap, softWrap: softWrap,
overflow: overflow, overflow: overflow,
textScaleFactor: textScaleFactor, textScaleFactor: textScaleFactor,
...@@ -3806,6 +3805,7 @@ class RichText extends LeafRenderObjectWidget { ...@@ -3806,6 +3805,7 @@ class RichText extends LeafRenderObjectWidget {
@override @override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) { void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
assert(textDirection != null || debugCheckHasDirectionality(context));
renderObject renderObject
..text = text ..text = text
..textAlign = textAlign ..textAlign = textAlign
......
...@@ -7,6 +7,7 @@ import 'dart:developer' show Timeline; // to disambiguate reference in dartdocs ...@@ -7,6 +7,7 @@ import 'dart:developer' show Timeline; // to disambiguate reference in dartdocs
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'framework.dart'; import 'framework.dart';
import 'media_query.dart'; import 'media_query.dart';
import 'table.dart'; import 'table.dart';
...@@ -202,6 +203,43 @@ bool debugCheckHasMediaQuery(BuildContext context) { ...@@ -202,6 +203,43 @@ bool debugCheckHasMediaQuery(BuildContext context) {
return true; return true;
} }
/// Asserts that the given context has a [Directionality] ancestor.
///
/// Used by various widgets to make sure that they are only used in an
/// appropriate context.
///
/// To invoke this function, use the following pattern, typically in the
/// relevant Widget's build method:
///
/// ```dart
/// assert(debugCheckHasDirectionality(context));
/// ```
///
/// Does nothing if asserts are disabled. Always returns true.
bool debugCheckHasDirectionality(BuildContext context) {
assert(() {
if (context.widget is! Directionality && context.ancestorWidgetOfExactType(Directionality) == null) {
final Element element = context;
throw new FlutterError(
'No Directionality widget found.\n'
'${context.widget.runtimeType} widgets require a Directionality widget ancestor.\n'
'The specific widget that could not find a Directionality ancestor was:\n'
' ${context.widget}\n'
'The ownership chain for the affected widget is:\n'
' ${element.debugGetCreatorChain(10)}\n'
'Typically, the Directionality widget is introduced by the MaterialApp '
'or WidgetsApp widget at the top of your application widget tree. It '
'determines the ambient reading direction and is used, for example, to '
'determine how to lay out text, how to interpret "start" and "end" '
'values, and to resolve EdgeInsetsDirectional, '
'FractionalOffsetDirectional, and other *Directional objects.'
);
}
return true;
}());
return true;
}
/// Asserts that the `built` widget is not null. /// Asserts that the `built` widget is not null.
/// ///
/// Used when the given `widget` calls a builder function to check that the /// Used when the given `widget` calls a builder function to check that the
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'icon_data.dart'; import 'icon_data.dart';
import 'icon_theme.dart'; import 'icon_theme.dart';
...@@ -84,8 +85,8 @@ class Icon extends StatelessWidget { ...@@ -84,8 +85,8 @@ class Icon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null, 'Icon widgets required an ambient Directionality.');
final IconThemeData iconTheme = IconTheme.of(context); final IconThemeData iconTheme = IconTheme.of(context);
......
...@@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart'; ...@@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'basic.dart'; import 'basic.dart';
import 'container.dart'; import 'container.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'text.dart'; import 'text.dart';
import 'ticker_provider.dart'; import 'ticker_provider.dart';
...@@ -486,7 +487,8 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer> ...@@ -486,7 +487,8 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer>
/// See also: /// See also:
/// ///
/// * [AnimatedPositionedDirectional], which adapts to the ambient /// * [AnimatedPositionedDirectional], which adapts to the ambient
/// [Directionality]. /// [Directionality] (the same as this widget, but for animating
/// [PositionedDirectional]).
class AnimatedPositioned extends ImplicitlyAnimatedWidget { class AnimatedPositioned extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates its position implicitly. /// Creates a widget that animates its position implicitly.
/// ///
...@@ -545,14 +547,14 @@ class AnimatedPositioned extends ImplicitlyAnimatedWidget { ...@@ -545,14 +547,14 @@ class AnimatedPositioned extends ImplicitlyAnimatedWidget {
/// 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
/// set. The third must be null. /// be set. The third must be null.
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
/// set. The third must be null. /// be set. The third must be null.
final double height; final double height;
@override @override
...@@ -597,7 +599,7 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione ...@@ -597,7 +599,7 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione
right: _right?.evaluate(animation), right: _right?.evaluate(animation),
bottom: _bottom?.evaluate(animation), bottom: _bottom?.evaluate(animation),
width: _width?.evaluate(animation), width: _width?.evaluate(animation),
height: _height?.evaluate(animation) height: _height?.evaluate(animation),
); );
} }
...@@ -613,21 +615,25 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione ...@@ -613,21 +615,25 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione
} }
} }
/// Animated version of [PositionedDirectional] which automatically transitions the child's /// Animated version of [PositionedDirectional] which automatically transitions
/// position over a given duration whenever the given position changes. /// the child's position over a given duration whenever the given position
/// changes.
///
/// The ambient [Directionality] is used to determine whether [start] is to the
/// left or to the right.
/// ///
/// Only works if it's the child of a [Stack]. /// Only works if it's the child of a [Stack].
/// ///
/// See also: /// See also:
/// ///
/// * [AnimatedPositioned], which specifies the widget's position visually. /// * [AnimatedPositioned], which specifies the widget's position visually (the
/// * same as this widget, but for animating [Positioned]).
class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget { class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates its position implicitly. /// Creates a widget that animates its position implicitly.
/// ///
/// Only two out of the three horizontal values ([start], [end], /// Only two out of the three horizontal values ([start], [end], [width]), and
/// [width]), and only two out of the three vertical values ([top], /// only two out of the three vertical values ([top], [bottom], [height]), can
/// [bottom], [height]), can be set. In each case, at least one of /// be set. In each case, at least one of the three must be null.
/// the three must be null.
/// ///
/// The [curve] and [duration] arguments must not be null. /// The [curve] and [duration] arguments must not be null.
const AnimatedPositionedDirectional({ const AnimatedPositionedDirectional({
...@@ -662,14 +668,14 @@ class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget { ...@@ -662,14 +668,14 @@ class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget {
/// The child's width. /// The child's width.
/// ///
/// Only two out of the three horizontal values (start, end, width) can be /// Only two out of the three horizontal values ([start], [end], [width]) can
/// set. The third must be null. /// be set. The third must be null.
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
/// set. The third must be null. /// be set. The third must be null.
final double height; final double height;
@override @override
...@@ -707,14 +713,16 @@ class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<Animat ...@@ -707,14 +713,16 @@ class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<Animat
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new PositionedDirectional( assert(debugCheckHasDirectionality(context));
return new Positioned.directional(
textDirection: Directionality.of(context),
child: widget.child, child: widget.child,
start: _start?.evaluate(animation), start: _start?.evaluate(animation),
top: _top?.evaluate(animation), top: _top?.evaluate(animation),
end: _end?.evaluate(animation), end: _end?.evaluate(animation),
bottom: _bottom?.evaluate(animation), bottom: _bottom?.evaluate(animation),
width: _width?.evaluate(animation), width: _width?.evaluate(animation),
height: _height?.evaluate(animation) height: _height?.evaluate(animation),
); );
} }
......
...@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
/// [NavigationToolbar] is a layout helper to position 3 widgets or groups of /// [NavigationToolbar] is a layout helper to position 3 widgets or groups of
...@@ -60,6 +61,7 @@ class NavigationToolbar extends StatelessWidget { ...@@ -60,6 +61,7 @@ class NavigationToolbar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
if (leading != null) if (leading != null)
...@@ -72,7 +74,6 @@ class NavigationToolbar extends StatelessWidget { ...@@ -72,7 +74,6 @@ class NavigationToolbar extends StatelessWidget {
children.add(new LayoutId(id: _ToolbarSlot.trailing, child: trailing)); children.add(new LayoutId(id: _ToolbarSlot.trailing, child: trailing));
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
return new CustomMultiChildLayout( return new CustomMultiChildLayout(
delegate: new _ToolbarLayout( delegate: new _ToolbarLayout(
centerMiddle: centerMiddle, centerMiddle: centerMiddle,
......
...@@ -10,6 +10,7 @@ import 'package:flutter/physics.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'notification_listener.dart'; import 'notification_listener.dart';
import 'page_storage.dart'; import 'page_storage.dart';
...@@ -451,8 +452,8 @@ class _PageViewState extends State<PageView> { ...@@ -451,8 +452,8 @@ class _PageViewState extends State<PageView> {
AxisDirection _getDirection(BuildContext context) { AxisDirection _getDirection(BuildContext context) {
switch (widget.scrollDirection) { switch (widget.scrollDirection) {
case Axis.horizontal: case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection); final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection; return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical: case Axis.vertical:
......
...@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'primary_scroll_controller.dart'; import 'primary_scroll_controller.dart';
import 'scroll_controller.dart'; import 'scroll_controller.dart';
...@@ -180,8 +181,8 @@ abstract class ScrollView extends StatelessWidget { ...@@ -180,8 +181,8 @@ abstract class ScrollView extends StatelessWidget {
AxisDirection getDirection(BuildContext context) { AxisDirection getDirection(BuildContext context) {
switch (scrollDirection) { switch (scrollDirection) {
case Axis.horizontal: case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection); final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection; return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical: case Axis.vertical:
......
...@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; ...@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'basic.dart'; import 'basic.dart';
import 'debug.dart';
import 'framework.dart'; import 'framework.dart';
import 'primary_scroll_controller.dart'; import 'primary_scroll_controller.dart';
import 'scroll_controller.dart'; import 'scroll_controller.dart';
...@@ -117,8 +118,8 @@ class SingleChildScrollView extends StatelessWidget { ...@@ -117,8 +118,8 @@ class SingleChildScrollView extends StatelessWidget {
AxisDirection _getDirection(BuildContext context) { AxisDirection _getDirection(BuildContext context) {
switch (scrollDirection) { switch (scrollDirection) {
case Axis.horizontal: case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context); final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection); final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection; return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical: case Axis.vertical:
......
...@@ -202,6 +202,7 @@ class Table extends RenderObjectWidget { ...@@ -202,6 +202,7 @@ class Table extends RenderObjectWidget {
@override @override
RenderTable createRenderObject(BuildContext context) { RenderTable createRenderObject(BuildContext context) {
assert(debugCheckHasDirectionality(context));
return new RenderTable( return new RenderTable(
columns: children.isNotEmpty ? children[0].children.length : 0, columns: children.isNotEmpty ? children[0].children.length : 0,
rows: children.length, rows: children.length,
...@@ -218,6 +219,7 @@ class Table extends RenderObjectWidget { ...@@ -218,6 +219,7 @@ class Table extends RenderObjectWidget {
@override @override
void updateRenderObject(BuildContext context, RenderTable renderObject) { void updateRenderObject(BuildContext context, RenderTable renderObject) {
assert(debugCheckHasDirectionality(context));
assert(renderObject.columns == (children.isNotEmpty ? children[0].children.length : 0)); assert(renderObject.columns == (children.isNotEmpty ? children[0].children.length : 0));
assert(renderObject.rows == children.length); assert(renderObject.rows == children.length);
renderObject renderObject
......
...@@ -34,4 +34,11 @@ void main() { ...@@ -34,4 +34,11 @@ void main() {
expect(text, isNotNull); expect(text, isNotNull);
expect(text.textScaleFactor, 3.0); expect(text.textScaleFactor, 3.0);
}); });
testWidgets('Text throws a nice error message if there\'s no Directionality', (WidgetTester tester) async {
await tester.pumpWidget(const Text('Hello'));
final String message = tester.takeException().toString();
expect(message, contains('Directionality'));
expect(message, contains(' Text '));
});
} }
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