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;
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
const double _kOffset = 40.0; // distance to bottom of banner, at a 45 degree angle inwards
......@@ -294,6 +295,7 @@ class Banner extends StatelessWidget {
@override
Widget build(BuildContext context) {
assert((textDirection != null && layoutDirection != null) || debugCheckHasDirectionality(context));
return new CustomPaint(
foregroundPainter: new BannerPainter(
message: message,
......
......@@ -3792,11 +3792,10 @@ class RichText extends LeafRenderObjectWidget {
@override
RenderParagraph createRenderObject(BuildContext context) {
final TextDirection direction = textDirection ?? Directionality.of(context);
assert(direction != null, 'A RichText was created with no textDirection and no ambient Directionality widget.');
assert(textDirection != null || debugCheckHasDirectionality(context));
return new RenderParagraph(text,
textAlign: textAlign,
textDirection: direction,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
......@@ -3806,6 +3805,7 @@ class RichText extends LeafRenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
assert(textDirection != null || debugCheckHasDirectionality(context));
renderObject
..text = text
..textAlign = textAlign
......
......@@ -7,6 +7,7 @@ import 'dart:developer' show Timeline; // to disambiguate reference in dartdocs
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'framework.dart';
import 'media_query.dart';
import 'table.dart';
......@@ -202,6 +203,43 @@ bool debugCheckHasMediaQuery(BuildContext context) {
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.
///
/// Used when the given `widget` calls a builder function to check that the
......
......@@ -5,6 +5,7 @@
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'icon_data.dart';
import 'icon_theme.dart';
......@@ -84,8 +85,8 @@ class Icon extends StatelessWidget {
@override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null, 'Icon widgets required an ambient Directionality.');
final IconThemeData iconTheme = IconTheme.of(context);
......
......@@ -8,6 +8,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'basic.dart';
import 'container.dart';
import 'debug.dart';
import 'framework.dart';
import 'text.dart';
import 'ticker_provider.dart';
......@@ -486,7 +487,8 @@ class _AnimatedContainerState extends AnimatedWidgetBaseState<AnimatedContainer>
/// See also:
///
/// * [AnimatedPositionedDirectional], which adapts to the ambient
/// [Directionality].
/// [Directionality] (the same as this widget, but for animating
/// [PositionedDirectional]).
class AnimatedPositioned extends ImplicitlyAnimatedWidget {
/// Creates a widget that animates its position implicitly.
///
......@@ -545,14 +547,14 @@ class AnimatedPositioned extends ImplicitlyAnimatedWidget {
/// The child's width.
///
/// Only two out of the three horizontal values (left, right, width) can be
/// set. The third must be null.
/// Only two out of the three horizontal values ([left], [right], [width]) can
/// be set. The third must be null.
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.
/// Only two out of the three vertical values ([top], [bottom], [height]) can
/// be set. The third must be null.
final double height;
@override
......@@ -597,7 +599,7 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione
right: _right?.evaluate(animation),
bottom: _bottom?.evaluate(animation),
width: _width?.evaluate(animation),
height: _height?.evaluate(animation)
height: _height?.evaluate(animation),
);
}
......@@ -613,21 +615,25 @@ class _AnimatedPositionedState extends AnimatedWidgetBaseState<AnimatedPositione
}
}
/// Animated version of [PositionedDirectional] which automatically transitions the child's
/// position over a given duration whenever the given position changes.
/// Animated version of [PositionedDirectional] which automatically transitions
/// 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].
///
/// 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 {
/// Creates a widget that animates its position implicitly.
///
/// Only two out of the three horizontal values ([start], [end],
/// [width]), and only two out of the three vertical values ([top],
/// [bottom], [height]), can be set. In each case, at least one of
/// the three must be null.
/// Only two out of the three horizontal values ([start], [end], [width]), and
/// only two out of the three vertical values ([top], [bottom], [height]), can
/// be set. In each case, at least one of the three must be null.
///
/// The [curve] and [duration] arguments must not be null.
const AnimatedPositionedDirectional({
......@@ -662,14 +668,14 @@ class AnimatedPositionedDirectional extends ImplicitlyAnimatedWidget {
/// The child's width.
///
/// Only two out of the three horizontal values (start, end, width) can be
/// set. The third must be null.
/// Only two out of the three horizontal values ([start], [end], [width]) can
/// be set. The third must be null.
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.
/// Only two out of the three vertical values ([top], [bottom], [height]) can
/// be set. The third must be null.
final double height;
@override
......@@ -707,14 +713,16 @@ class _AnimatedPositionedDirectionalState extends AnimatedWidgetBaseState<Animat
@override
Widget build(BuildContext context) {
return new PositionedDirectional(
assert(debugCheckHasDirectionality(context));
return new Positioned.directional(
textDirection: Directionality.of(context),
child: widget.child,
start: _start?.evaluate(animation),
top: _top?.evaluate(animation),
end: _end?.evaluate(animation),
bottom: _bottom?.evaluate(animation),
width: _width?.evaluate(animation),
height: _height?.evaluate(animation)
height: _height?.evaluate(animation),
);
}
......
......@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
/// [NavigationToolbar] is a layout helper to position 3 widgets or groups of
......@@ -60,6 +61,7 @@ class NavigationToolbar extends StatelessWidget {
@override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
final List<Widget> children = <Widget>[];
if (leading != null)
......@@ -72,7 +74,6 @@ class NavigationToolbar extends StatelessWidget {
children.add(new LayoutId(id: _ToolbarSlot.trailing, child: trailing));
final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
return new CustomMultiChildLayout(
delegate: new _ToolbarLayout(
centerMiddle: centerMiddle,
......
......@@ -10,6 +10,7 @@ import 'package:flutter/physics.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'notification_listener.dart';
import 'page_storage.dart';
......@@ -451,8 +452,8 @@ class _PageViewState extends State<PageView> {
AxisDirection _getDirection(BuildContext context) {
switch (widget.scrollDirection) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return widget.reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
......
......@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
......@@ -180,8 +181,8 @@ abstract class ScrollView extends StatelessWidget {
AxisDirection getDirection(BuildContext context) {
switch (scrollDirection) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
......
......@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'debug.dart';
import 'framework.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
......@@ -117,8 +118,8 @@ class SingleChildScrollView extends StatelessWidget {
AxisDirection _getDirection(BuildContext context) {
switch (scrollDirection) {
case Axis.horizontal:
assert(debugCheckHasDirectionality(context));
final TextDirection textDirection = Directionality.of(context);
assert(textDirection != null);
final AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
......
......@@ -202,6 +202,7 @@ class Table extends RenderObjectWidget {
@override
RenderTable createRenderObject(BuildContext context) {
assert(debugCheckHasDirectionality(context));
return new RenderTable(
columns: children.isNotEmpty ? children[0].children.length : 0,
rows: children.length,
......@@ -218,6 +219,7 @@ class Table extends RenderObjectWidget {
@override
void updateRenderObject(BuildContext context, RenderTable renderObject) {
assert(debugCheckHasDirectionality(context));
assert(renderObject.columns == (children.isNotEmpty ? children[0].children.length : 0));
assert(renderObject.rows == children.length);
renderObject
......
......@@ -34,4 +34,11 @@ void main() {
expect(text, isNotNull);
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