From ca7d2d23cf601d42c1e1354355116c0c96f7fdb2 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Thu, 7 Sep 2017 16:57:38 -0700 Subject: [PATCH] TextPainter RTL (#11888) --- bin/internal/engine.version | 2 +- examples/hello_world/README.md | 7 + examples/hello_world/lib/arabic.dart | 7 + examples/hello_world/lib/main.dart | 2 +- examples/layers/lib/main.dart | 2 +- examples/layers/raw/hello_world.dart | 4 +- examples/layers/raw/text.dart | 8 +- examples/layers/rendering/flex_layout.dart | 25 +- examples/layers/rendering/hello_world.dart | 9 +- examples/layers/rendering/touch_input.dart | 7 +- .../layers/test/smoketests/lib/main_test.dart | 13 + .../test/smoketests/raw/canvas_test.dart | 13 + .../test/smoketests/raw/hello_world_test.dart | 13 + .../smoketests/raw/spinning_square_test.dart | 13 + .../layers/test/smoketests/raw/text_test.dart | 13 + .../test/smoketests/raw/touch_input_test.dart | 13 + .../custom_coordinate_systems_test.dart | 13 + .../rendering/flex_layout_test.dart | 13 + .../rendering/hello_world_test.dart | 13 + .../rendering/spinning_square_test.dart | 13 + .../rendering/touch_input_test.dart | 13 + .../smoketests/services/isolate_test.dart | 13 + .../smoketests/services/lifecycle_test.dart | 13 + .../widgets/custom_render_box_test.dart | 13 + .../smoketests/widgets/gestures_test.dart | 13 + .../smoketests/widgets/hello_world_test.dart | 13 + .../smoketests/widgets/media_query_test.dart | 13 + .../test/smoketests/widgets/sectors_test.dart | 13 + .../widgets/spinning_mixed_test.dart | 13 + .../widgets/spinning_square_test.dart | 13 + .../smoketests/widgets/styled_text_test.dart | 13 + examples/layers/widgets/hello_world.dart | 2 +- examples/layers/widgets/spinning_mixed.dart | 69 +-- packages/flutter/lib/foundation.dart | 1 + .../flutter/lib/src/foundation/unicode.dart | 98 ++++ packages/flutter/lib/src/material/slider.dart | 42 +- .../flutter/lib/src/material/text_field.dart | 9 +- .../flutter/lib/src/material/time_picker.dart | 3 +- .../lib/src/painting/flutter_logo.dart | 7 +- .../lib/src/painting/text_painter.dart | 97 +++- .../flutter/lib/src/painting/text_style.dart | 4 +- .../flutter/lib/src/rendering/editable.dart | 45 +- packages/flutter/lib/src/rendering/flex.dart | 7 +- .../flutter/lib/src/rendering/object.dart | 6 +- .../flutter/lib/src/rendering/paragraph.dart | 69 ++- .../flutter/lib/src/rendering/proxy_box.dart | 21 +- .../flutter/lib/src/rendering/semantics.dart | 51 +- .../lib/src/rendering/sliver_grid.dart | 3 +- packages/flutter/lib/src/widgets/banner.dart | 34 +- packages/flutter/lib/src/widgets/basic.dart | 51 +- .../lib/src/widgets/editable_text.dart | 40 +- packages/flutter/lib/src/widgets/icon.dart | 20 +- .../lib/src/widgets/semantics_debugger.dart | 32 +- packages/flutter/lib/src/widgets/text.dart | 19 +- .../lib/src/widgets/widget_inspector.dart | 17 +- .../flutter/test/cupertino/button_test.dart | 25 +- .../flutter/test/cupertino/dialog_test.dart | 19 +- .../flutter/test/material/buttons_test.dart | 54 +- .../test/material/circle_avatar_test.dart | 17 +- .../test/material/expand_icon_test.dart | 25 +- .../flutter/test/material/feedback_test.dart | 2 +- .../material/floating_action_button_test.dart | 18 +- .../test/material/grid_title_test.dart | 7 +- .../test/material/icon_button_test.dart | 37 +- .../test/material/refresh_indicator_test.dart | 45 +- .../test/material/text_field_test.dart | 6 +- .../flutter/test/material/tooltip_test.dart | 481 +++++++++--------- .../test/painting/text_painter_test.dart | 26 +- .../test/painting/text_style_test.dart | 14 +- packages/flutter/test/rendering/box_test.dart | 4 +- .../flutter/test/rendering/editable_test.dart | 6 +- .../flutter/test/rendering/flex_test.dart | 4 +- .../flutter/test/rendering/overflow_test.dart | 12 +- .../rendering/paragraph_intrinsics_test.dart | 5 +- .../test/rendering/paragraph_test.dart | 33 +- .../semantics_and_children_test.dart | 7 +- .../flutter/test/rendering/slivers_test.dart | 4 +- .../flutter/test/rendering/wrap_test.dart | 4 +- .../test/widgets/animated_container_test.dart | 6 +- .../widgets/animated_cross_fade_test.dart | 12 +- .../test/widgets/animated_list_test.dart | 2 +- packages/flutter/test/widgets/async_test.dart | 4 +- .../flutter/test/widgets/banner_test.dart | 12 +- .../flutter/test/widgets/baseline_test.dart | 4 +- .../test/widgets/clamp_overscrolls_test.dart | 4 +- .../test/widgets/default_text_style_test.dart | 4 +- .../test/widgets/dismissible_test.dart | 104 ++-- .../widgets/dispose_ancestor_lookup_test.dart | 2 +- packages/flutter/test/widgets/flex_test.dart | 6 +- packages/flutter/test/widgets/flow_test.dart | 2 +- packages/flutter/test/widgets/focus_test.dart | 2 +- packages/flutter/test/widgets/form_test.dart | 79 +-- .../test/widgets/global_keys_moving_test.dart | 2 +- .../test/widgets/hit_testing_test.dart | 2 +- .../flutter/test/widgets/hyperlink_test.dart | 1 + packages/flutter/test/widgets/icon_test.dart | 77 ++- .../independent_widget_layout_test.dart | 2 +- .../flutter/test/widgets/inherited_test.dart | 12 +- .../test/widgets/list_view_builder_test.dart | 6 +- .../widgets/list_view_horizontal_test.dart | 2 +- .../test/widgets/list_view_misc_test.dart | 2 +- .../flutter/test/widgets/list_view_test.dart | 4 +- .../widgets/list_view_viewporting_test.dart | 10 +- .../list_view_with_inherited_test.dart | 2 +- .../flutter/test/widgets/listener_test.dart | 2 +- .../test/widgets/localizations_test.dart | 2 +- .../test/widgets/modal_barrier_test.dart | 2 +- .../flutter/test/widgets/overlay_test.dart | 8 +- .../test/widgets/pageable_list_test.dart | 12 +- .../widgets/reparent_state_harder_test.dart | 2 +- .../flutter/test/widgets/run_app_test.dart | 4 +- .../widgets/scrollable_animations_test.dart | 6 +- .../widgets/scrollable_semantics_test.dart | 2 +- .../test/widgets/semantics_10_test.dart | 58 ++- .../test/widgets/semantics_11_test.dart | 1 + .../test/widgets/semantics_1_test.dart | 17 +- .../test/widgets/semantics_2_test.dart | 16 +- .../test/widgets/semantics_3_test.dart | 10 +- .../test/widgets/semantics_4_test.dart | 21 +- .../test/widgets/semantics_5_test.dart | 1 + .../test/widgets/semantics_7_test.dart | 104 ++-- .../test/widgets/semantics_8_test.dart | 10 +- .../test/widgets/semantics_9_test.dart | 11 +- .../test/widgets/semantics_debugger_test.dart | 65 ++- .../flutter/test/widgets/semantics_test.dart | 102 +++- .../test/widgets/semantics_tester.dart | 106 ++-- .../test/widgets/set_state_1_test.dart | 4 +- .../test/widgets/set_state_2_test.dart | 2 +- .../test/widgets/set_state_3_test.dart | 2 +- .../test/widgets/set_state_4_test.dart | 2 +- .../widgets/sliver_fill_viewport_test.dart | 7 +- .../sliver_prototype_item_extent_test.dart | 2 +- .../test/widgets/sliver_semantics_test.dart | 121 +++-- .../slivers_block_global_key_test.dart | 2 +- packages/flutter/test/widgets/stack_test.dart | 4 +- .../flutter/test/widgets/syncing_test.dart | 4 +- packages/flutter/test/widgets/table_test.dart | 119 +++-- packages/flutter/test/widgets/text_test.dart | 6 +- .../tracking_scroll_controller_test.dart | 24 +- .../test/widgets/transitions_test.dart | 6 +- .../test/widgets/widget_inspector_test.dart | 18 +- packages/flutter/test/widgets/wrap_test.dart | 2 +- packages/flutter_test/lib/src/binding.dart | 11 +- .../flutter_test/test/widget_tester_test.dart | 31 +- 144 files changed, 2223 insertions(+), 1019 deletions(-) create mode 100644 examples/hello_world/README.md create mode 100644 examples/hello_world/lib/arabic.dart create mode 100644 examples/layers/test/smoketests/lib/main_test.dart create mode 100644 examples/layers/test/smoketests/raw/canvas_test.dart create mode 100644 examples/layers/test/smoketests/raw/hello_world_test.dart create mode 100644 examples/layers/test/smoketests/raw/spinning_square_test.dart create mode 100644 examples/layers/test/smoketests/raw/text_test.dart create mode 100644 examples/layers/test/smoketests/raw/touch_input_test.dart create mode 100644 examples/layers/test/smoketests/rendering/custom_coordinate_systems_test.dart create mode 100644 examples/layers/test/smoketests/rendering/flex_layout_test.dart create mode 100644 examples/layers/test/smoketests/rendering/hello_world_test.dart create mode 100644 examples/layers/test/smoketests/rendering/spinning_square_test.dart create mode 100644 examples/layers/test/smoketests/rendering/touch_input_test.dart create mode 100644 examples/layers/test/smoketests/services/isolate_test.dart create mode 100644 examples/layers/test/smoketests/services/lifecycle_test.dart create mode 100644 examples/layers/test/smoketests/widgets/custom_render_box_test.dart create mode 100644 examples/layers/test/smoketests/widgets/gestures_test.dart create mode 100644 examples/layers/test/smoketests/widgets/hello_world_test.dart create mode 100644 examples/layers/test/smoketests/widgets/media_query_test.dart create mode 100644 examples/layers/test/smoketests/widgets/sectors_test.dart create mode 100644 examples/layers/test/smoketests/widgets/spinning_mixed_test.dart create mode 100644 examples/layers/test/smoketests/widgets/spinning_square_test.dart create mode 100644 examples/layers/test/smoketests/widgets/styled_text_test.dart create mode 100644 packages/flutter/lib/src/foundation/unicode.dart diff --git a/bin/internal/engine.version b/bin/internal/engine.version index a0483e3f78..7c8fb53a3c 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -ccf68cdcb66f9fe354bf3e8472bb0b47c83e8ac9 +2d7c30033d76fda9779462827ad5322d78e1fb3a diff --git a/examples/hello_world/README.md b/examples/hello_world/README.md new file mode 100644 index 0000000000..67155176d7 --- /dev/null +++ b/examples/hello_world/README.md @@ -0,0 +1,7 @@ +``` +# To run the Hello World demo: +flutter run + +# To run the Hello World demo showing Arabic: +flutter run lib/arabic.dart +``` diff --git a/examples/hello_world/lib/arabic.dart b/examples/hello_world/lib/arabic.dart new file mode 100644 index 0000000000..d26ee5af62 --- /dev/null +++ b/examples/hello_world/lib/arabic.dart @@ -0,0 +1,7 @@ +// Copyright 2015 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'; + +void main() => runApp(const Center(child: const Text('برنامج أهلا بالعالم', textDirection: TextDirection.rtl))); diff --git a/examples/hello_world/lib/main.dart b/examples/hello_world/lib/main.dart index 1ad8675054..93f7f5b821 100644 --- a/examples/hello_world/lib/main.dart +++ b/examples/hello_world/lib/main.dart @@ -4,4 +4,4 @@ import 'package:flutter/widgets.dart'; -void main() => runApp(const Center(child: const Text('Hello, world!'))); +void main() => runApp(const Center(child: const Text('Hello, world!', textDirection: TextDirection.ltr))); diff --git a/examples/layers/lib/main.dart b/examples/layers/lib/main.dart index 864e8f1d28..e1e068d1e2 100644 --- a/examples/layers/lib/main.dart +++ b/examples/layers/lib/main.dart @@ -4,4 +4,4 @@ import 'package:flutter/widgets.dart'; -void main() => runApp(const Center(child: const Text('flutter run -t xxx/yyy.dart'))); +void main() => runApp(const Center(child: const Text('flutter run -t xxx/yyy.dart', textDirection: TextDirection.ltr))); diff --git a/examples/layers/raw/hello_world.dart b/examples/layers/raw/hello_world.dart index b2f67f3a00..83d3bb577e 100644 --- a/examples/layers/raw/hello_world.dart +++ b/examples/layers/raw/hello_world.dart @@ -11,7 +11,9 @@ void beginFrame(Duration timeStamp) { final double devicePixelRatio = ui.window.devicePixelRatio; final ui.Size logicalSize = ui.window.physicalSize / devicePixelRatio; - final ui.ParagraphBuilder paragraphBuilder = new ui.ParagraphBuilder(new ui.ParagraphStyle()) + final ui.ParagraphBuilder paragraphBuilder = new ui.ParagraphBuilder( + new ui.ParagraphStyle(textDirection: ui.TextDirection.ltr), + ) ..addText('Hello, world.'); final ui.Paragraph paragraph = paragraphBuilder.build() ..layout(new ui.ParagraphConstraints(width: logicalSize.width)); diff --git a/examples/layers/raw/text.dart b/examples/layers/raw/text.dart index 3c3e92f5eb..15029c7cfe 100644 --- a/examples/layers/raw/text.dart +++ b/examples/layers/raw/text.dart @@ -52,7 +52,13 @@ void beginFrame(Duration timeStamp) { void main() { // To create a paragraph of text, we use ParagraphBuilder. - final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(new ui.ParagraphStyle()) + final ui.ParagraphBuilder builder = new ui.ParagraphBuilder( + // The text below has a primary direction of left-to-right. + // The embedded text has other directions. + // If this was TextDirection.rtl, the "Hello, world" text would end up on + // the other side of the right-to-left text. + new ui.ParagraphStyle(textDirection: ui.TextDirection.ltr), + ) // We first push a style that turns the text blue. ..pushStyle(new ui.TextStyle(color: const ui.Color(0xFF0000FF))) ..addText('Hello, ') diff --git a/examples/layers/rendering/flex_layout.dart b/examples/layers/rendering/flex_layout.dart index dc38d85712..0084ebae84 100644 --- a/examples/layers/rendering/flex_layout.dart +++ b/examples/layers/rendering/flex_layout.dart @@ -14,24 +14,36 @@ void main() { void addAlignmentRow(CrossAxisAlignment crossAxisAlignment) { TextStyle style = const TextStyle(color: const Color(0xFF000000)); - final RenderParagraph paragraph = new RenderParagraph(new TextSpan(style: style, text: '$crossAxisAlignment')); + final RenderParagraph paragraph = new RenderParagraph( + new TextSpan(style: style, text: '$crossAxisAlignment'), + textDirection: TextDirection.ltr, + ); table.add(new RenderPadding(child: paragraph, padding: const EdgeInsets.only(top: 20.0))); final RenderFlex row = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic); style = const TextStyle(fontSize: 15.0, color: const Color(0xFF000000)); row.add(new RenderDecoratedBox( decoration: const BoxDecoration(color: const Color(0x7FFFCCCC)), - child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo')) + child: new RenderParagraph( + new TextSpan(style: style, text: 'foo foo foo'), + textDirection: TextDirection.ltr, + ), )); style = const TextStyle(fontSize: 10.0, color: const Color(0xFF000000)); row.add(new RenderDecoratedBox( decoration: const BoxDecoration(color: const Color(0x7FCCFFCC)), - child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo')) + child: new RenderParagraph( + new TextSpan(style: style, text: 'foo foo foo'), + textDirection: TextDirection.ltr, + ), )); final RenderFlex subrow = new RenderFlex(crossAxisAlignment: crossAxisAlignment, textBaseline: TextBaseline.alphabetic); style = const TextStyle(fontSize: 25.0, color: const Color(0xFF000000)); subrow.add(new RenderDecoratedBox( decoration: const BoxDecoration(color: const Color(0x7FCCCCFF)), - child: new RenderParagraph(new TextSpan(style: style, text: 'foo foo foo foo')) + child: new RenderParagraph( + new TextSpan(style: style, text: 'foo foo foo foo'), + textDirection: TextDirection.ltr, + ), )); subrow.add(new RenderSolidColorBox(const Color(0x7FCCFFFF), desiredSize: const Size(30.0, 40.0))); row.add(subrow); @@ -48,7 +60,10 @@ void main() { void addJustificationRow(MainAxisAlignment justify) { const TextStyle style = const TextStyle(color: const Color(0xFF000000)); - final RenderParagraph paragraph = new RenderParagraph(new TextSpan(style: style, text: '$justify')); + final RenderParagraph paragraph = new RenderParagraph( + new TextSpan(style: style, text: '$justify'), + textDirection: TextDirection.ltr, + ); table.add(new RenderPadding(child: paragraph, padding: const EdgeInsets.only(top: 20.0))); final RenderFlex row = new RenderFlex(direction: Axis.horizontal); row.add(new RenderSolidColorBox(const Color(0xFFFFCCCC), desiredSize: const Size(80.0, 60.0))); diff --git a/examples/layers/rendering/hello_world.dart b/examples/layers/rendering/hello_world.dart index 2c772d112a..69a4dc2415 100644 --- a/examples/layers/rendering/hello_world.dart +++ b/examples/layers/rendering/hello_world.dart @@ -16,7 +16,14 @@ void main() { alignment: FractionalOffset.center, // We use a RenderParagraph to display the text 'Hello, world.' without // any explicit styling. - child: new RenderParagraph(const TextSpan(text: 'Hello, world.')) + child: new RenderParagraph( + const TextSpan(text: 'Hello, world.'), + // The text is in English so we specify the text direction as + // left-to-right. If the text had been in Hebrew or Arabic, we would + // have specified right-to-left. The Flutter framework does not assume a + // particular text direction. + textDirection: TextDirection.ltr, + ), ) ); } diff --git a/examples/layers/rendering/touch_input.dart b/examples/layers/rendering/touch_input.dart index efa793b16d..891e509fea 100644 --- a/examples/layers/rendering/touch_input.dart +++ b/examples/layers/rendering/touch_input.dart @@ -104,8 +104,9 @@ void main() { final RenderParagraph paragraph = new RenderParagraph( const TextSpan( style: const TextStyle(color: Colors.black87), - text: "Touch me!" - ) + text: "Touch me!", + ), + textDirection: TextDirection.ltr, ); // A stack is a render object that layers its children on top of each other. // The bottom later is our RenderDots object, and on top of that we show the @@ -114,7 +115,7 @@ void main() { children: [ new RenderDots(), paragraph, - ] + ], ); // The "parentData" field of a render object is controlled by the render // object's parent render object. Now that we've added the paragraph as a diff --git a/examples/layers/test/smoketests/lib/main_test.dart b/examples/layers/test/smoketests/lib/main_test.dart new file mode 100644 index 0000000000..b51650666f --- /dev/null +++ b/examples/layers/test/smoketests/lib/main_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../lib/main.dart' as demo; + +void main() { + test('layers smoketest for lib/main.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/raw/canvas_test.dart b/examples/layers/test/smoketests/raw/canvas_test.dart new file mode 100644 index 0000000000..979180a5df --- /dev/null +++ b/examples/layers/test/smoketests/raw/canvas_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../raw/canvas.dart' as demo; + +void main() { + test('layers smoketest for raw/canvas.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/raw/hello_world_test.dart b/examples/layers/test/smoketests/raw/hello_world_test.dart new file mode 100644 index 0000000000..c46b114633 --- /dev/null +++ b/examples/layers/test/smoketests/raw/hello_world_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../raw/hello_world.dart' as demo; + +void main() { + test('layers smoketest for raw/hello_world.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/raw/spinning_square_test.dart b/examples/layers/test/smoketests/raw/spinning_square_test.dart new file mode 100644 index 0000000000..e289f2b79c --- /dev/null +++ b/examples/layers/test/smoketests/raw/spinning_square_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../raw/spinning_square.dart' as demo; + +void main() { + test('layers smoketest for raw/spinning_square.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/raw/text_test.dart b/examples/layers/test/smoketests/raw/text_test.dart new file mode 100644 index 0000000000..1ba1d38b47 --- /dev/null +++ b/examples/layers/test/smoketests/raw/text_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../raw/text.dart' as demo; + +void main() { + test('layers smoketest for raw/text.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/raw/touch_input_test.dart b/examples/layers/test/smoketests/raw/touch_input_test.dart new file mode 100644 index 0000000000..09065d0a48 --- /dev/null +++ b/examples/layers/test/smoketests/raw/touch_input_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../raw/touch_input.dart' as demo; + +void main() { + test('layers smoketest for raw/touch_input.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/rendering/custom_coordinate_systems_test.dart b/examples/layers/test/smoketests/rendering/custom_coordinate_systems_test.dart new file mode 100644 index 0000000000..c6bcf0ad89 --- /dev/null +++ b/examples/layers/test/smoketests/rendering/custom_coordinate_systems_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../rendering/custom_coordinate_systems.dart' as demo; + +void main() { + test('layers smoketest for rendering/custom_coordinate_systems.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/rendering/flex_layout_test.dart b/examples/layers/test/smoketests/rendering/flex_layout_test.dart new file mode 100644 index 0000000000..1d1f6fe2b4 --- /dev/null +++ b/examples/layers/test/smoketests/rendering/flex_layout_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../rendering/flex_layout.dart' as demo; + +void main() { + test('layers smoketest for rendering/flex_layout.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/rendering/hello_world_test.dart b/examples/layers/test/smoketests/rendering/hello_world_test.dart new file mode 100644 index 0000000000..df8932bcd4 --- /dev/null +++ b/examples/layers/test/smoketests/rendering/hello_world_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../rendering/hello_world.dart' as demo; + +void main() { + test('layers smoketest for rendering/hello_world.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/rendering/spinning_square_test.dart b/examples/layers/test/smoketests/rendering/spinning_square_test.dart new file mode 100644 index 0000000000..78ceb99934 --- /dev/null +++ b/examples/layers/test/smoketests/rendering/spinning_square_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../rendering/spinning_square.dart' as demo; + +void main() { + test('layers smoketest for rendering/spinning_square.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/rendering/touch_input_test.dart b/examples/layers/test/smoketests/rendering/touch_input_test.dart new file mode 100644 index 0000000000..dae629f5d9 --- /dev/null +++ b/examples/layers/test/smoketests/rendering/touch_input_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../rendering/touch_input.dart' as demo; + +void main() { + test('layers smoketest for rendering/touch_input.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/services/isolate_test.dart b/examples/layers/test/smoketests/services/isolate_test.dart new file mode 100644 index 0000000000..ed5efc1d80 --- /dev/null +++ b/examples/layers/test/smoketests/services/isolate_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../services/isolate.dart' as demo; + +void main() { + test('layers smoketest for services/isolate.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/services/lifecycle_test.dart b/examples/layers/test/smoketests/services/lifecycle_test.dart new file mode 100644 index 0000000000..fa6f4ce067 --- /dev/null +++ b/examples/layers/test/smoketests/services/lifecycle_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../services/lifecycle.dart' as demo; + +void main() { + test('layers smoketest for services/lifecycle.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/custom_render_box_test.dart b/examples/layers/test/smoketests/widgets/custom_render_box_test.dart new file mode 100644 index 0000000000..2744ebbd64 --- /dev/null +++ b/examples/layers/test/smoketests/widgets/custom_render_box_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/custom_render_box.dart' as demo; + +void main() { + test('layers smoketest for widgets/custom_render_box.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/gestures_test.dart b/examples/layers/test/smoketests/widgets/gestures_test.dart new file mode 100644 index 0000000000..44f842273d --- /dev/null +++ b/examples/layers/test/smoketests/widgets/gestures_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/gestures.dart' as demo; + +void main() { + test('layers smoketest for widgets/gestures.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/hello_world_test.dart b/examples/layers/test/smoketests/widgets/hello_world_test.dart new file mode 100644 index 0000000000..0d6cb4e441 --- /dev/null +++ b/examples/layers/test/smoketests/widgets/hello_world_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/hello_world.dart' as demo; + +void main() { + test('layers smoketest for widgets/hello_world.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/media_query_test.dart b/examples/layers/test/smoketests/widgets/media_query_test.dart new file mode 100644 index 0000000000..f7540f8aaa --- /dev/null +++ b/examples/layers/test/smoketests/widgets/media_query_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/media_query.dart' as demo; + +void main() { + test('layers smoketest for widgets/media_query.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/sectors_test.dart b/examples/layers/test/smoketests/widgets/sectors_test.dart new file mode 100644 index 0000000000..4171497984 --- /dev/null +++ b/examples/layers/test/smoketests/widgets/sectors_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/sectors.dart' as demo; + +void main() { + test('layers smoketest for widgets/sectors.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/spinning_mixed_test.dart b/examples/layers/test/smoketests/widgets/spinning_mixed_test.dart new file mode 100644 index 0000000000..6cb3bd54de --- /dev/null +++ b/examples/layers/test/smoketests/widgets/spinning_mixed_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/spinning_mixed.dart' as demo; + +void main() { + test('layers smoketest for widgets/spinning_mixed.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/spinning_square_test.dart b/examples/layers/test/smoketests/widgets/spinning_square_test.dart new file mode 100644 index 0000000000..e24858d12f --- /dev/null +++ b/examples/layers/test/smoketests/widgets/spinning_square_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/spinning_square.dart' as demo; + +void main() { + test('layers smoketest for widgets/spinning_square.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/test/smoketests/widgets/styled_text_test.dart b/examples/layers/test/smoketests/widgets/styled_text_test.dart new file mode 100644 index 0000000000..3fc1a05a31 --- /dev/null +++ b/examples/layers/test/smoketests/widgets/styled_text_test.dart @@ -0,0 +1,13 @@ +// Copyright 2017 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:test/test.dart'; + +import '../../../widgets/styled_text.dart' as demo; + +void main() { + test('layers smoketest for widgets/styled_text.dart', () { + demo.main(); + }); +} diff --git a/examples/layers/widgets/hello_world.dart b/examples/layers/widgets/hello_world.dart index 1ad8675054..93f7f5b821 100644 --- a/examples/layers/widgets/hello_world.dart +++ b/examples/layers/widgets/hello_world.dart @@ -4,4 +4,4 @@ import 'package:flutter/widgets.dart'; -void main() => runApp(const Center(child: const Text('Hello, world!'))); +void main() => runApp(const Center(child: const Text('Hello, world!', textDirection: TextDirection.ltr))); diff --git a/examples/layers/widgets/spinning_mixed.dart b/examples/layers/widgets/spinning_mixed.dart index 71ba4da553..8b2a404e22 100644 --- a/examples/layers/widgets/spinning_mixed.dart +++ b/examples/layers/widgets/spinning_mixed.dart @@ -37,40 +37,43 @@ BuildOwner owner = new BuildOwner(); void attachWidgetTreeToRenderTree(RenderProxyBox container) { element = new RenderObjectToWidgetAdapter( container: container, - child: new Container( - height: 300.0, - child: new Column( - children: [ - const Rectangle(const Color(0xFF00FFFF)), - new Material( - child: new Container( - padding: const EdgeInsets.all(10.0), - margin: const EdgeInsets.all(10.0), - child: new Row( - children: [ - new RaisedButton( - child: new Row( - children: [ - new Image.network('https://flutter.io/images/favicon.png'), - const Text('PRESS ME'), - ] + child: new Directionality( + textDirection: TextDirection.ltr, + child: new Container( + height: 300.0, + child: new Column( + children: [ + const Rectangle(const Color(0xFF00FFFF)), + new Material( + child: new Container( + padding: const EdgeInsets.all(10.0), + margin: const EdgeInsets.all(10.0), + child: new Row( + children: [ + new RaisedButton( + child: new Row( + children: [ + new Image.network('https://flutter.io/images/favicon.png'), + const Text('PRESS ME'), + ], + ), + onPressed: () { + value = value == null ? 0.1 : (value + 0.1) % 1.0; + attachWidgetTreeToRenderTree(container); + }, ), - onPressed: () { - value = value == null ? 0.1 : (value + 0.1) % 1.0; - attachWidgetTreeToRenderTree(container); - } - ), - new CircularProgressIndicator(value: value), - ], - mainAxisAlignment: MainAxisAlignment.spaceAround - ) - ) - ), - const Rectangle(const Color(0xFFFFFF00)), - ], - mainAxisAlignment: MainAxisAlignment.spaceBetween - ) - ) + new CircularProgressIndicator(value: value), + ], + mainAxisAlignment: MainAxisAlignment.spaceAround, + ), + ), + ), + const Rectangle(const Color(0xFFFFFF00)), + ], + mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + ), + ), ).attachToRenderTree(owner, element); } diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index 5d06b779eb..18c2099c36 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -46,3 +46,4 @@ export 'src/foundation/print.dart'; export 'src/foundation/profile.dart'; export 'src/foundation/serialization.dart'; export 'src/foundation/synchronous_future.dart'; +export 'src/foundation/unicode.dart'; diff --git a/packages/flutter/lib/src/foundation/unicode.dart b/packages/flutter/lib/src/foundation/unicode.dart new file mode 100644 index 0000000000..a51e907299 --- /dev/null +++ b/packages/flutter/lib/src/foundation/unicode.dart @@ -0,0 +1,98 @@ +// Copyright 2017 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. + +/// Constants for useful Unicode characters. +/// +/// Currently, these characters are all related to bidirectional text. +/// +/// See also: +/// +/// * , which describes the Unicode +/// bidirectional text algorithm. +class Unicode { + Unicode._(); + /// U+202A LEFT-TO-RIGHT EMBEDDING + /// + /// Treat the following text as embedded left-to-right. + /// + /// Use [PDF] to end the embedding. + static const String LRE = '\u202A'; + + /// U+202B RIGHT-TO-LEFT EMBEDDING + /// + /// Treat the following text as embedded right-to-left. + /// + /// Use [PDF] to end the embedding. + static const String RLE = '\u202B'; + + /// U+202C POP DIRECTIONAL FORMATTING + /// + /// End the scope of the last [LRE], [RLE], [RLO], or [LRO]. + static const String PDF = '\u202C'; + + /// U+202A LEFT-TO-RIGHT OVERRIDE + /// + /// Force following characters to be treated as strong left-to-right characters. + /// + /// For example, this causes Hebrew text to render backwards. + /// + /// Use [PDF] to end the override. + static const String LRO = '\u202D'; + + /// U+202B RIGHT-TO-LEFT OVERRIDE + /// + /// Force following characters to be treated as strong right-to-left characters. + /// + /// For example, this causes English text to render backwards. + /// + /// Use [PDF] to end the override. + static const String RLO = '\u202E'; + + /// U+2066 LEFT-TO-RIGHT ISOLATE + /// + /// Treat the following text as isolated and left-to-right. + /// + /// Use [PDI] to end the isolated scope. + static const String LRI = '\u2066'; + + /// U+2067 RIGHT-TO-LEFT ISOLATE + /// + /// Treat the following text as isolated and right-to-left. + /// + /// Use [PDI] to end the isolated scope. + static const String RLI = '\u2067'; + + /// U+2068 FIRST STRONG ISOLATE + /// + /// Treat the following text as isolated and in the direction of its first + /// strong directional character that is not inside a nested isolate. + /// + /// This essentially "autodetects" the directionality of the text. It is not + /// 100% reliable. For example, Arabic text that starts with an English quote + /// will be detected as LTR, not RTL, which will lead to the text being in a + /// weird order. + /// + /// Use [PDI] to end the isolated scope. + static const String FSI = '\u2068'; + + /// U+2069 POP DIRECTIONAL ISOLATE + /// + /// End the scope of the last [LRI], [RLI], or [FSI]. + static const String PDI = '\u2069'; + + /// U+200E LEFT-TO-RIGHT MARK + /// + /// Left-to-right zero-width character. + static const String LRM = '\u200E'; + + /// U+200F RIGHT-TO-LEFT MARK + /// + /// Right-to-left zero-width non-Arabic character. + static const String RLM = '\u200F'; + + /// U+061C ARABIC LETTER MARK + /// + /// Right-to-left zero-width Arabic character. + static const String ALM = '\u061C'; +} diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index db40342432..426f9acd7a 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -295,6 +295,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { @required TextDirection textDirection, }) : assert(value != null && value >= 0.0 && value <= 1.0), assert(textDirection != null), + _label = label, _value = value, _divisions = divisions, _activeColor = activeColor, @@ -303,7 +304,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { _textTheme = textTheme, _onChanged = onChanged, _textDirection = textDirection { - this.label = label; + _updateLabelPainter(); final GestureArenaTeam team = new GestureArenaTeam(); _drag = new HorizontalDragGestureRecognizer() ..team = team @@ -356,19 +357,7 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { if (value == _label) return; _label = value; - if (value != null) { - // TODO(abarth): Handle textScaleFactor. - // https://github.com/flutter/flutter/issues/5938 - _labelPainter - ..text = new TextSpan( - style: _textTheme.body1.copyWith(fontSize: 10.0), - text: value - ) - ..layout(); - } else { - _labelPainter.text = null; - } - markNeedsLayout(); + _updateLabelPainter(); } Color get activeColor => _activeColor; @@ -424,11 +413,29 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { TextDirection _textDirection; set textDirection(TextDirection value) { assert(value != null); - if (_textDirection == value) + if (value == _textDirection) return; _textDirection = value; - // TODO(abarth): Update _labelPainter's text direction. - markNeedsPaint(); + _updateLabelPainter(); + } + + void _updateLabelPainter() { + if (label != null) { + // TODO(abarth): Handle textScaleFactor. https://github.com/flutter/flutter/issues/5938 + _labelPainter + ..text = new TextSpan( + style: _textTheme.body1.copyWith(fontSize: 10.0), + text: label, + ) + ..textDirection = textDirection + ..layout(); + } else { + _labelPainter.text = null; + } + // Changing the textDirection can result in the layout changing, because the + // bidi algorithm might line up the glyphs differently which can result in + // different ligatures, different shapes, etc. So we always markNeedsLayout. + markNeedsLayout(); } double get _trackLength => size.width - 2.0 * _kReactionRadius; @@ -633,7 +640,6 @@ class _RenderSlider extends RenderBox implements SemanticsActionHandler { ..lineTo(center.dx + tipAttachment, center.dy + tipAttachment) ..close(); canvas.drawPath(path, primaryPaint); - _labelPainter.layout(); final Offset labelOffset = new Offset( center.dx - _labelPainter.width / 2.0, center.dy - _labelPainter.height / 2.0 diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 0f3e01310d..528cad1c7c 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -69,8 +69,8 @@ class TextField extends StatefulWidget { /// the number of lines. By default, it is 1, meaning this is a single-line /// text field. If it is not null, it must be greater than zero. /// - /// The [keyboardType], [autofocus], [obscureText], and [autocorrect] arguments - /// must not be null. + /// The [keyboardType], [textAlign], [autofocus], [obscureText], and + /// [autocorrect] arguments must not be null. const TextField({ Key key, this.controller, @@ -78,7 +78,7 @@ class TextField extends StatefulWidget { this.decoration: const InputDecoration(), this.keyboardType: TextInputType.text, this.style, - this.textAlign, + this.textAlign: TextAlign.start, this.autofocus: false, this.obscureText: false, this.autocorrect: true, @@ -87,6 +87,7 @@ class TextField extends StatefulWidget { this.onSubmitted, this.inputFormatters, }) : assert(keyboardType != null), + assert(textAlign != null), assert(autofocus != null), assert(obscureText != null), assert(autocorrect != null), @@ -125,6 +126,8 @@ class TextField extends StatefulWidget { final TextStyle style; /// How the text being edited should be aligned horizontally. + /// + /// Defaults to [TextAlign.start]. final TextAlign textAlign; /// Whether this text field should focus itself if nothing else is already diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index cca45f07ed..f1ce572244 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -356,7 +356,8 @@ List _initPainters(TextTheme textTheme, List labels) { // TODO(abarth): Handle textScaleFactor. // https://github.com/flutter/flutter/issues/5939 painters[i] = new TextPainter( - text: new TextSpan(style: style, text: label) + text: new TextSpan(style: style, text: label), + textDirection: TextDirection.ltr, )..layout(); } return painters; diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index d647613357..d8a027b941 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -247,9 +247,10 @@ class _FlutterLogoPainter extends BoxPainter { fontFamily: 'Roboto', fontSize: 100.0 * 350.0 / 247.0, // 247 is the height of the F when the fontSize is 350, assuming device pixel ratio 1.0 fontWeight: FontWeight.w300, - textBaseline: TextBaseline.alphabetic - ) - ) + textBaseline: TextBaseline.alphabetic, + ), + ), + textDirection: TextDirection.ltr, ); _textPainter.layout(); final ui.TextBox textSize = _textPainter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: kLabel.length)).single; diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 03a3ca9355..3fee2d9106 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -33,21 +33,26 @@ final String _kZeroWidthSpace = new String.fromCharCode(0x200B); class TextPainter { /// Creates a text painter that paints the given text. /// - /// The text argument is optional but [text] must be non-null before calling - /// [layout]. + /// The `text` and `textDirection` arguments are optional but [text] and + /// [textDirection] must be non-null before calling [layout]. + /// + /// The [textAlign] property must not be null. /// /// The [maxLines] property, if non-null, must be greater than zero. TextPainter({ TextSpan text, - TextAlign textAlign, + TextAlign textAlign: TextAlign.start, + TextDirection textDirection, double textScaleFactor: 1.0, int maxLines, String ellipsis, }) : assert(text == null || text.debugAssertIsValid()), + assert(textAlign != null), assert(textScaleFactor != null), assert(maxLines == null || maxLines > 0), _text = text, _textAlign = textAlign, + _textDirection = textDirection, _textScaleFactor = textScaleFactor, _maxLines = maxLines, _ellipsis = ellipsis; @@ -58,6 +63,8 @@ class TextPainter { /// The (potentially styled) text to paint. /// /// After this is set, you must call [layout] before the next call to [paint]. + /// + /// This and [textDirection] must be non-null before you call [layout]. TextSpan get text => _text; TextSpan _text; set text(TextSpan value) { @@ -74,9 +81,12 @@ class TextPainter { /// How the text should be aligned horizontally. /// /// After this is set, you must call [layout] before the next call to [paint]. + /// + /// The [textAlign] property must not be null. It defaults to [TextAlign.start]. TextAlign get textAlign => _textAlign; TextAlign _textAlign; set textAlign(TextAlign value) { + assert(value != null); if (_textAlign == value) return; _textAlign = value; @@ -84,6 +94,32 @@ class TextPainter { _needsLayout = true; } + /// The default directionality of the text. + /// + /// This controls how the [TextAlign.start], [TextAlign.end], and + /// [TextAlign.justify] values of [textAlign] are resolved. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the [text] is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// After this is set, you must call [layout] before the next call to [paint]. + /// + /// This and [text] must be non-null before you call [layout]. + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (_textDirection == value) + return; + _textDirection = value; + _paragraph = null; + _layoutTemplate = null; // Shouldn't really matter, but for strict correctness... + _needsLayout = true; + } + /// The number of font pixels for each logical pixel. /// /// For example, if the text scale factor is 1.5, text will be 50% larger than @@ -149,14 +185,20 @@ class TextPainter { ui.Paragraph _layoutTemplate; - ui.ParagraphStyle _createParagraphStyle() { + ui.ParagraphStyle _createParagraphStyle([TextDirection defaultTextDirection]) { + // The defaultTextDirection argument is used for preferredLineHeight in case + // textDirection hasn't yet been set. + assert(textAlign != null); + assert(textDirection != null || defaultTextDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.'); return _text.style?.getParagraphStyle( textAlign: textAlign, + textDirection: textDirection ?? defaultTextDirection, textScaleFactor: textScaleFactor, maxLines: _maxLines, ellipsis: _ellipsis, ) ?? new ui.ParagraphStyle( textAlign: textAlign, + textDirection: textDirection ?? defaultTextDirection, maxLines: maxLines, ellipsis: ellipsis, ); @@ -169,11 +211,17 @@ class TextPainter { /// relative a typical line of text. /// /// Obtaining this value does not require calling [layout]. + /// + /// The style of the [text] property is used to determine the font settings + /// that contribute to the [preferredLineHeight]. If [text] is null or if it + /// specifies no styles, the default [TextStyle] values are used (a 10 pixel + /// sans-serif font). double get preferredLineHeight { - assert(text != null); if (_layoutTemplate == null) { - final ui.ParagraphBuilder builder = new ui.ParagraphBuilder(_createParagraphStyle()); - if (text.style != null) + final ui.ParagraphBuilder builder = new ui.ParagraphBuilder( + _createParagraphStyle(TextDirection.rtl), + ); // direction doesn't matter, text is just a zero width space + if (text?.style != null) builder.pushStyle(text.style.getTextStyle(textScaleFactor: textScaleFactor)); builder.addText(_kZeroWidthSpace); _layoutTemplate = builder.build() @@ -272,10 +320,14 @@ class TextPainter { /// Computes the visual position of the glyphs for painting the text. /// /// The text will layout with a width that's as close to its max intrinsic - /// width as possible while still being greater than or equal to minWidth and - /// less than or equal to maxWidth. + /// width as possible while still being greater than or equal to `minWidth` and + /// less than or equal to `maxWidth`. + /// + /// The [text] and [textDirection] properties must be non-null before this is + /// called. void layout({ double minWidth: 0.0, double maxWidth: double.INFINITY }) { - assert(_text != null); + assert(text != null, 'TextPainter.text must be set to a non-null value before using the TextPainter.'); + assert(textDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.'); if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth) return; _needsLayout = false; @@ -352,15 +404,34 @@ class TextPainter { } Offset get _emptyOffset { - // TODO(abarth): Handle the directionality of the text painter itself. - switch (textAlign ?? TextAlign.left) { + assert(!_needsLayout); // implies textDirection is non-null + assert(textAlign != null); + switch (textAlign) { case TextAlign.left: - case TextAlign.justify: return Offset.zero; case TextAlign.right: return new Offset(width, 0.0); case TextAlign.center: return new Offset(width / 2.0, 0.0); + case TextAlign.justify: + case TextAlign.start: + assert(textDirection != null); + switch (textDirection) { + case TextDirection.rtl: + return new Offset(width, 0.0); + case TextDirection.ltr: + return Offset.zero; + } + return null; + case TextAlign.end: + assert(textDirection != null); + switch (textDirection) { + case TextDirection.rtl: + return Offset.zero; + case TextDirection.ltr: + return new Offset(width, 0.0); + } + return null; } return null; } diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 2c8cdf2b9a..747307dcb5 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -369,6 +369,7 @@ class TextStyle extends Diagnosticable { /// specified and non-null, must be greater than zero. ui.ParagraphStyle getParagraphStyle({ TextAlign textAlign, + TextDirection textDirection, double textScaleFactor: 1.0, String ellipsis, int maxLines, @@ -377,6 +378,7 @@ class TextStyle extends Diagnosticable { assert(maxLines == null || maxLines > 0); return new ui.ParagraphStyle( textAlign: textAlign, + textDirection: textDirection, fontWeight: fontWeight, fontStyle: fontStyle, fontFamily: fontFamily, @@ -418,7 +420,7 @@ class TextStyle extends Diagnosticable { bool operator ==(dynamic other) { if (identical(this, other)) return true; - if (other is! TextStyle) + if (other.runtimeType != runtimeType) return false; final TextStyle typedOther = other; return inherit == typedOther.inherit && diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 994e129975..3439e0132b 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -87,6 +87,10 @@ class TextSelectionPoint { class RenderEditable extends RenderBox { /// Creates a render object that implements the visual aspects of a text field. /// + /// The [textAlign] argument must not be null. It defaults to [TextAlign.start]. + /// + /// The [textDirection] argument must not be null. + /// /// If [showCursor] is not specified, then it defaults to hiding the cursor. /// /// The [maxLines] property can be set to null to remove the restriction on @@ -97,7 +101,8 @@ class RenderEditable extends RenderBox { /// ViewportOffset.zero] if you have no need for scrolling. RenderEditable({ TextSpan text, - TextAlign textAlign, + @required TextDirection textDirection, + TextAlign textAlign: TextAlign.start, Color cursorColor, ValueNotifier showCursor, int maxLines: 1, @@ -107,10 +112,17 @@ class RenderEditable extends RenderBox { @required ViewportOffset offset, this.onSelectionChanged, this.onCaretChanged, - }) : assert(maxLines == null || maxLines > 0), + }) : assert(textAlign != null), + assert(textDirection != null, 'RenderEditable created without a textDirection.'), + assert(maxLines == null || maxLines > 0), assert(textScaleFactor != null), assert(offset != null), - _textPainter = new TextPainter(text: text, textAlign: textAlign, textScaleFactor: textScaleFactor), + _textPainter = new TextPainter( + text: text, + textAlign: textAlign, + textDirection: textDirection, + textScaleFactor: textScaleFactor, + ), _cursorColor = cursorColor, _showCursor = showCursor ?? new ValueNotifier(false), _maxLines = maxLines, @@ -146,7 +158,7 @@ class RenderEditable extends RenderBox { markNeedsLayout(); } - /// The text to display + /// The text to display. TextSpan get text => _textPainter.text; final TextPainter _textPainter; set text(TextSpan value) { @@ -157,14 +169,39 @@ class RenderEditable extends RenderBox { } /// How the text should be aligned horizontally. + /// + /// This must not be null. TextAlign get textAlign => _textPainter.textAlign; set textAlign(TextAlign value) { + assert(value != null); if (_textPainter.textAlign == value) return; _textPainter.textAlign = value; markNeedsPaint(); } + /// The directionality of the text. + /// + /// This decides how the [TextAlign.start], [TextAlign.end], and + /// [TextAlign.justify] values of [textAlign] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the [text] is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// This must not be null. + TextDirection get textDirection => _textPainter.textDirection; + set textDirection(TextDirection value) { + assert(value != null); + if (_textPainter.textDirection == value) + return; + _textPainter.textDirection = value; + markNeedsTextLayout(); + } + /// The color to use when painting the cursor. Color get cursorColor => _cursorColor; Color _cursorColor; diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart index aa496144cb..3f93ad1d59 100644 --- a/packages/flutter/lib/src/rendering/flex.dart +++ b/packages/flutter/lib/src/rendering/flex.dart @@ -993,12 +993,13 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin('creator', debugCreator, defaultValue: null)); - description.add(new DiagnosticsProperty('parentData', parentData, tooltip: _debugCanParentUseSize == true ? "can use size" : null)); - description.add(new DiagnosticsProperty('constraints', constraints)); + description.add(new DiagnosticsProperty('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, ifNull: 'MISSING')); + description.add(new DiagnosticsProperty('constraints', constraints, ifNull: 'MISSING')); // don't access it via the "layer" getter since that's only valid when we don't need paint description.add(new DiagnosticsProperty('layer', _layer, defaultValue: null)); - description.add(new DiagnosticsProperty('_semantics', _semantics, defaultValue: null)); + description.add(new DiagnosticsProperty('semantics node', _semantics, defaultValue: null)); description.add(new FlagProperty( 'isBlockingSemanticsOfPreviouslyPaintedNodes', value: isBlockingSemanticsOfPreviouslyPaintedNodes, diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 14d9f614db..83b13045b7 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -32,19 +32,22 @@ const String _kEllipsis = '\u2026'; class RenderParagraph extends RenderBox { /// Creates a paragraph render object. /// - /// The [text], [overflow], [softWrap], and [textScaleFactor] arguments must - /// not be null. + /// The [text], [textAlign], [textDirection], [overflow], [softWrap], and + /// [textScaleFactor] arguments must not be null. /// /// The [maxLines] property may be null (and indeed defaults to null), but if /// it is not null, it must be greater than zero. RenderParagraph(TextSpan text, { - TextAlign textAlign, + TextAlign textAlign: TextAlign.start, + @required TextDirection textDirection, bool softWrap: true, TextOverflow overflow: TextOverflow.clip, double textScaleFactor: 1.0, int maxLines, }) : assert(text != null), assert(text.debugAssertIsValid()), + assert(textAlign != null), + assert(textDirection != null), assert(softWrap != null), assert(overflow != null), assert(textScaleFactor != null), @@ -54,6 +57,7 @@ class RenderParagraph extends RenderBox { _textPainter = new TextPainter( text: text, textAlign: textAlign, + textDirection: textDirection, textScaleFactor: textScaleFactor, maxLines: maxLines, ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null, @@ -84,12 +88,35 @@ class RenderParagraph extends RenderBox { /// How the text should be aligned horizontally. TextAlign get textAlign => _textPainter.textAlign; set textAlign(TextAlign value) { + assert(value != null); if (_textPainter.textAlign == value) return; _textPainter.textAlign = value; markNeedsPaint(); } + /// The directionality of the text. + /// + /// This decides how the [TextAlign.start], [TextAlign.end], and + /// [TextAlign.justify] values of [textAlign] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the [text] is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// This must not be null. + TextDirection get textDirection => _textPainter.textDirection; + set textDirection(TextDirection value) { + assert(value != null); + if (_textPainter.textDirection == value) + return; + _textPainter.textDirection = value; + markNeedsLayout(); + } + /// Whether the text should break at soft line breaks. /// /// If false, the glyphs in the text will be positioned as if there was @@ -148,10 +175,8 @@ class RenderParagraph extends RenderBox { } void _layoutText({ double minWidth: 0.0, double maxWidth: double.INFINITY }) { - _textPainter.layout( - minWidth: minWidth, - maxWidth: _softWrap || _overflow == TextOverflow.ellipsis ? maxWidth : double.INFINITY - ); + final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis; + _textPainter.layout(minWidth: minWidth, maxWidth: widthMatters ? maxWidth : double.INFINITY); } void _layoutTextWithConstraints(BoxConstraints constraints) { @@ -245,14 +270,24 @@ class RenderParagraph extends RenderBox { _overflowShader = null; break; case TextOverflow.fade: + assert(textDirection != null); final TextPainter fadeSizePainter = new TextPainter( text: new TextSpan(style: _textPainter.text.style, text: '\u2026'), - textScaleFactor: textScaleFactor + textDirection: textDirection, + textScaleFactor: textScaleFactor, )..layout(); if (didOverflowWidth) { - final double fadeEnd = size.width; - final double fadeStart = fadeEnd - fadeSizePainter.width; - // TODO(abarth): This shader has an LTR bias. + double fadeEnd, fadeStart; + switch (textDirection) { + case TextDirection.rtl: + fadeEnd = 0.0; + fadeStart = fadeSizePainter.width; + break; + case TextDirection.ltr: + fadeEnd = size.width; + fadeStart = fadeEnd - fadeSizePainter.width; + break; + } _overflowShader = new ui.Gradient.linear( new Offset(fadeStart, 0.0), new Offset(fadeEnd, 0.0), @@ -373,10 +408,22 @@ class RenderParagraph extends RenderBox { void _annotate(SemanticsNode node) { node.label = text.toPlainText(); + node.textDirection = textDirection; } @override List debugDescribeChildren() { return [text.toDiagnosticsNode(name: 'text', style: DiagnosticsTreeStyle.transition)]; } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add(new EnumProperty('textAlign', textAlign)); + description.add(new EnumProperty('textDirection', textDirection)); + 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('overflow', overflow)); + description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: 1.0)); + description.add(new IntProperty('maxLines', maxLines, ifNull: 'unlimited')); + } } diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 9b6d0ccad7..56492c666f 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -3114,17 +3114,21 @@ class RenderSemanticsAnnotations extends RenderProxyBox { /// Creates a render object that attaches a semantic annotation. /// /// The [container] argument must not be null. + /// + /// If the [label] is not null, the [textDirection] must also not be null. RenderSemanticsAnnotations({ RenderBox child, bool container: false, bool checked, bool selected, String label, + TextDirection textDirection, }) : assert(container != null), _container = container, _checked = checked, _selected = selected, _label = label, + _textDirection = textDirection, super(child); /// If 'container' is true, this RenderObject will introduce a new @@ -3182,11 +3186,24 @@ class RenderSemanticsAnnotations extends RenderProxyBox { markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue); } + /// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value. + /// + /// This must not be null if [label] is not null. + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (textDirection == value) + return; + final bool hadValue = textDirection != null; + _textDirection = value; + markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue); + } + @override bool get isSemanticBoundary => container; @override - SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null ? _annotate : null; + SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null || textDirection != null ? _annotate : null; void _annotate(SemanticsNode node) { if (checked != null) { @@ -3198,6 +3215,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { node.isSelected = selected; if (label != null) node.label = label; + if (textDirection != null) + node.textDirection = textDirection; } } diff --git a/packages/flutter/lib/src/rendering/semantics.dart b/packages/flutter/lib/src/rendering/semantics.dart index f1c27b630f..17e5c52048 100644 --- a/packages/flutter/lib/src/rendering/semantics.dart +++ b/packages/flutter/lib/src/rendering/semantics.dart @@ -92,16 +92,20 @@ class SemanticsData { /// Creates a semantics data object. /// /// The [flags], [actions], [label], and [Rect] arguments must not be null. + /// + /// If [label] is not empty, then [textDirection] must also not be null. const SemanticsData({ @required this.flags, @required this.actions, @required this.label, + @required this.textDirection, @required this.rect, @required this.tags, - this.transform + this.transform, }) : assert(flags != null), assert(actions != null), assert(label != null), + assert(label == '' || textDirection != null, 'A SemanticsData object with label "$label" had a null textDirection.'), assert(rect != null), assert(tags != null); @@ -112,8 +116,13 @@ class SemanticsData { final int actions; /// A textual description of this node. + /// + /// The text's reading direction is given by [textDirection]. final String label; + /// The reading direction for the text in [label]. + final TextDirection textDirection; + /// The bounding box for this node in its coordinate system. final Rect rect; @@ -149,6 +158,8 @@ class SemanticsData { } if (label.isNotEmpty) buffer.write('; "$label"'); + if (textDirection != null) + buffer.write('; $textDirection'); buffer.write(')'); return buffer.toString(); } @@ -161,13 +172,14 @@ class SemanticsData { return typedOther.flags == flags && typedOther.actions == actions && typedOther.label == label + && typedOther.textDirection == textDirection && typedOther.rect == rect && setEquals(typedOther.tags, tags) && typedOther.transform == transform; } @override - int get hashCode => hashValues(flags, actions, label, rect, tags, transform); + int get hashCode => hashValues(flags, actions, label, textDirection, rect, tags, transform); } /// A node that represents some semantic data. @@ -342,6 +354,8 @@ class SemanticsNode extends AbstractNode { set isSelected(bool value) => _setFlag(SemanticsFlags.isSelected, value); /// A textual description of this node. + /// + /// The text's reading direction is given by [textDirection]. String get label => _label; String _label = ''; set label(String value) { @@ -352,6 +366,17 @@ class SemanticsNode extends AbstractNode { } } + /// The reading direction for the text in [label]. + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + assert(value != null); + if (_textDirection != value) { + _textDirection = value; + _markDirty(); + } + } + final Set _tags = new Set(); /// Tags the [SemanticsNode] with [tag]. @@ -385,6 +410,7 @@ class SemanticsNode extends AbstractNode { if (hadInheritedMergeAllDescendantsIntoThisNode) _inheritedMergeAllDescendantsIntoThisNodeValue = true; _label = ''; + _textDirection = null; _tags.clear(); _markDirty(); } @@ -598,18 +624,31 @@ class SemanticsNode extends AbstractNode { int flags = _flags; int actions = _actions; String label = _label; + TextDirection textDirection = _textDirection; final Set tags = new Set.from(_tags); if (mergeAllDescendantsIntoThisNode) { _visitDescendants((SemanticsNode node) { flags |= node._flags; actions |= node._actions; + textDirection ??= node._textDirection; tags.addAll(node._tags); if (node.label.isNotEmpty) { + String nestedLabel = node.label; + if (textDirection != node.textDirection && node.textDirection != null) { + switch (node.textDirection) { + case TextDirection.rtl: + nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}'; + break; + case TextDirection.ltr: + nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}'; + break; + } + } if (label.isEmpty) - label = node.label; + label = nestedLabel; else - label = '$label\n${node.label}'; + label = '$label\n$nestedLabel'; } return true; }); @@ -619,6 +658,7 @@ class SemanticsNode extends AbstractNode { flags: flags, actions: actions, label: label, + textDirection: textDirection, rect: rect, transform: transform, tags: tags, @@ -650,6 +690,7 @@ class SemanticsNode extends AbstractNode { actions: data.actions, rect: data.rect, label: data.label, + textDirection: data.textDirection, transform: data.transform?.storage ?? _kIdentityTransform, children: children, ); @@ -696,6 +737,8 @@ class SemanticsNode extends AbstractNode { buffer.write('; selected'); if (label.isNotEmpty) buffer.write('; "$label"'); + if (textDirection != null) + buffer.write('; $textDirection'); buffer.write(')'); return buffer.toString(); } diff --git a/packages/flutter/lib/src/rendering/sliver_grid.dart b/packages/flutter/lib/src/rendering/sliver_grid.dart index de1f752b2e..037c67c46d 100644 --- a/packages/flutter/lib/src/rendering/sliver_grid.dart +++ b/packages/flutter/lib/src/rendering/sliver_grid.dart @@ -553,7 +553,8 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) { final SliverGridGeometry gridGeometry = layout.getGeometryForChildIndex(index); final RenderBox child = insertAndLayoutLeadingChild( - gridGeometry.getBoxConstraints(constraints)); + gridGeometry.getBoxConstraints(constraints), + ); final SliverGridParentData childParentData = child.parentData; childParentData.layoutOffset = gridGeometry.scrollOffset; childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; diff --git a/packages/flutter/lib/src/widgets/banner.dart b/packages/flutter/lib/src/widgets/banner.dart index 8a75f89a00..1d8c22ce08 100644 --- a/packages/flutter/lib/src/widgets/banner.dart +++ b/packages/flutter/lib/src/widgets/banner.dart @@ -41,13 +41,15 @@ enum BannerLocation { class BannerPainter extends CustomPainter { /// Creates a banner painter. /// - /// The [message] and [location] arguments must not be null. + /// The [message], [textDirection], and [location] arguments must not be null. BannerPainter({ @required this.message, + @required this.textDirection, @required this.location, this.color: _kColor, this.textStyle: _kTextStyle, }) : assert(message != null), + assert(textDirection != null), assert(location != null), assert(color != null), assert(textStyle != null); @@ -55,6 +57,16 @@ class BannerPainter extends CustomPainter { /// The message to show in the banner. final String message; + /// The directionality of the text. + /// + /// This is used to disambiguate how to render bidirectional text. For + /// example, if the message is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + final TextDirection textDirection; + /// Where to show the banner (e.g., the upper right corder). final BannerLocation location; @@ -82,6 +94,7 @@ class BannerPainter extends CustomPainter { _textPainter = new TextPainter( text: new TextSpan(style: textStyle, text: message), textAlign: TextAlign.center, + textDirection: textDirection, ); _prepared = true; } @@ -169,6 +182,7 @@ class Banner extends StatelessWidget { Key key, this.child, @required this.message, + this.textDirection, @required this.location, this.color: _kColor, this.textStyle: _kTextStyle, @@ -184,6 +198,18 @@ class Banner extends StatelessWidget { /// The message to show in the banner. final String message; + /// The directionality of the text. + /// + /// This is used to disambiguate how to render bidirectional text. For + /// example, if the message is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// Defaults to the ambient [Directionality], if any. + final TextDirection textDirection; + /// Where to show the banner (e.g., the upper right corder). final BannerLocation location; @@ -198,6 +224,7 @@ class Banner extends StatelessWidget { return new CustomPaint( foregroundPainter: new BannerPainter( message: message, + textDirection: textDirection ?? Directionality.of(context), location: location, color: color, textStyle: textStyle, @@ -210,6 +237,7 @@ class Banner extends StatelessWidget { void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); description.add(new StringProperty('message', message, showName: false)); + description.add(new EnumProperty('textDirection', textDirection, defaultValue: null)); description.add(new EnumProperty('location', location)); description.add(new DiagnosticsProperty('color', color, showName: false)); textStyle?.debugFillProperties(description, prefix: 'text '); @@ -236,7 +264,9 @@ class CheckedModeBanner extends StatelessWidget { result = new Banner( child: result, message: 'SLOW MODE', - location: BannerLocation.topRight); + textDirection: TextDirection.ltr, + location: BannerLocation.topRight, + ); return true; }); return result; diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 2090de2a3b..250f4b5e82 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -3704,20 +3704,25 @@ class Flow extends MultiChildRenderObjectWidget { class RichText extends LeafRenderObjectWidget { /// Creates a paragraph of rich text. /// - /// The [text], [softWrap], [overflow], nad [textScaleFactor] arguments must - /// not be null. + /// The [text], [textAlign], [softWrap], [overflow], nad [textScaleFactor] + /// arguments must not be null. /// /// The [maxLines] property may be null (and indeed defaults to null), but if /// it is not null, it must be greater than zero. + /// + /// The [textDirection], if null, defaults to the ambient [Directionality], + /// which in that case must not be null. const RichText({ Key key, @required this.text, - this.textAlign, + this.textAlign: TextAlign.start, + this.textDirection, this.softWrap: true, this.overflow: TextOverflow.clip, this.textScaleFactor: 1.0, this.maxLines, }) : assert(text != null), + assert(textAlign != null), assert(softWrap != null), assert(overflow != null), assert(textScaleFactor != null), @@ -3730,6 +3735,22 @@ class RichText extends LeafRenderObjectWidget { /// How the text should be aligned horizontally. final TextAlign textAlign; + /// The directionality of the text. + /// + /// This decides how [textAlign] values like [TextAlign.start] and + /// [TextAlign.end] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the [text] is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// Defaults to the ambient [Directionality], if any. If there is no ambient + /// [Directionality], then this must not be null. + final TextDirection textDirection; + /// Whether the text should break at soft line breaks. /// /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space. @@ -3754,8 +3775,11 @@ 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.'); return new RenderParagraph(text, textAlign: textAlign, + textDirection: direction, softWrap: softWrap, overflow: overflow, textScaleFactor: textScaleFactor, @@ -3768,6 +3792,7 @@ class RichText extends LeafRenderObjectWidget { renderObject ..text = text ..textAlign = textAlign + ..textDirection = textDirection ?? Directionality.of(context) ..softWrap = softWrap ..overflow = overflow ..textScaleFactor = textScaleFactor @@ -4289,6 +4314,7 @@ class Semantics extends SingleChildRenderObjectWidget { this.checked, this.selected, this.label, + this.textDirection, }) : assert(container != null), super(key: key, child: child); @@ -4317,8 +4343,20 @@ class Semantics extends SingleChildRenderObjectWidget { final bool selected; /// Provides a textual description of the widget. + /// + /// If a label is provided, there must either by an ambient [Directionality] + /// or an explicit [textDirection] should be provided. final String label; + /// The reading direction of the [label]. + /// + /// Defaults to the ambient [Directionality]. + final TextDirection textDirection; + + TextDirection _getTextDirection(BuildContext context) { + return textDirection ?? (label != null ? Directionality.of(context) : null); + } + @override RenderSemanticsAnnotations createRenderObject(BuildContext context) { return new RenderSemanticsAnnotations( @@ -4326,6 +4364,7 @@ class Semantics extends SingleChildRenderObjectWidget { checked: checked, selected: selected, label: label, + textDirection: _getTextDirection(context), ); } @@ -4335,7 +4374,8 @@ class Semantics extends SingleChildRenderObjectWidget { ..container = container ..checked = checked ..selected = selected - ..label = label; + ..label = label + ..textDirection = _getTextDirection(context); } @override @@ -4344,7 +4384,8 @@ class Semantics extends SingleChildRenderObjectWidget { description.add(new DiagnosticsProperty('container', container)); description.add(new DiagnosticsProperty('checked', checked, defaultValue: null)); description.add(new DiagnosticsProperty('selected', selected, defaultValue: null)); - description.add(new StringProperty('label', label)); + description.add(new StringProperty('label', label, defaultValue: '')); + description.add(new EnumProperty('textDirection', textDirection, defaultValue: null)); } } diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 6aba833efa..aba7051f04 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -144,8 +144,8 @@ class EditableText extends StatefulWidget { /// the number of lines. By default, it is 1, meaning this is a single-line /// text field. If it is not null, it must be greater than zero. /// - /// The [controller], [focusNode], [style], and [cursorColor] arguments must - /// not be null. + /// The [controller], [focusNode], [style], [cursorColor], and [textAlign] + /// arguments must not be null. EditableText({ Key key, @required this.controller, @@ -154,7 +154,8 @@ class EditableText extends StatefulWidget { this.autocorrect: true, @required this.style, @required this.cursorColor, - this.textAlign, + this.textAlign: TextAlign.start, + this.textDirection, this.textScaleFactor, this.maxLines: 1, this.autofocus: false, @@ -171,6 +172,7 @@ class EditableText extends StatefulWidget { assert(autocorrect != null), assert(style != null), assert(cursorColor != null), + assert(textAlign != null), assert(maxLines == null || maxLines > 0), assert(autofocus != null), inputFormatters = maxLines == 1 @@ -201,8 +203,25 @@ class EditableText extends StatefulWidget { final TextStyle style; /// How the text should be aligned horizontally. + /// + /// Defaults to [TextAlign.start]. final TextAlign textAlign; + /// The directionality of the text. + /// + /// This decides how [textAlign] values like [TextAlign.start] and + /// [TextAlign.end] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the text is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// Defaults to the ambient [Directionality], if any. + final TextDirection textDirection; + /// The number of font pixels for each logical pixel. /// /// For example, if the text scale factor is 1.5, text will be 50% larger than @@ -266,6 +285,7 @@ class EditableText extends StatefulWidget { description.add(new DiagnosticsProperty('autocorrect', autocorrect, defaultValue: true)); style?.debugFillProperties(description); description.add(new EnumProperty('textAlign', textAlign, defaultValue: null)); + description.add(new EnumProperty('textDirection', textDirection, defaultValue: null)); description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null)); description.add(new IntProperty('maxLines', maxLines, defaultValue: 1)); description.add(new DiagnosticsProperty('autofocus', autofocus, defaultValue: false)); @@ -575,6 +595,12 @@ class EditableTextState extends State with AutomaticKeepAliveClien updateKeepAlive(); } + TextDirection get _textDirection { + final TextDirection result = widget.textDirection ?? Directionality.of(context); + assert(result != null, '$runtimeType created without a textDirection and with no ambient Directionality.'); + return result; + } + @override Widget build(BuildContext context) { FocusScope.of(context).reparentIfNeeded(widget.focusNode); @@ -595,6 +621,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien selectionColor: widget.selectionColor, textScaleFactor: widget.textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, textAlign: widget.textAlign, + textDirection: _textDirection, obscureText: widget.obscureText, obscureShowCharacterAtIndex: _obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : null, autocorrect: widget.autocorrect, @@ -619,13 +646,15 @@ class _Editable extends LeafRenderObjectWidget { this.selectionColor, this.textScaleFactor, this.textAlign, + @required this.textDirection, this.obscureText, this.obscureShowCharacterAtIndex, this.autocorrect, this.offset, this.onSelectionChanged, this.onCaretChanged, - }) : super(key: key); + }) : assert(textDirection != null), + super(key: key); final TextEditingValue value; final TextStyle style; @@ -635,6 +664,7 @@ class _Editable extends LeafRenderObjectWidget { final Color selectionColor; final double textScaleFactor; final TextAlign textAlign; + final TextDirection textDirection; final bool obscureText; final int obscureShowCharacterAtIndex; final bool autocorrect; @@ -652,6 +682,7 @@ class _Editable extends LeafRenderObjectWidget { selectionColor: selectionColor, textScaleFactor: textScaleFactor, textAlign: textAlign, + textDirection: textDirection, selection: value.selection, offset: offset, onSelectionChanged: onSelectionChanged, @@ -669,6 +700,7 @@ class _Editable extends LeafRenderObjectWidget { ..selectionColor = selectionColor ..textScaleFactor = textScaleFactor ..textAlign = textAlign + ..textDirection = textDirection ..selection = value.selection ..offset = offset ..onSelectionChanged = onSelectionChanged diff --git a/packages/flutter/lib/src/widgets/icon.dart b/packages/flutter/lib/src/widgets/icon.dart index f92a57110a..d652c9f0a2 100644 --- a/packages/flutter/lib/src/widgets/icon.dart +++ b/packages/flutter/lib/src/widgets/icon.dart @@ -17,6 +17,10 @@ import 'icon_theme_data.dart'; /// Icons are not interactive. For an interactive icon, consider material's /// [IconButton]. /// +/// There must be an ambient [Directionality] widget when using [Icon]. +/// Typically this is introduced automatically by the [WidgetsApp] or +/// [MaterialApp]. +/// /// See also: /// /// * [IconButton], for interactive icons. @@ -80,6 +84,9 @@ class Icon extends StatelessWidget { @override Widget build(BuildContext context) { + final TextDirection textDirection = Directionality.of(context); + assert(textDirection != null, 'Icon widgets required an ambient Directionality.'); + final IconThemeData iconTheme = IconTheme.of(context); final double iconSize = size ?? iconTheme.size; @@ -98,18 +105,19 @@ class Icon extends StatelessWidget { height: iconSize, child: new Center( child: new RichText( + textDirection: textDirection, // Since we already fetched it for the assert... text: new TextSpan( text: new String.fromCharCode(icon.codePoint), style: new TextStyle( inherit: false, color: iconColor, fontSize: iconSize, - fontFamily: icon.fontFamily - ) - ) - ) - ) - ) + fontFamily: icon.fontFamily, + ), + ), + ), + ), + ), ); } diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index f6e1c2e7cc..779aeb953f 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -228,15 +228,29 @@ String _getMessage(SemanticsNode node) { if (isAdjustable) annotations.add('adjustable'); + assert(data.label != null); String message; - if (annotations.isEmpty) { - assert(data.label != null); - message = data.label; + if (data.label.isEmpty) { + message = annotations.join('; '); } else { - if (data.label.isEmpty) { - message = annotations.join('; '); + String label; + if (data.textDirection == null) { + label = '${Unicode.FSI}${data.label}${Unicode.PDI}'; + annotations.insert(0, 'MISSING TEXT DIRECTION'); } else { - message = '${data.label} (${annotations.join('; ')})'; + switch (data.textDirection) { + case TextDirection.rtl: + label = '${Unicode.RLI}${data.label}${Unicode.PDF}'; + break; + case TextDirection.ltr: + label = data.label; + break; + } + } + if (annotations.isEmpty) { + message = label; + } else { + message = '$label (${annotations.join('; ')})'; } } @@ -257,7 +271,11 @@ void _paintMessage(Canvas canvas, SemanticsNode node) { canvas.save(); canvas.clipRect(rect); final TextPainter textPainter = new TextPainter() - ..text = new TextSpan(style: _messageStyle, text: message) + ..text = new TextSpan( + style: _messageStyle, + text: message, + ) + ..textDirection = TextDirection.ltr // _getMessage always returns LTR text, even if node.label is RTL ..textAlign = TextAlign.center ..layout(maxWidth: rect.width); diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart index ca2599fa50..916f148df5 100644 --- a/packages/flutter/lib/src/widgets/text.dart +++ b/packages/flutter/lib/src/widgets/text.dart @@ -200,6 +200,7 @@ class Text extends StatelessWidget { Key key, this.style, this.textAlign, + this.textDirection, this.softWrap, this.overflow, this.textScaleFactor, @@ -220,6 +221,21 @@ class Text extends StatelessWidget { /// How the text should be aligned horizontally. final TextAlign textAlign; + /// The directionality of the text. + /// + /// This decides how [textAlign] values like [TextAlign.start] and + /// [TextAlign.end] are interpreted. + /// + /// This is also used to disambiguate how to render bidirectional text. For + /// example, if the [data] is an English phrase followed by a Hebrew phrase, + /// in a [TextDirection.ltr] context the English phrase will be on the left + /// and the Hebrew phrase to its right, while in a [TextDirection.rtl] + /// context, the English phrase will be on the right and the Hebrow phrase on + /// its left. + /// + /// Defaults to the ambient [Directionality], if any. + final TextDirection textDirection; + /// Whether the text should break at soft line breaks. /// /// If false, the glyphs in the text will be positioned as if there was unlimited horizontal space. @@ -257,7 +273,8 @@ class Text extends StatelessWidget { if (style == null || style.inherit) effectiveTextStyle = defaultTextStyle.style.merge(style); return new RichText( - textAlign: textAlign ?? defaultTextStyle.textAlign, + textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, + textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null. softWrap: softWrap ?? defaultTextStyle.softWrap, overflow: overflow ?? defaultTextStyle.overflow, textScaleFactor: textScaleFactor ?? MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0, diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 26a631400b..e9bd1f292a 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -361,12 +361,14 @@ class _InspectorOverlayRenderState { @required this.selected, @required this.candidates, @required this.tooltip, + @required this.textDirection, }); final Rect overlayRect; final _TransformedRect selected; final List<_TransformedRect> candidates; final String tooltip; + final TextDirection textDirection; @override bool operator ==(dynamic other) { @@ -447,6 +449,7 @@ class _InspectorOverlayLayer extends Layer { overlayRect: overlayRect, selected: new _TransformedRect(selected), tooltip: selected.toString(), + textDirection: TextDirection.ltr, candidates: candidates, ); @@ -497,15 +500,22 @@ class _InspectorOverlayLayer extends Layer { final double offsetFromWidget = 9.0; final double verticalOffset = (targetRect.height) / 2 + offsetFromWidget; - _paintDescription(canvas, state.tooltip, target, verticalOffset, size, targetRect); + _paintDescription(canvas, state.tooltip, state.textDirection, target, verticalOffset, size, targetRect); // TODO(jacobr): provide an option to perform a debug paint of just the // selected widget. return recorder.endRecording(); } - void _paintDescription(Canvas canvas, String message, Offset target, - double verticalOffset, Size size, Rect targetRect) { + void _paintDescription( + Canvas canvas, + String message, + TextDirection textDirection, + Offset target, + double verticalOffset, + Size size, + Rect targetRect, + ) { canvas.save(); final double maxWidth = size.width - 2 * (_kScreenEdgeMargin + _kTooltipPadding); if (_textPainter == null || _textPainter.text.text != message || _textPainterMaxWidth != maxWidth) { @@ -514,6 +524,7 @@ class _InspectorOverlayLayer extends Layer { ..maxLines = _kMaxTooltipLines ..ellipsis = '...' ..text = new TextSpan(style: _messageStyle, text: message) + ..textDirection = textDirection ..layout(maxWidth: maxWidth); } diff --git a/packages/flutter/test/cupertino/button_test.dart b/packages/flutter/test/cupertino/button_test.dart index 75f12590eb..086a248ab3 100644 --- a/packages/flutter/test/cupertino/button_test.dart +++ b/packages/flutter/test/cupertino/button_test.dart @@ -14,7 +14,7 @@ const TextStyle testStyle = const TextStyle( void main() { testWidgets('Default layout minimum size', (WidgetTester tester) async { await tester.pumpWidget( - const Center(child: const CupertinoButton( + boilerplate(child: const CupertinoButton( child: const Text('X', style: testStyle), onPressed: null, )) @@ -30,7 +30,7 @@ void main() { testWidgets('Minimum size parameter', (WidgetTester tester) async { final double minSize = 60.0; await tester.pumpWidget( - new Center(child: new CupertinoButton( + boilerplate(child: new CupertinoButton( child: const Text('X', style: testStyle), onPressed: null, minSize: minSize, @@ -46,7 +46,7 @@ void main() { testWidgets('Size grows with text', (WidgetTester tester) async { await tester.pumpWidget( - const Center(child: const CupertinoButton( + boilerplate(child: const CupertinoButton( child: const Text('XXXX', style: testStyle), onPressed: null, )) @@ -60,7 +60,7 @@ void main() { }); testWidgets('Button with background is wider', (WidgetTester tester) async { - await tester.pumpWidget(const Center(child: const CupertinoButton( + await tester.pumpWidget(boilerplate(child: const CupertinoButton( child: const Text('X', style: testStyle), onPressed: null, color: const Color(0xFFFFFFFF), @@ -74,7 +74,7 @@ void main() { }); testWidgets('Custom padding', (WidgetTester tester) async { - await tester.pumpWidget(const Center(child: const CupertinoButton( + await tester.pumpWidget(boilerplate(child: const CupertinoButton( child: const Text(' ', style: testStyle), onPressed: null, padding: const EdgeInsets.all(100.0), @@ -91,7 +91,7 @@ void main() { await tester.pumpWidget( new StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return new Center( + return boilerplate( child: new CupertinoButton( child: const Text('Tap me'), onPressed: () { @@ -115,7 +115,7 @@ void main() { }); testWidgets('Disabled button doesn\'t animate', (WidgetTester tester) async { - await tester.pumpWidget(const Center(child: const CupertinoButton( + await tester.pumpWidget(boilerplate(child: const CupertinoButton( child: const Text('Tap me'), onPressed: null, ))); @@ -126,7 +126,7 @@ void main() { }); testWidgets('pressedOpacity defaults to 0.1', (WidgetTester tester) async { - await tester.pumpWidget(new Center(child: new CupertinoButton( + await tester.pumpWidget(boilerplate(child: new CupertinoButton( child: const Text('Tap me'), onPressed: () { }, ))); @@ -146,7 +146,7 @@ void main() { testWidgets('pressedOpacity parameter', (WidgetTester tester) async { final double pressedOpacity = 0.5; - await tester.pumpWidget(new Center(child: new CupertinoButton( + await tester.pumpWidget(boilerplate(child: new CupertinoButton( pressedOpacity: pressedOpacity, child: const Text('Tap me'), onPressed: () { }, @@ -165,3 +165,10 @@ void main() { expect(opacity.opacity, pressedOpacity); }); } + +Widget boilerplate({ Widget child }) { + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center(child: child), + ); +} \ No newline at end of file diff --git a/packages/flutter/test/cupertino/dialog_test.dart b/packages/flutter/test/cupertino/dialog_test.dart index 4cfa288c02..f7ef03ac70 100644 --- a/packages/flutter/test/cupertino/dialog_test.dart +++ b/packages/flutter/test/cupertino/dialog_test.dart @@ -64,10 +64,10 @@ void main() { }); testWidgets('Dialog destructive action styles', (WidgetTester tester) async { - await tester.pumpWidget(const CupertinoDialogAction( + await tester.pumpWidget(boilerplate(const CupertinoDialogAction( isDestructiveAction: true, child: const Text('Ok'), - )); + ))); final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle)); @@ -76,10 +76,10 @@ void main() { }); testWidgets('Dialog default action styles', (WidgetTester tester) async { - await tester.pumpWidget(const CupertinoDialogAction( + await tester.pumpWidget(boilerplate(const CupertinoDialogAction( isDefaultAction: true, child: const Text('Ok'), - )); + ))); final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle)); @@ -87,11 +87,11 @@ void main() { }); testWidgets('Default and destructive style', (WidgetTester tester) async { - await tester.pumpWidget(const CupertinoDialogAction( + await tester.pumpWidget(boilerplate(const CupertinoDialogAction( isDefaultAction: true, isDestructiveAction: true, child: const Text('Ok'), - )); + ))); final DefaultTextStyle widget = tester.widget(find.byType(DefaultTextStyle)); @@ -99,3 +99,10 @@ void main() { expect(widget.style.color.red, greaterThan(widget.style.color.blue)); }); } + +Widget boilerplate(Widget child) { + return new Directionality( + textDirection: TextDirection.ltr, + child: child, + ); +} diff --git a/packages/flutter/test/material/buttons_test.dart b/packages/flutter/test/material/buttons_test.dart index 180116a850..213ad330a2 100644 --- a/packages/flutter/test/material/buttons_test.dart +++ b/packages/flutter/test/material/buttons_test.dart @@ -14,14 +14,17 @@ void main() { testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( - new Material( - child: new Center( - child: new FlatButton( - onPressed: () { }, - child: const Text('ABC') - ) - ) - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Material( + child: new Center( + child: new FlatButton( + onPressed: () { }, + child: const Text('ABC') + ), + ), + ), + ), ); expect(semantics, hasSemantics( @@ -58,9 +61,12 @@ void main() { ); await tester.pumpWidget( - new Theme( - data: new ThemeData(), - child: buttonWidget, + new Directionality( + textDirection: TextDirection.ltr, + child: new Theme( + data: new ThemeData(), + child: buttonWidget, + ), ), ); @@ -88,12 +94,15 @@ void main() { ); await tester.pumpWidget( - new Theme( - data: new ThemeData( - highlightColor: themeHighlightColor1, - splashColor: themeSplashColor1, + new Directionality( + textDirection: TextDirection.ltr, + child: new Theme( + data: new ThemeData( + highlightColor: themeHighlightColor1, + splashColor: themeSplashColor1, + ), + child: buttonWidget, ), - child: buttonWidget, ), ); @@ -108,12 +117,15 @@ void main() { final Color themeHighlightColor2 = const Color(0xFF002200); await tester.pumpWidget( - new Theme( - data: new ThemeData( - highlightColor: themeHighlightColor2, - splashColor: themeSplashColor2, + new Directionality( + textDirection: TextDirection.ltr, + child: new Theme( + data: new ThemeData( + highlightColor: themeHighlightColor2, + splashColor: themeSplashColor2, + ), + child: buttonWidget, // same widget, so does not get updated because of us ), - child: buttonWidget, // same widget, so does not get updated because of us ), ); diff --git a/packages/flutter/test/material/circle_avatar_test.dart b/packages/flutter/test/material/circle_avatar_test.dart index 1d5466ebbc..05a32f9982 100644 --- a/packages/flutter/test/material/circle_avatar_test.dart +++ b/packages/flutter/test/material/circle_avatar_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('CircleAvatar with background color', (WidgetTester tester) async { final Color backgroundColor = Colors.blue.shade400; await tester.pumpWidget( - new Center( + wrap( child: new CircleAvatar( backgroundColor: backgroundColor, radius: 50.0, @@ -33,7 +33,7 @@ void main() { testWidgets('CircleAvatar with foreground color', (WidgetTester tester) async { final Color foregroundColor = Colors.red.shade100; await tester.pumpWidget( - new Center( + wrap( child: new CircleAvatar( foregroundColor: foregroundColor, child: const Text('Z'), @@ -60,9 +60,9 @@ void main() { primaryColorBrightness: Brightness.light, ); await tester.pumpWidget( - new Theme( - data: theme, - child: const Center( + wrap( + child: new Theme( + data: theme, child: const CircleAvatar( child: const Text('Z'), ), @@ -79,3 +79,10 @@ void main() { expect(paragraph.text.style.color, equals(theme.primaryTextTheme.title.color)); }); } + +Widget wrap({ Widget child }) { + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center(child: child), + ); +} diff --git a/packages/flutter/test/material/expand_icon_test.dart b/packages/flutter/test/material/expand_icon_test.dart index e4a02b7da9..94fd279161 100644 --- a/packages/flutter/test/material/expand_icon_test.dart +++ b/packages/flutter/test/material/expand_icon_test.dart @@ -10,14 +10,12 @@ void main() { bool expanded = false; await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new ExpandIcon( onPressed: (bool isExpanded) { expanded = !expanded; } ) - ) ) ); @@ -31,12 +29,10 @@ void main() { testWidgets('ExpandIcon disabled', (WidgetTester tester) async { await tester.pumpWidget( - const Material( - child: const Center( + wrap( child: const ExpandIcon( onPressed: null ) - ) ) ); @@ -48,27 +44,23 @@ void main() { bool expanded = false; await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new ExpandIcon( isExpanded: false, onPressed: (bool isExpanded) { expanded = !expanded; } ) - ) ) ); await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new ExpandIcon( isExpanded: true, onPressed: (bool isExpanded) { expanded = !expanded; } - ) ) ) ); @@ -76,3 +68,12 @@ void main() { expect(expanded, isFalse); }); } + +Widget wrap({ Widget child }) { + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Material(child: child), + ), + ); +} diff --git a/packages/flutter/test/material/feedback_test.dart b/packages/flutter/test/material/feedback_test.dart index b8676d58e2..9977316443 100644 --- a/packages/flutter/test/material/feedback_test.dart +++ b/packages/flutter/test/material/feedback_test.dart @@ -154,7 +154,7 @@ class TestWidget extends StatelessWidget { return new GestureDetector( onTap: tapHandler(context), onLongPress: longPressHandler(context), - child: const Text('X'), + child: const Text('X', textDirection: TextDirection.ltr), ); } } diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart index e4b2b46a73..c14eac3468 100644 --- a/packages/flutter/test/material/floating_action_button_test.dart +++ b/packages/flutter/test/material/floating_action_button_test.dart @@ -6,16 +6,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('Floating Action Button control test', - (WidgetTester tester) async { + testWidgets('Floating Action Button control test', (WidgetTester tester) async { bool didPressButton = false; await tester.pumpWidget( - new Center( - child: new FloatingActionButton( - onPressed: () { - didPressButton = true; - }, - child: const Icon(Icons.add), + new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new FloatingActionButton( + onPressed: () { + didPressButton = true; + }, + child: const Icon(Icons.add), + ), ), ), ); diff --git a/packages/flutter/test/material/grid_title_test.dart b/packages/flutter/test/material/grid_title_test.dart index 5cf53a6997..2bce52632d 100644 --- a/packages/flutter/test/material/grid_title_test.dart +++ b/packages/flutter/test/material/grid_title_test.dart @@ -38,7 +38,12 @@ void main() { expect(tester.getBottomLeft(find.byKey(headerKey)).dy, lessThan(tester.getTopLeft(find.byKey(footerKey)).dy)); - await tester.pumpWidget(const GridTile(child: const Text('Simple'))); + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: const GridTile(child: const Text('Simple')), + ), + ); expect(find.text('Simple'), findsOneWidget); }); diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart index 6d7a2f57c5..7813094850 100644 --- a/packages/flutter/test/material/icon_button_test.dart +++ b/packages/flutter/test/material/icon_button_test.dart @@ -24,13 +24,11 @@ void main() { testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async { await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new IconButton( onPressed: mockOnPressedFunction, icon: const Icon(Icons.link), ), - ), ), ); @@ -43,14 +41,12 @@ void main() { testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async { await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new IconButton( iconSize: 10.0, onPressed: mockOnPressedFunction, icon: const Icon(Icons.link), ), - ), ), ); @@ -60,15 +56,13 @@ void main() { testWidgets('test icons can be small when total size is >48dp', (WidgetTester tester) async { await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new IconButton( iconSize: 10.0, padding: const EdgeInsets.all(30.0), onPressed: mockOnPressedFunction, icon: const Icon(Icons.link), ), - ), ), ); @@ -78,15 +72,13 @@ void main() { testWidgets('test default icon buttons are constrained', (WidgetTester tester) async { await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new IconButton( padding: EdgeInsets.zero, onPressed: mockOnPressedFunction, icon: const Icon(Icons.ac_unit), iconSize: 80.0, ), - ), ), ); @@ -120,14 +112,12 @@ void main() { testWidgets('test default padding', (WidgetTester tester) async { await tester.pumpWidget( - new Material( - child: new Center( + wrap( child: new IconButton( onPressed: mockOnPressedFunction, icon: const Icon(Icons.ac_unit), iconSize: 80.0, ), - ), ), ); @@ -203,15 +193,13 @@ void main() { final Color directSplashColor = const Color(0xFF00000F); final Color directHighlightColor = const Color(0xFF0000F0); - Widget buttonWidget = new Material( - child: new Center( + Widget buttonWidget = wrap( child: new IconButton( icon: const Icon(Icons.android), splashColor: directSplashColor, highlightColor: directHighlightColor, onPressed: () { /* enable the button */ }, ), - ), ); await tester.pumpWidget( @@ -236,13 +224,11 @@ void main() { final Color themeSplashColor1 = const Color(0xFF000F00); final Color themeHighlightColor1 = const Color(0xFF00FF00); - buttonWidget = new Material( - child: new Center( + buttonWidget = wrap( child: new IconButton( icon: const Icon(Icons.android), onPressed: () { /* enable the button */ }, ), - ), ); await tester.pumpWidget( @@ -285,3 +271,12 @@ void main() { await gesture.up(); }); } + +Widget wrap({ Widget child }) { + return new Directionality( + textDirection: TextDirection.ltr, + child: new Material( + child: new Center(child: child), + ), + ); +} diff --git a/packages/flutter/test/material/refresh_indicator_test.dart b/packages/flutter/test/material/refresh_indicator_test.dart index b60a67fb1a..e5b784f8ee 100644 --- a/packages/flutter/test/material/refresh_indicator_test.dart +++ b/packages/flutter/test/material/refresh_indicator_test.dart @@ -23,9 +23,8 @@ void main() { testWidgets('RefreshIndicator', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: refresh, child: new ListView( physics: const AlwaysScrollableScrollPhysics(), @@ -51,9 +50,8 @@ void main() { testWidgets('RefreshIndicator - bottom', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: refresh, child: new ListView( reverse: true, @@ -80,9 +78,8 @@ void main() { testWidgets('RefreshIndicator - top - position', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: holdRefresh, child: new ListView( physics: const AlwaysScrollableScrollPhysics(), @@ -107,9 +104,8 @@ void main() { testWidgets('RefreshIndicator - bottom - position', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: holdRefresh, child: new ListView( reverse: true, @@ -135,9 +131,8 @@ void main() { testWidgets('RefreshIndicator - no movement', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: refresh, child: new ListView( physics: const AlwaysScrollableScrollPhysics(), @@ -164,9 +159,8 @@ void main() { testWidgets('RefreshIndicator - not enough', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: refresh, child: new ListView( physics: const AlwaysScrollableScrollPhysics(), @@ -192,9 +186,8 @@ void main() { testWidgets('RefreshIndicator - show - slow', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: holdRefresh, // this one never returns child: new ListView( physics: const AlwaysScrollableScrollPhysics(), @@ -236,9 +229,8 @@ void main() { testWidgets('RefreshIndicator - show - fast', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: refresh, child: new ListView( physics: const AlwaysScrollableScrollPhysics(), @@ -281,9 +273,8 @@ void main() { testWidgets('RefreshIndicator - show - fast - twice', (WidgetTester tester) async { refreshCalled = false; await tester.pumpWidget( - new Directionality( - textDirection: TextDirection.ltr, - child: new RefreshIndicator( + new MaterialApp( + home: new RefreshIndicator( onRefresh: refresh, child: new ListView( physics: const AlwaysScrollableScrollPhysics(), diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 2cfa2d0097..f2ea7f9769 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -1260,7 +1260,7 @@ void main() { testWidgets('Cannot enter new lines onto single line TextField', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); - await tester.pumpWidget(new Material( + await tester.pumpWidget(boilerplate( child: new TextField(controller: textController, decoration: null), )); @@ -1272,7 +1272,7 @@ void main() { testWidgets('Injected formatters are chained', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); - await tester.pumpWidget(new Material( + await tester.pumpWidget(boilerplate( child: new TextField( controller: textController, decoration: null, @@ -1293,7 +1293,7 @@ void main() { testWidgets('Chained formatters are in sequence', (WidgetTester tester) async { final TextEditingController textController = new TextEditingController(); - await tester.pumpWidget(new Material( + await tester.pumpWidget(boilerplate( child: new TextField( controller: textController, decoration: null, diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index f592e81880..0ae17a64b7 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -32,34 +32,37 @@ void main() { testWidgets('Does tooltip end up in the right place - center', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 300.0, - top: 0.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 20.0, - padding: const EdgeInsets.all(5.0), - verticalOffset: 20.0, - preferBelow: false, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 300.0, + top: 0.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 20.0, + padding: const EdgeInsets.all(5.0), + verticalOffset: 20.0, + preferBelow: false, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -85,34 +88,37 @@ void main() { testWidgets('Does tooltip end up in the right place - top left', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 0.0, - top: 0.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 20.0, - padding: const EdgeInsets.all(5.0), - verticalOffset: 20.0, - preferBelow: false, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 0.0, + top: 0.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 20.0, + padding: const EdgeInsets.all(5.0), + verticalOffset: 20.0, + preferBelow: false, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -134,34 +140,37 @@ void main() { testWidgets('Does tooltip end up in the right place - center prefer above fits', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 400.0, - top: 300.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 100.0, - padding: const EdgeInsets.all(0.0), - verticalOffset: 100.0, - preferBelow: false, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 400.0, + top: 300.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 100.0, + padding: const EdgeInsets.all(0.0), + verticalOffset: 100.0, + preferBelow: false, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -185,34 +194,37 @@ void main() { testWidgets('Does tooltip end up in the right place - center prefer above does not fit', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 400.0, - top: 299.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 190.0, - padding: const EdgeInsets.all(0.0), - verticalOffset: 100.0, - preferBelow: false, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 400.0, + top: 299.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 190.0, + padding: const EdgeInsets.all(0.0), + verticalOffset: 100.0, + preferBelow: false, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -247,34 +259,37 @@ void main() { testWidgets('Does tooltip end up in the right place - center prefer below fits', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 400.0, - top: 300.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 190.0, - padding: const EdgeInsets.all(0.0), - verticalOffset: 100.0, - preferBelow: true, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 400.0, + top: 300.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 190.0, + padding: const EdgeInsets.all(0.0), + verticalOffset: 100.0, + preferBelow: true, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -297,34 +312,37 @@ void main() { testWidgets('Does tooltip end up in the right place - way off to the right', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 1600.0, - top: 300.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 10.0, - padding: const EdgeInsets.all(0.0), - verticalOffset: 10.0, - preferBelow: true, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 1600.0, + top: 300.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 10.0, + padding: const EdgeInsets.all(0.0), + verticalOffset: 10.0, + preferBelow: true, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -349,34 +367,37 @@ void main() { testWidgets('Does tooltip end up in the right place - near the edge', (WidgetTester tester) async { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 780.0, - top: 300.0, - child: new Tooltip( - key: key, - message: tooltipText, - height: 10.0, - padding: const EdgeInsets.all(0.0), - verticalOffset: 10.0, - preferBelow: true, - child: new Container( - width: 0.0, - height: 0.0 - ) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 780.0, + top: 300.0, + child: new Tooltip( + key: key, + message: tooltipText, + height: 10.0, + padding: const EdgeInsets.all(0.0), + verticalOffset: 10.0, + preferBelow: true, + child: new Container( + width: 0.0, + height: 0.0, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) @@ -438,27 +459,30 @@ void main() { final GlobalKey key = new GlobalKey(); await tester.pumpWidget( - new Overlay( - initialEntries: [ - new OverlayEntry( - builder: (BuildContext context) { - return new Stack( - children: [ - new Positioned( - left: 780.0, - top: 300.0, - child: new Tooltip( - key: key, - message: tooltipText, - child: new Container(width: 0.0, height: 0.0) - ) - ), - ] - ); - } - ), - ] - ) + new Directionality( + textDirection: TextDirection.ltr, + child: new Overlay( + initialEntries: [ + new OverlayEntry( + builder: (BuildContext context) { + return new Stack( + children: [ + new Positioned( + left: 780.0, + top: 300.0, + child: new Tooltip( + key: key, + message: tooltipText, + child: new Container(width: 0.0, height: 0.0), + ), + ), + ], + ); + }, + ), + ], + ), + ), ); expect(semantics, hasSemantics(new TestSemantics.root(label: tooltipText))); @@ -504,18 +528,19 @@ void main() { testWidgets('Haptic feedback', (WidgetTester tester) async { final FeedbackTester feedback = new FeedbackTester(); - await tester.pumpWidget(new MaterialApp( + await tester.pumpWidget( + new MaterialApp( home: new Center( - child: new Tooltip( - message: 'Foo', - child: new Container( - width: 100.0, - height: 100.0, - color: Colors.green[500], - ) - ) - ) - ) + child: new Tooltip( + message: 'Foo', + child: new Container( + width: 100.0, + height: 100.0, + color: Colors.green[500], + ), + ), + ), + ), ); await tester.longPress(find.byType(Tooltip)); diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index 3db5ea4fd9..7a86425cb5 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io' as io; import 'dart:ui' as ui; import 'package:flutter/painting.dart'; @@ -10,7 +9,8 @@ import 'package:flutter_test/flutter_test.dart'; void main() { test('TextPainter caret test', () { - final TextPainter painter = new TextPainter(); + final TextPainter painter = new TextPainter() + ..textDirection = TextDirection.ltr; String text = 'A'; painter.text = new TextSpan(text: text); @@ -27,13 +27,20 @@ void main() { painter.layout(); caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length), ui.Rect.zero); expect(caretOffset.dx, painter.width); - }, skip: io.Platform.isMacOS); // TODO(goderbauer): Disabled because of https://github.com/flutter/flutter/issues/4273 + }); test('TextPainter error test', () { - final TextPainter painter = new TextPainter(); + final TextPainter painter = new TextPainter(textDirection: TextDirection.ltr); expect(() { painter.paint(null, Offset.zero); }, throwsFlutterError); }); + test('TextPainter requires textDirection', () { + final TextPainter painter1 = new TextPainter(text: const TextSpan(text: '')); + expect(() { painter1.layout(); }, throwsAssertionError); + final TextPainter painter2 = new TextPainter(text: const TextSpan(text: ''), textDirection: TextDirection.rtl); + expect(() { painter2.layout(); }, isNot(throwsException)); + }); + test('TextPainter size test', () { final TextPainter painter = new TextPainter( text: const TextSpan( @@ -44,20 +51,27 @@ void main() { fontSize: 123.0, ), ), + textDirection: TextDirection.ltr, ); painter.layout(); expect(painter.size, const Size(123.0, 123.0)); }); test('TextPainter default text height is 14 pixels', () { - final TextPainter painter = new TextPainter(text: const TextSpan(text: 'x')); + final TextPainter painter = new TextPainter( + text: const TextSpan(text: 'x'), + textDirection: TextDirection.ltr, + ); painter.layout(); expect(painter.preferredLineHeight, 14.0); expect(painter.size, const Size(14.0, 14.0)); }); test('TextPainter sets paragraph size from root', () { - final TextPainter painter = new TextPainter(text: const TextSpan(text: 'x', style: const TextStyle(fontSize: 100.0))); + final TextPainter painter = new TextPainter( + text: const TextSpan(text: 'x', style: const TextStyle(fontSize: 100.0)), + textDirection: TextDirection.ltr, + ); painter.layout(); expect(painter.preferredLineHeight, 100.0); expect(painter.size, const Size(100.0, 100.0)); diff --git a/packages/flutter/test/painting/text_style_test.dart b/packages/flutter/test/painting/text_style_test.dart index 29d5a65b37..438c4f358b 100644 --- a/packages/flutter/test/painting/text_style_test.dart +++ b/packages/flutter/test/painting/text_style_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/painting.dart'; import 'package:test/test.dart'; void main() { - test("TextStyle control test", () { + test('TextStyle control test', () { expect( const TextStyle(inherit: false).toString(), equals('TextStyle(inherit: false, )'), @@ -117,9 +117,17 @@ void main() { final ui.ParagraphStyle ps2 = s2.getParagraphStyle(textAlign: TextAlign.center); expect(ps2, equals(new ui.ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontSize: 10.0, lineHeight: 100.0))); - expect(ps2.toString(), 'ParagraphStyle(textAlign: TextAlign.center, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 10.0, lineHeight: 100.0x, ellipsis: unspecified)'); + expect(ps2.toString(), 'ParagraphStyle(textAlign: TextAlign.center, textDirection: unspecified, fontWeight: FontWeight.w800, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 10.0, lineHeight: 100.0x, ellipsis: unspecified)'); final ui.ParagraphStyle ps5 = s5.getParagraphStyle(); expect(ps5, equals(new ui.ParagraphStyle(fontWeight: FontWeight.w700, fontSize: 12.0, lineHeight: 123.0))); - expect(ps5.toString(), 'ParagraphStyle(textAlign: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 12.0, lineHeight: 123.0x, ellipsis: unspecified)'); + expect(ps5.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: unspecified, fontWeight: FontWeight.w700, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: 12.0, lineHeight: 123.0x, ellipsis: unspecified)'); + + final ui.ParagraphStyle ps6 = const TextStyle().getParagraphStyle(textDirection: TextDirection.ltr); + expect(ps6, equals(new ui.ParagraphStyle(textDirection: TextDirection.ltr))); + expect(ps6.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.ltr, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: unspecified, lineHeight: unspecified, ellipsis: unspecified)'); + + final ui.ParagraphStyle ps7 = const TextStyle().getParagraphStyle(textDirection: TextDirection.rtl); + expect(ps7, equals(new ui.ParagraphStyle(textDirection: TextDirection.rtl))); + expect(ps7.toString(), 'ParagraphStyle(textAlign: unspecified, textDirection: TextDirection.rtl, fontWeight: unspecified, fontStyle: unspecified, maxLines: unspecified, fontFamily: unspecified, fontSize: unspecified, lineHeight: unspecified, ellipsis: unspecified)'); }); } diff --git a/packages/flutter/test/rendering/box_test.dart b/packages/flutter/test/rendering/box_test.dart index 8b2d55625f..d6b81c0c37 100644 --- a/packages/flutter/test/rendering/box_test.dart +++ b/packages/flutter/test/rendering/box_test.dart @@ -73,8 +73,8 @@ void main() { expect(coloredBox, hasAGoodToStringDeep); expect(coloredBox.toStringDeep(), equalsIgnoringHashCodes( 'RenderDecoratedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' - ' parentData: null\n' - ' constraints: null\n' + ' parentData: MISSING\n' + ' constraints: MISSING\n' ' size: MISSING\n' ' decoration: BoxDecoration:\n' ' \n' diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index 9dd3c579f3..2fd8849deb 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -12,6 +12,8 @@ void main() { style: const TextStyle(height: 1.0, fontSize: 10.0, fontFamily: 'Ahem'), text: '12345', ), + textAlign: TextAlign.start, + textDirection: TextDirection.ltr, offset: new ViewportOffset.zero(), ); expect(editable.getMinIntrinsicWidth(double.INFINITY), 50.0); @@ -23,8 +25,8 @@ void main() { editable.toStringDeep(), equalsIgnoringHashCodes( 'RenderEditable#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' - ' │ parentData: null\n' - ' │ constraints: null\n' + ' │ parentData: MISSING\n' + ' │ constraints: MISSING\n' ' │ size: MISSING\n' ' │ cursorColor: null\n' ' │ showCursor: ValueNotifier#00000(false)\n' diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart index c4423234f4..89fe5150ad 100644 --- a/packages/flutter/test/rendering/flex_test.dart +++ b/packages/flutter/test/rendering/flex_test.dart @@ -91,8 +91,8 @@ void main() { flex.toStringDeep(), equalsIgnoringHashCodes( 'RenderFlex#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' - ' parentData: null\n' - ' constraints: null\n' + ' parentData: MISSING\n' + ' constraints: MISSING\n' ' size: MISSING\n' ' direction: horizontal\n' ' mainAxisAlignment: start\n' diff --git a/packages/flutter/test/rendering/overflow_test.dart b/packages/flutter/test/rendering/overflow_test.dart index 63e03f0d63..cd0e9479ec 100644 --- a/packages/flutter/test/rendering/overflow_test.dart +++ b/packages/flutter/test/rendering/overflow_test.dart @@ -9,13 +9,16 @@ import 'package:test/test.dart'; import 'rendering_tester.dart'; void main() { - test("overflow should not affect baseline", () { + test('overflow should not affect baseline', () { RenderBox root, child, text; double baseline1, baseline2, height1, height2; root = new RenderPositionedBox( child: new RenderCustomPaint( - child: child = text = new RenderParagraph(const TextSpan(text: 'Hello World')), + child: child = text = new RenderParagraph( + const TextSpan(text: 'Hello World'), + textDirection: TextDirection.ltr, + ), painter: new TestCallbackPainter( onPaint: () { baseline1 = child.getDistanceToBaseline(TextBaseline.alphabetic); @@ -29,7 +32,10 @@ void main() { root = new RenderPositionedBox( child: new RenderCustomPaint( child: child = new RenderConstrainedOverflowBox( - child: text = new RenderParagraph(const TextSpan(text: 'Hello World')), + child: text = new RenderParagraph( + const TextSpan(text: 'Hello World'), + textDirection: TextDirection.ltr, + ), maxHeight: height1 / 2.0, alignment: const FractionalOffset(0.0, 0.0) ), diff --git a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart index cd9c6bb313..644d70990c 100644 --- a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart +++ b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart @@ -10,8 +10,9 @@ void main() { final RenderParagraph paragraph = new RenderParagraph( const TextSpan( style: const TextStyle(height: 1.0), - text: 'Hello World' - ) + text: 'Hello World', + ), + textDirection: TextDirection.ltr, ); final RenderListBody testBlock = new RenderListBody( children: [ diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart index 1b67e88a7a..380fb25315 100644 --- a/packages/flutter/test/rendering/paragraph_test.dart +++ b/packages/flutter/test/rendering/paragraph_test.dart @@ -14,7 +14,10 @@ const String _kText = 'I polished up that handle so carefullee\nThat now I am th void main() { test('getOffsetForCaret control test', () { - final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText)); + final RenderParagraph paragraph = new RenderParagraph( + const TextSpan(text: _kText), + textDirection: TextDirection.ltr, + ); layout(paragraph); final Rect caret = new Rect.fromLTWH(0.0, 0.0, 2.0, 20.0); @@ -30,7 +33,10 @@ void main() { }); test('getPositionForOffset control test', () { - final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText)); + final RenderParagraph paragraph = new RenderParagraph( + const TextSpan(text: _kText), + textDirection: TextDirection.ltr, + ); layout(paragraph); final TextPosition position20 = paragraph.getPositionForOffset(const Offset(20.0, 5.0)); @@ -44,7 +50,10 @@ void main() { }); test('getBoxesForSelection control test', () { - final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText)); + final RenderParagraph paragraph = new RenderParagraph( + const TextSpan(text: _kText), + textDirection: TextDirection.ltr, + ); layout(paragraph); List boxes = paragraph.getBoxesForSelection( @@ -61,7 +70,10 @@ void main() { }); test('getWordBoundary control test', () { - final RenderParagraph paragraph = new RenderParagraph(const TextSpan(text: _kText)); + final RenderParagraph paragraph = new RenderParagraph( + const TextSpan(text: _kText), + textDirection: TextDirection.ltr, + ); layout(paragraph); final TextRange range5 = paragraph.getWordBoundary(const TextPosition(offset: 5)); @@ -81,6 +93,7 @@ void main() { 'is a wrapping test. It should wrap at manual newlines, and if softWrap is true, also at spaces.', style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ), + textDirection: TextDirection.ltr, maxLines: 1, softWrap: true, ); @@ -157,6 +170,7 @@ void main() { // 0 1 2 3 4 5 6 7 8 9 10 11 12 style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ), + textDirection: TextDirection.ltr, ); layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0)); void layoutAt(int maxLines) { @@ -183,6 +197,7 @@ void main() { text: 'Hello', style: const TextStyle(color: const Color(0xFF000000)), ), + textDirection: TextDirection.ltr, ); layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0), phase: EnginePhase.paint); expect(paragraph.debugNeedsLayout, isFalse); @@ -210,15 +225,21 @@ void main() { test('toStringDeep', () { final RenderParagraph paragraph = new RenderParagraph( const TextSpan(text: _kText), + textDirection: TextDirection.ltr, ); expect(paragraph, hasAGoodToStringDeep); expect( paragraph.toStringDeep(), equalsIgnoringHashCodes( 'RenderParagraph#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' - ' │ parentData: null\n' - ' │ constraints: null\n' + ' │ parentData: MISSING\n' + ' │ constraints: MISSING\n' ' │ size: MISSING\n' + ' │ textAlign: start\n' + ' │ textDirection: ltr\n' + ' │ softWrap: wrapping at box width\n' + ' │ overflow: clip\n' + ' │ maxLines: unlimited\n' ' ╘═╦══ text ═══\n' ' ║ TextSpan:\n' ' ║ "I polished up that handle so carefullee\n' diff --git a/packages/flutter/test/rendering/semantics_and_children_test.dart b/packages/flutter/test/rendering/semantics_and_children_test.dart index 82ca3bb884..90afb11951 100644 --- a/packages/flutter/test/rendering/semantics_and_children_test.dart +++ b/packages/flutter/test/rendering/semantics_and_children_test.dart @@ -16,7 +16,12 @@ int countSemanticsChildren(RenderObject object) { void main() { test('RenderOpacity and children and semantics', () { - final RenderOpacity box = new RenderOpacity(child: new RenderParagraph(const TextSpan())); + final RenderOpacity box = new RenderOpacity( + child: new RenderParagraph( + const TextSpan(), + textDirection: TextDirection.ltr, + ), + ); expect(countSemanticsChildren(box), 1); box.opacity = 0.5; expect(countSemanticsChildren(box), 1); diff --git a/packages/flutter/test/rendering/slivers_test.dart b/packages/flutter/test/rendering/slivers_test.dart index 91b05dca2a..1525a5a84d 100644 --- a/packages/flutter/test/rendering/slivers_test.dart +++ b/packages/flutter/test/rendering/slivers_test.dart @@ -19,8 +19,8 @@ void main() { root.toStringDeep(), equalsIgnoringHashCodes( 'RenderViewport#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' - ' parentData: null\n' - ' constraints: null\n' + ' parentData: MISSING\n' + ' constraints: MISSING\n' ' size: MISSING\n' ' axisDirection: down\n' ' crossAxisDirection: right\n' diff --git a/packages/flutter/test/rendering/wrap_test.dart b/packages/flutter/test/rendering/wrap_test.dart index 650b6d32ea..bc0329da94 100644 --- a/packages/flutter/test/rendering/wrap_test.dart +++ b/packages/flutter/test/rendering/wrap_test.dart @@ -13,8 +13,8 @@ void main() { renderWrap.toStringDeep(), equalsIgnoringHashCodes( 'RenderWrap#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' - ' parentData: null\n' - ' constraints: null\n' + ' parentData: MISSING\n' + ' constraints: MISSING\n' ' size: MISSING\n' ' direction: horizontal\n' ' alignment: start\n' diff --git a/packages/flutter/test/widgets/animated_container_test.dart b/packages/flutter/test/widgets/animated_container_test.dart index dcd89cd53f..f0c1c805d4 100644 --- a/packages/flutter/test/widgets/animated_container_test.dart +++ b/packages/flutter/test/widgets/animated_container_test.dart @@ -230,7 +230,7 @@ void main() { duration: const Duration(milliseconds: 200), width: 100.0, height: 100.0, - child: const Text('X') + child: const Text('X', textDirection: TextDirection.ltr) ) ) ); @@ -250,7 +250,7 @@ void main() { duration: const Duration(milliseconds: 200), width: 200.0, height: 200.0, - child: const Text('X') + child: const Text('X', textDirection: TextDirection.ltr) ) ) ); @@ -274,7 +274,7 @@ void main() { duration: const Duration(milliseconds: 200), width: 200.0, height: 100.0, - child: const Text('X') + child: const Text('X', textDirection: TextDirection.ltr) ) ) ); diff --git a/packages/flutter/test/widgets/animated_cross_fade_test.dart b/packages/flutter/test/widgets/animated_cross_fade_test.dart index ed09f63bdf..7dd78247cb 100644 --- a/packages/flutter/test/widgets/animated_cross_fade_test.dart +++ b/packages/flutter/test/widgets/animated_cross_fade_test.dart @@ -302,16 +302,16 @@ void main() { testWidgets('AnimatedCrossFade.layoutBuilder', (WidgetTester tester) async { await tester.pumpWidget(const AnimatedCrossFade( - firstChild: const Text('AAA'), - secondChild: const Text('BBB'), + firstChild: const Text('AAA', textDirection: TextDirection.ltr), + secondChild: const Text('BBB', textDirection: TextDirection.ltr), crossFadeState: CrossFadeState.showFirst, duration: const Duration(milliseconds: 50), )); expect(find.text('AAA'), findsOneWidget); expect(find.text('BBB'), findsOneWidget); await tester.pumpWidget(new AnimatedCrossFade( - firstChild: const Text('AAA'), - secondChild: const Text('BBB'), + firstChild: const Text('AAA', textDirection: TextDirection.ltr), + secondChild: const Text('BBB', textDirection: TextDirection.ltr), crossFadeState: CrossFadeState.showFirst, duration: const Duration(milliseconds: 50), layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a, @@ -319,8 +319,8 @@ void main() { expect(find.text('AAA'), findsOneWidget); expect(find.text('BBB'), findsNothing); await tester.pumpWidget(new AnimatedCrossFade( - firstChild: const Text('AAA'), - secondChild: const Text('BBB'), + firstChild: const Text('AAA', textDirection: TextDirection.ltr), + secondChild: const Text('BBB', textDirection: TextDirection.ltr), crossFadeState: CrossFadeState.showSecond, duration: const Duration(milliseconds: 50), layoutBuilder: (Widget a, Key aKey, Widget b, Key bKey) => a, diff --git a/packages/flutter/test/widgets/animated_list_test.dart b/packages/flutter/test/widgets/animated_list_test.dart index 54e81672e0..08bde082c6 100644 --- a/packages/flutter/test/widgets/animated_list_test.dart +++ b/packages/flutter/test/widgets/animated_list_test.dart @@ -121,7 +121,7 @@ void main() { child: new SizedBox( height: 100.0, child: new Center( - child: new Text('item $item'), + child: new Text('item $item', textDirection: TextDirection.ltr), ), ), ); diff --git a/packages/flutter/test/widgets/async_test.dart b/packages/flutter/test/widgets/async_test.dart index ad5bade695..ff1a96fd14 100644 --- a/packages/flutter/test/widgets/async_test.dart +++ b/packages/flutter/test/widgets/async_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { Widget snapshotText(BuildContext context, AsyncSnapshot snapshot) { - return new Text(snapshot.toString()); + return new Text(snapshot.toString(), textDirection: TextDirection.ltr); } group('AsyncSnapshot', () { test('requiring data succeeds if data is present', () { @@ -280,5 +280,5 @@ class StringCollector extends StreamBuilderBase> { List afterDisconnected(List current) => current..add('disc'); @override - Widget build(BuildContext context, List currentSummary) => new Text(currentSummary.join(', ')); + Widget build(BuildContext context, List currentSummary) => new Text(currentSummary.join(', '), textDirection: TextDirection.ltr); } diff --git a/packages/flutter/test/widgets/banner_test.dart b/packages/flutter/test/widgets/banner_test.dart index c52e616fb4..0208dda9ce 100644 --- a/packages/flutter/test/widgets/banner_test.dart +++ b/packages/flutter/test/widgets/banner_test.dart @@ -19,7 +19,8 @@ class TestCanvas implements Canvas { void main() { test('A Banner with a location of topLeft paints in the top left', () { final BannerPainter bannerPainter = new BannerPainter( - message:"foo", + message: 'foo', + textDirection: TextDirection.ltr, location: BannerLocation.topLeft ); @@ -45,7 +46,8 @@ void main() { test('A Banner with a location of topRight paints in the top right', () { final BannerPainter bannerPainter = new BannerPainter( - message:"foo", + message: 'foo', + textDirection: TextDirection.ltr, location: BannerLocation.topRight ); @@ -71,7 +73,8 @@ void main() { test('A Banner with a location of bottomLeft paints in the bottom left', () { final BannerPainter bannerPainter = new BannerPainter( - message:"foo", + message: 'foo', + textDirection: TextDirection.ltr, location: BannerLocation.bottomLeft ); @@ -97,7 +100,8 @@ void main() { test('A Banner with a location of bottomRight paints in the bottom right', () { final BannerPainter bannerPainter = new BannerPainter( - message:"foo", + message: 'foo', + textDirection: TextDirection.ltr, location: BannerLocation.bottomRight ); diff --git a/packages/flutter/test/widgets/baseline_test.dart b/packages/flutter/test/widgets/baseline_test.dart index 23216b06cf..3dfdbb3039 100644 --- a/packages/flutter/test/widgets/baseline_test.dart +++ b/packages/flutter/test/widgets/baseline_test.dart @@ -14,7 +14,7 @@ void main() { fontFamily: 'Ahem', fontSize: 100.0, ), - child: const Text('X'), + child: const Text('X', textDirection: TextDirection.ltr), ), ), ); @@ -32,7 +32,7 @@ void main() { fontFamily: 'Ahem', fontSize: 100.0, ), - child: const Text('X'), + child: const Text('X', textDirection: TextDirection.ltr), ), ), ), diff --git a/packages/flutter/test/widgets/clamp_overscrolls_test.dart b/packages/flutter/test/widgets/clamp_overscrolls_test.dart index 6dffdea045..c5cc92deae 100644 --- a/packages/flutter/test/widgets/clamp_overscrolls_test.dart +++ b/packages/flutter/test/widgets/clamp_overscrolls_test.dart @@ -21,9 +21,9 @@ Widget buildFrame(ScrollPhysics physics) { crossAxisAlignment: CrossAxisAlignment.start, textDirection: TextDirection.ltr, children: [ - const SizedBox(height: 100.0, child: const Text('top')), + const SizedBox(height: 100.0, child: const Text('top', textDirection: TextDirection.ltr)), new Expanded(child: new Container()), - const SizedBox(height: 100.0, child: const Text('bottom')), + const SizedBox(height: 100.0, child: const Text('bottom', textDirection: TextDirection.ltr)), ], ), ), diff --git a/packages/flutter/test/widgets/default_text_style_test.dart b/packages/flutter/test/widgets/default_text_style_test.dart index 28da82627e..e5909c18ee 100644 --- a/packages/flutter/test/widgets/default_text_style_test.dart +++ b/packages/flutter/test/widgets/default_text_style_test.dart @@ -7,7 +7,7 @@ import 'package:flutter/widgets.dart'; void main() { testWidgets('DefaultTextStyle changes propagate to Text', (WidgetTester tester) async { - const Text textWidget = const Text('Hello'); + const Text textWidget = const Text('Hello', textDirection: TextDirection.ltr); const TextStyle s1 = const TextStyle( fontSize: 10.0, fontWeight: FontWeight.w800, @@ -16,7 +16,7 @@ void main() { await tester.pumpWidget(const DefaultTextStyle( style: s1, - child: textWidget + child: textWidget, )); RichText text = tester.firstWidget(find.byType(RichText)); diff --git a/packages/flutter/test/widgets/dismissible_test.dart b/packages/flutter/test/widgets/dismissible_test.dart index 6ebe430046..00b7e0b313 100644 --- a/packages/flutter/test/widgets/dismissible_test.dart +++ b/packages/flutter/test/widgets/dismissible_test.dart @@ -14,48 +14,51 @@ List dismissedItems = []; Widget background; Widget buildTest({ double startToEndThreshold }) { - return new StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - Widget buildDismissibleItem(int item) { - return new Dismissible( - key: new ValueKey(item), - direction: dismissDirection, - onDismissed: (DismissDirection direction) { - setState(() { - reportedDismissDirection = direction; + return new Directionality( + textDirection: TextDirection.ltr, + child: new StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + Widget buildDismissibleItem(int item) { + return new Dismissible( + key: new ValueKey(item), + direction: dismissDirection, + onDismissed: (DismissDirection direction) { + setState(() { + reportedDismissDirection = direction; + expect(dismissedItems.contains(item), isFalse); + dismissedItems.add(item); + }); + }, + onResize: () { expect(dismissedItems.contains(item), isFalse); - dismissedItems.add(item); - }); - }, - onResize: () { - expect(dismissedItems.contains(item), isFalse); - }, - background: background, - dismissThresholds: startToEndThreshold == null - ? {} - : {DismissDirection.startToEnd: startToEndThreshold}, + }, + background: background, + dismissThresholds: startToEndThreshold == null + ? {} + : {DismissDirection.startToEnd: startToEndThreshold}, + child: new Container( + width: itemExtent, + height: itemExtent, + child: new Text(item.toString()), + ), + ); + } + + return new Directionality( + textDirection: TextDirection.ltr, child: new Container( - width: itemExtent, - height: itemExtent, - child: new Text(item.toString()), + padding: const EdgeInsets.all(10.0), + child: new ListView( + scrollDirection: scrollDirection, + itemExtent: itemExtent, + children: [0, 1, 2, 3, 4] + .where((int i) => !dismissedItems.contains(i)) + .map(buildDismissibleItem).toList(), + ), ), ); - } - - return new Directionality( - textDirection: TextDirection.ltr, - child: new Container( - padding: const EdgeInsets.all(10.0), - child: new ListView( - scrollDirection: scrollDirection, - itemExtent: itemExtent, - children: [0, 1, 2, 3, 4] - .where((int i) => !dismissedItems.contains(i)) - .map(buildDismissibleItem).toList(), - ), - ), - ); - }, + }, + ), ); } @@ -287,18 +290,23 @@ void main() { // actually remove the dismissed widget, which is a violation of the // Dismissible contract. This is not an example of good practice. testWidgets('dismissing bottom then top (smoketest)', (WidgetTester tester) async { - await tester.pumpWidget(new Center( - child: new Container( - width: 100.0, - height: 1000.0, - child: new Column( - children: [ - const Test1215DismissibleWidget('1'), - const Test1215DismissibleWidget('2'), - ], + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Container( + width: 100.0, + height: 1000.0, + child: new Column( + children: [ + const Test1215DismissibleWidget('1'), + const Test1215DismissibleWidget('2'), + ], + ), + ), ), ), - )); + ); expect(find.text('1'), findsOneWidget); expect(find.text('2'), findsOneWidget); await dismissElement(tester, find.text('2'), gestureDirection: DismissDirection.startToEnd); diff --git a/packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart b/packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart index 08f4a97ebc..bb5350497c 100644 --- a/packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart +++ b/packages/flutter/test/widgets/dispose_ancestor_lookup_test.dart @@ -24,7 +24,7 @@ class TestWidgetState extends State { } @override - Widget build(BuildContext context) => const Text('test'); + Widget build(BuildContext context) => const Text('test', textDirection: TextDirection.ltr); } void main() { diff --git a/packages/flutter/test/widgets/flex_test.dart b/packages/flutter/test/widgets/flex_test.dart index 126e5efa30..f93df040c5 100644 --- a/packages/flutter/test/widgets/flex_test.dart +++ b/packages/flutter/test/widgets/flex_test.dart @@ -28,7 +28,7 @@ void main() { width: 100.0, height: 100.0, child: const Center( - child: const Text('X'), + child: const Text('X', textDirection: TextDirection.ltr), ), ), ), @@ -63,8 +63,8 @@ void main() { new Row( textDirection: TextDirection.ltr, children: [ - const Expanded(flex: null, child: const Text('one')), - const Flexible(flex: null, child: const Text('two')), + const Expanded(flex: null, child: const Text('one', textDirection: TextDirection.ltr)), + const Flexible(flex: null, child: const Text('two', textDirection: TextDirection.ltr)), ], ), ); diff --git a/packages/flutter/test/widgets/flow_test.dart b/packages/flutter/test/widgets/flow_test.dart index 237ba6345d..18991e8835 100644 --- a/packages/flutter/test/widgets/flow_test.dart +++ b/packages/flutter/test/widgets/flow_test.dart @@ -44,7 +44,7 @@ void main() { width: 100.0, height: 100.0, color: const Color(0xFF0000FF), - child: new Text('$i') + child: new Text('$i', textDirection: TextDirection.ltr) ) ); } diff --git a/packages/flutter/test/widgets/focus_test.dart b/packages/flutter/test/widgets/focus_test.dart index 282d1f20fc..874ef9032a 100644 --- a/packages/flutter/test/widgets/focus_test.dart +++ b/packages/flutter/test/widgets/focus_test.dart @@ -47,7 +47,7 @@ class TestFocusableState extends State { child: new AnimatedBuilder( animation: focusNode, builder: (BuildContext context, Widget child) { - return new Text(focusNode.hasFocus ? widget.yes : widget.no); + return new Text(focusNode.hasFocus ? widget.yes : widget.no, textDirection: TextDirection.ltr); }, ), ); diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart index 2b83611ff5..4e27812159 100644 --- a/packages/flutter/test/widgets/form_test.dart +++ b/packages/flutter/test/widgets/form_test.dart @@ -11,12 +11,15 @@ void main() { String fieldValue; Widget builder() { - return new Center( - child: new Material( - child: new Form( - key: formKey, - child: new TextFormField( - onSaved: (String value) { fieldValue = value; }, + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Material( + child: new Form( + key: formKey, + child: new TextFormField( + onSaved: (String value) { fieldValue = value; }, + ), ), ), ), @@ -42,11 +45,14 @@ void main() { String fieldValue; Widget builder() { - return new Center( - child: new Material( - child: new Form( - child: new TextField( - onChanged: (String value) { fieldValue = value; }, + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Material( + child: new Form( + child: new TextField( + onChanged: (String value) { fieldValue = value; }, + ), ), ), ), @@ -72,13 +78,16 @@ void main() { String errorText(String value) => value + '/error'; Widget builder(bool autovalidate) { - return new Center( - child: new Material( - child: new Form( - key: formKey, - autovalidate: autovalidate, - child: new TextFormField( - validator: errorText, + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Material( + child: new Form( + key: formKey, + autovalidate: autovalidate, + child: new TextFormField( + validator: errorText, + ), ), ), ), @@ -162,12 +171,15 @@ void main() { final GlobalKey> inputKey = new GlobalKey>(); Widget builder() { - return new Center( - child: new Material( - child: new Form( - child: new TextFormField( - key: inputKey, - controller: controller, + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Material( + child: new Form( + child: new TextFormField( + key: inputKey, + controller: controller, + ), ), ), ), @@ -198,14 +210,17 @@ void main() { String fieldValue; Widget builder(bool remove) { - return new Center( - child: new Material( - child: new Form( - key: formKey, - child: remove ? new Container() : new TextFormField( - autofocus: true, - onSaved: (String value) { fieldValue = value; }, - validator: (String value) { return value.isEmpty ? null : 'yes'; } + return new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new Material( + child: new Form( + key: formKey, + child: remove ? new Container() : new TextFormField( + autofocus: true, + onSaved: (String value) { fieldValue = value; }, + validator: (String value) { return value.isEmpty ? null : 'yes'; } + ), ), ), ), diff --git a/packages/flutter/test/widgets/global_keys_moving_test.dart b/packages/flutter/test/widgets/global_keys_moving_test.dart index 2e7346e09a..12f1276061 100644 --- a/packages/flutter/test/widgets/global_keys_moving_test.dart +++ b/packages/flutter/test/widgets/global_keys_moving_test.dart @@ -25,7 +25,7 @@ class StatefulLeafState extends State { void markNeedsBuild() { setState(() { }); } @override - Widget build(BuildContext context) => const Text('leaf'); + Widget build(BuildContext context) => const Text('leaf', textDirection: TextDirection.ltr); } class KeyedWrapper extends StatelessWidget { diff --git a/packages/flutter/test/widgets/hit_testing_test.dart b/packages/flutter/test/widgets/hit_testing_test.dart index d069d0b15e..7afa44d666 100644 --- a/packages/flutter/test/widgets/hit_testing_test.dart +++ b/packages/flutter/test/widgets/hit_testing_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart'; void main() { testWidgets('toString control test', (WidgetTester tester) async { - await tester.pumpWidget(const Center(child: const Text('Hello'))); + await tester.pumpWidget(const Center(child: const Text('Hello', textDirection: TextDirection.ltr))); final HitTestResult result = tester.hitTestOnBinding(Offset.zero); expect(result, hasOneLineDescription); expect(result.path.first, hasOneLineDescription); diff --git a/packages/flutter/test/widgets/hyperlink_test.dart b/packages/flutter/test/widgets/hyperlink_test.dart index 98b135249a..6f6fede85f 100644 --- a/packages/flutter/test/widgets/hyperlink_test.dart +++ b/packages/flutter/test/widgets/hyperlink_test.dart @@ -26,6 +26,7 @@ void main() { new Center( child: new RichText( key: textKey, + textDirection: TextDirection.ltr, text: new TextSpan( children: [ new TextSpan( diff --git a/packages/flutter/test/widgets/icon_test.dart b/packages/flutter/test/widgets/icon_test.dart index bfbe94ef27..3ad61deebb 100644 --- a/packages/flutter/test/widgets/icon_test.dart +++ b/packages/flutter/test/widgets/icon_test.dart @@ -8,13 +8,16 @@ import 'package:flutter/widgets.dart'; void main() { testWidgets('Can set opacity for an Icon', (WidgetTester tester) async { await tester.pumpWidget( - const IconTheme( - data: const IconThemeData( - color: const Color(0xFF666666), - opacity: 0.5 + const Directionality( + textDirection: TextDirection.ltr, + child: const IconTheme( + data: const IconThemeData( + color: const Color(0xFF666666), + opacity: 0.5 + ), + child: const Icon(const IconData(0xd0a0, fontFamily: 'Arial')) ), - child: const Icon(const IconData(0xd0a0, fontFamily: 'Arial')) - ) + ), ); final RichText text = tester.widget(find.byType(RichText)); expect(text.text.style.color, const Color(0xFF666666).withOpacity(0.5)); @@ -22,8 +25,11 @@ void main() { testWidgets('Icon sizing - no theme, default size', (WidgetTester tester) async { await tester.pumpWidget( - const Center( - child: const Icon(null), + const Directionality( + textDirection: TextDirection.ltr, + child: const Center( + child: const Icon(null), + ), ), ); @@ -33,10 +39,13 @@ void main() { testWidgets('Icon sizing - no theme, explicit size', (WidgetTester tester) async { await tester.pumpWidget( - const Center( - child: const Icon( - null, - size: 96.0, + const Directionality( + textDirection: TextDirection.ltr, + child: const Center( + child: const Icon( + null, + size: 96.0, + ), ), ), ); @@ -47,10 +56,13 @@ void main() { testWidgets('Icon sizing - sized theme', (WidgetTester tester) async { await tester.pumpWidget( - const Center( - child: const IconTheme( - data: const IconThemeData(size: 36.0), - child: const Icon(null), + const Directionality( + textDirection: TextDirection.ltr, + child: const Center( + child: const IconTheme( + data: const IconThemeData(size: 36.0), + child: const Icon(null), + ), ), ), ); @@ -61,12 +73,15 @@ void main() { testWidgets('Icon sizing - sized theme, explicit size', (WidgetTester tester) async { await tester.pumpWidget( - const Center( - child: const IconTheme( - data: const IconThemeData(size: 36.0), - child: const Icon( - null, - size: 48.0, + const Directionality( + textDirection: TextDirection.ltr, + child: const Center( + child: const IconTheme( + data: const IconThemeData(size: 36.0), + child: const Icon( + null, + size: 48.0, + ), ), ), ) @@ -78,10 +93,13 @@ void main() { testWidgets('Icon sizing - sizeless theme, default size', (WidgetTester tester) async { await tester.pumpWidget( - const Center( - child: const IconTheme( - data: const IconThemeData(), - child: const Icon(null), + const Directionality( + textDirection: TextDirection.ltr, + child: const Center( + child: const IconTheme( + data: const IconThemeData(), + child: const Icon(null), + ), ), ), ); @@ -93,8 +111,11 @@ void main() { testWidgets('Icon with custom font', (WidgetTester tester) async { await tester.pumpWidget( - const Center( - child: const Icon(const IconData(0x41, fontFamily: 'Roboto')), + const Directionality( + textDirection: TextDirection.ltr, + child: const Center( + child: const Icon(const IconData(0x41, fontFamily: 'Roboto')), + ), ), ); diff --git a/packages/flutter/test/widgets/independent_widget_layout_test.dart b/packages/flutter/test/widgets/independent_widget_layout_test.dart index 5ae15a1127..b9306d0903 100644 --- a/packages/flutter/test/widgets/independent_widget_layout_test.dart +++ b/packages/flutter/test/widgets/independent_widget_layout_test.dart @@ -92,7 +92,7 @@ class TriggerableState extends State { @override Widget build(BuildContext context) { widget.counter.count++; - return new Text("Bang $_count!"); + return new Text("Bang $_count!", textDirection: TextDirection.ltr); } } diff --git a/packages/flutter/test/widgets/inherited_test.dart b/packages/flutter/test/widgets/inherited_test.dart index d1931041b1..99cb5c8180 100644 --- a/packages/flutter/test/widgets/inherited_test.dart +++ b/packages/flutter/test/widgets/inherited_test.dart @@ -129,7 +129,7 @@ void main() { builder: (BuildContext context) { final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); log.add('a: ${v.value}'); - return const Text(''); + return const Text('', textDirection: TextDirection.ltr); } ) ) @@ -146,7 +146,7 @@ void main() { builder: (BuildContext context) { final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); log.add('b: ${v.value}'); - return const Text(''); + return const Text('', textDirection: TextDirection.ltr); } ) ) @@ -204,7 +204,7 @@ void main() { builder: (BuildContext context) { final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); log.add('a: ${v.value}'); - return const Text(''); + return const Text('', textDirection: TextDirection.ltr); } ) ) @@ -222,7 +222,7 @@ void main() { builder: (BuildContext context) { final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); log.add('b: ${v.value}'); - return const Text(''); + return const Text('', textDirection: TextDirection.ltr); } ) ) @@ -265,7 +265,7 @@ void main() { builder: (BuildContext context) { final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); log.add(v.value); - return const Text(''); + return const Text('', textDirection: TextDirection.ltr); } ); @@ -336,7 +336,7 @@ void main() { builder: (BuildContext context) { final ValueInherited v = context.inheritFromWidgetOfExactType(ValueInherited); log.add(v.value); - return const Text(''); + return const Text('', textDirection: TextDirection.ltr); } ); diff --git a/packages/flutter/test/widgets/list_view_builder_test.dart b/packages/flutter/test/widgets/list_view_builder_test.dart index 3ccfb611fb..26e7e78a11 100644 --- a/packages/flutter/test/widgets/list_view_builder_test.dart +++ b/packages/flutter/test/widgets/list_view_builder_test.dart @@ -66,7 +66,7 @@ void main() { key: new ValueKey(index), width: 500.0, // this should be ignored height: 400.0, // should be overridden by itemExtent - child: new Text('$index') + child: new Text('$index', textDirection: TextDirection.ltr) ); }; @@ -79,7 +79,7 @@ void main() { itemExtent: 200.0, itemBuilder: itemBuilder, ), - right: const Text('Not Today') + right: const Text('Not Today'), ), ); } @@ -190,7 +190,7 @@ void main() { final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) { callbackTracker.add(index); - return new Text('$index', key: new ValueKey(index)); + return new Text('$index', key: new ValueKey(index), textDirection: TextDirection.ltr); }; final Widget testWidget = new Directionality( diff --git a/packages/flutter/test/widgets/list_view_horizontal_test.dart b/packages/flutter/test/widgets/list_view_horizontal_test.dart index 1391ba49fd..9889146e33 100644 --- a/packages/flutter/test/widgets/list_view_horizontal_test.dart +++ b/packages/flutter/test/widgets/list_view_horizontal_test.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; const List items = const [0, 1, 2, 3, 4, 5]; diff --git a/packages/flutter/test/widgets/list_view_misc_test.dart b/packages/flutter/test/widgets/list_view_misc_test.dart index 298501c6fc..8878ecb35c 100644 --- a/packages/flutter/test/widgets/list_view_misc_test.dart +++ b/packages/flutter/test/widgets/list_view_misc_test.dart @@ -120,7 +120,7 @@ void main() { textDirection: TextDirection.ltr, child: new ListView( controller: controller, - children: [const Text("A"), const Text("B"), const Text("C")], + children: [const Text('A'), const Text('B'), const Text('C')], ), ); } diff --git a/packages/flutter/test/widgets/list_view_test.dart b/packages/flutter/test/widgets/list_view_test.dart index 4e133714b4..762352869f 100644 --- a/packages/flutter/test/widgets/list_view_test.dart +++ b/packages/flutter/test/widgets/list_view_test.dart @@ -189,7 +189,7 @@ void main() { child: new ListView( padding: const EdgeInsets.all(8.0), children: [ - const Text('padded'), + const Text('padded', textDirection: TextDirection.ltr), ], ), ), @@ -225,7 +225,7 @@ void main() { final TestSliverChildListDelegate delegate = new TestSliverChildListDelegate( new List.generate(20, (int i) { return new Container( - child: new Text('$i'), + child: new Text('$i', textDirection: TextDirection.ltr), ); }) ); diff --git a/packages/flutter/test/widgets/list_view_viewporting_test.dart b/packages/flutter/test/widgets/list_view_viewporting_test.dart index b510721551..bdf0487292 100644 --- a/packages/flutter/test/widgets/list_view_viewporting_test.dart +++ b/packages/flutter/test/widgets/list_view_viewporting_test.dart @@ -66,7 +66,7 @@ void main() { key: new ValueKey(index), width: 500.0, // this should be ignored height: 200.0, - child: new Text('$index'), + child: new Text('$index', textDirection: TextDirection.ltr), ); }; @@ -118,7 +118,7 @@ void main() { key: new ValueKey(index), height: 500.0, // this should be ignored width: 200.0, - child: new Text('$index'), + child: new Text('$index', textDirection: TextDirection.ltr), ); }; @@ -169,7 +169,7 @@ void main() { key: new ValueKey(index), width: 500.0, // this should be ignored height: 220.0, - child: new Text('$index') + child: new Text('$index', textDirection: TextDirection.ltr) ); }; @@ -214,7 +214,7 @@ void main() { width: 500.0, // this should be ignored height: 220.0, color: Theme.of(context).primaryColor, - child: new Text('$index'), + child: new Text('$index', textDirection: TextDirection.ltr), ); }; @@ -256,7 +256,7 @@ void main() { width: 500.0, // this should be ignored height: 220.0, color: Colors.green[500], - child: new Text('$index'), + child: new Text('$index', textDirection: TextDirection.ltr), ); }; diff --git a/packages/flutter/test/widgets/list_view_with_inherited_test.dart b/packages/flutter/test/widgets/list_view_with_inherited_test.dart index 9523ef2449..71d5936b63 100644 --- a/packages/flutter/test/widgets/list_view_with_inherited_test.dart +++ b/packages/flutter/test/widgets/list_view_with_inherited_test.dart @@ -15,7 +15,7 @@ Widget buildCard(BuildContext context, int index) { height: 100.0, child: new DefaultTextStyle( style: new TextStyle(fontSize: 2.0 + items.length.toDouble()), - child: new Text('${items[index]}') + child: new Text('${items[index]}', textDirection: TextDirection.ltr) ) ); } diff --git a/packages/flutter/test/widgets/listener_test.dart b/packages/flutter/test/widgets/listener_test.dart index 11c7b60897..16c7c7dc96 100644 --- a/packages/flutter/test/widgets/listener_test.dart +++ b/packages/flutter/test/widgets/listener_test.dart @@ -24,7 +24,7 @@ void main() { onPointerDown: (_) { log.add('bottom'); }, - child: const Text('X') + child: const Text('X', textDirection: TextDirection.ltr) ) ) ) diff --git a/packages/flutter/test/widgets/localizations_test.dart b/packages/flutter/test/widgets/localizations_test.dart index 18974fa491..90575e0138 100644 --- a/packages/flutter/test/widgets/localizations_test.dart +++ b/packages/flutter/test/widgets/localizations_test.dart @@ -130,7 +130,7 @@ void main() { buildFrame( buildContent: (BuildContext context) { pageContext = context; - return const Text('Hello World'); + return const Text('Hello World', textDirection: TextDirection.ltr); } ) ); diff --git a/packages/flutter/test/widgets/modal_barrier_test.dart b/packages/flutter/test/widgets/modal_barrier_test.dart index a2e9bdf173..b2d628fcb9 100644 --- a/packages/flutter/test/widgets/modal_barrier_test.dart +++ b/packages/flutter/test/widgets/modal_barrier_test.dart @@ -23,7 +23,7 @@ void main() { child: const SizedBox( width: 10.0, height: 10.0, - child: const Text('target') + child: const Text('target', textDirection: TextDirection.ltr) ) ); }); diff --git a/packages/flutter/test/widgets/overlay_test.dart b/packages/flutter/test/widgets/overlay_test.dart index 9c1e3df754..f7619beb7a 100644 --- a/packages/flutter/test/widgets/overlay_test.dart +++ b/packages/flutter/test/widgets/overlay_test.dart @@ -141,7 +141,7 @@ void main() { ' ╎ │ _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#00000] ←\n' ' ╎ │ TickerMode ← _Theatre ← Overlay-[GlobalKey#00000] ← [root]\n' ' ╎ │ parentData: not positioned; offset=Offset(0.0, 0.0)\n' - ' ╎ │ constraints: null\n' + ' ╎ │ constraints: MISSING\n' ' ╎ │ size: MISSING\n' ' ╎ │ maxWidth: 0.0\n' ' ╎ │ maxHeight: 0.0\n' @@ -151,7 +151,7 @@ void main() { ' ╎ _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#00000] ←\n' ' ╎ TickerMode ← _Theatre ← Overlay-[GlobalKey#00000] ← [root]\n' ' ╎ parentData: \n' - ' ╎ constraints: null\n' + ' ╎ constraints: MISSING\n' ' ╎ size: MISSING\n' ' ╎ additionalConstraints: BoxConstraints(biggest)\n' ' ╎\n' @@ -160,7 +160,7 @@ void main() { ' │ _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#00000] ←\n' ' │ TickerMode ← _Theatre ← Overlay-[GlobalKey#00000] ← [root]\n' ' │ parentData: not positioned; offset=Offset(0.0, 0.0)\n' - ' │ constraints: null\n' + ' │ constraints: MISSING\n' ' │ size: MISSING\n' ' │ maxWidth: 0.0\n' ' │ maxHeight: 0.0\n' @@ -170,7 +170,7 @@ void main() { ' _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#00000] ←\n' ' TickerMode ← _Theatre ← Overlay-[GlobalKey#00000] ← [root]\n' ' parentData: \n' - ' constraints: null\n' + ' constraints: MISSING\n' ' size: MISSING\n' ' additionalConstraints: BoxConstraints(biggest)\n' ), diff --git a/packages/flutter/test/widgets/pageable_list_test.dart b/packages/flutter/test/widgets/pageable_list_test.dart index de53906d56..ebf3242a5a 100644 --- a/packages/flutter/test/widgets/pageable_list_test.dart +++ b/packages/flutter/test/widgets/pageable_list_test.dart @@ -63,10 +63,14 @@ Future pageRight(WidgetTester tester) { void main() { testWidgets('PageView default control', (WidgetTester tester) async { - await tester.pumpWidget(new Directionality( - textDirection: TextDirection.ltr, - child: new Center(child: new PageView()) - )); + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new Center( + child: new PageView(), + ), + ), + ); }); testWidgets('PageView control test (LTR)', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/reparent_state_harder_test.dart b/packages/flutter/test/widgets/reparent_state_harder_test.dart index 602b4ef846..0c786b5ac1 100644 --- a/packages/flutter/test/widgets/reparent_state_harder_test.dart +++ b/packages/flutter/test/widgets/reparent_state_harder_test.dart @@ -52,7 +52,7 @@ class DummyStatefulWidget extends StatefulWidget { class DummyStatefulWidgetState extends State { @override - Widget build(BuildContext context) => const Text('LEAF'); + Widget build(BuildContext context) => const Text('LEAF', textDirection: TextDirection.ltr); } class RekeyableDummyStatefulWidgetWrapper extends StatefulWidget { diff --git a/packages/flutter/test/widgets/run_app_test.dart b/packages/flutter/test/widgets/run_app_test.dart index 901cfa1bb7..6f9de6d75e 100644 --- a/packages/flutter/test/widgets/run_app_test.dart +++ b/packages/flutter/test/widgets/run_app_test.dart @@ -11,9 +11,9 @@ void main() { new Material( child: new RaisedButton( onPressed: () { - runApp(const Center(child: const Text('Done'))); + runApp(const Center(child: const Text('Done', textDirection: TextDirection.ltr))); }, - child: const Text('GO') + child: const Text('GO', textDirection: TextDirection.ltr) ) ) ); diff --git a/packages/flutter/test/widgets/scrollable_animations_test.dart b/packages/flutter/test/widgets/scrollable_animations_test.dart index ffb7581003..bbe3d391bb 100644 --- a/packages/flutter/test/widgets/scrollable_animations_test.dart +++ b/packages/flutter/test/widgets/scrollable_animations_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('Does not animate if already at target position', (WidgetTester tester) async { final List textWidgets = []; for (int i = 0; i < 80; i++) - textWidgets.add(new Text('$i')); + textWidgets.add(new Text('$i', textDirection: TextDirection.ltr)); final ScrollController controller = new ScrollController(); await tester.pumpWidget( new Directionality( @@ -33,7 +33,7 @@ void main() { testWidgets('Does not animate if already at target position within tolerance', (WidgetTester tester) async { final List textWidgets = []; for (int i = 0; i < 80; i++) - textWidgets.add(new Text('$i')); + textWidgets.add(new Text('$i', textDirection: TextDirection.ltr)); final ScrollController controller = new ScrollController(); await tester.pumpWidget( new Directionality( @@ -59,7 +59,7 @@ void main() { testWidgets('Animates if going to a position outside of tolerance', (WidgetTester tester) async { final List textWidgets = []; for (int i = 0; i < 80; i++) - textWidgets.add(new Text('$i')); + textWidgets.add(new Text('$i', textDirection: TextDirection.ltr)); final ScrollController controller = new ScrollController(); await tester.pumpWidget( new Directionality( diff --git a/packages/flutter/test/widgets/scrollable_semantics_test.dart b/packages/flutter/test/widgets/scrollable_semantics_test.dart index f2192c0d4a..6252a60c13 100644 --- a/packages/flutter/test/widgets/scrollable_semantics_test.dart +++ b/packages/flutter/test/widgets/scrollable_semantics_test.dart @@ -47,7 +47,7 @@ void main() { for (int i = 0; i < 80; i++) containers.add(new MergeSemantics(child: new Container( height: kItemHeight, - child: new Text('container $i'), + child: new Text('container $i', textDirection: TextDirection.ltr), ))); final ScrollController scrollController = new ScrollController( diff --git a/packages/flutter/test/widgets/semantics_10_test.dart b/packages/flutter/test/widgets/semantics_10_test.dart index 7650ef2bdd..5ed047d29b 100644 --- a/packages/flutter/test/widgets/semantics_10_test.dart +++ b/packages/flutter/test/widgets/semantics_10_test.dart @@ -16,22 +16,22 @@ void main() { final SemanticsTester semantics = new SemanticsTester(tester); await tester.pumpWidget( - buildTestWidgets( - excludeSemantics: false, - label: 'label', - isSemanticsBoundary: true, - ) + buildTestWidgets( + excludeSemantics: false, + label: 'label', + isSemanticsBoundary: true, + ), ); callLog.clear(); // The following should not trigger an assert. await tester.pumpWidget( - buildTestWidgets( - excludeSemantics: true, - label: 'label CHANGED', - isSemanticsBoundary: false, - ) + buildTestWidgets( + excludeSemantics: true, + label: 'label CHANGED', + isSemanticsBoundary: false, + ), ); expect(callLog, ['markNeedsSemanticsUpdate(onlyChanges: true)', 'markNeedsSemanticsUpdate(onlyChanges: false)']); @@ -41,23 +41,26 @@ void main() { } Widget buildTestWidgets({bool excludeSemantics, String label, bool isSemanticsBoundary}) { - return new Semantics( - label: 'container', - container: true, - child: new ExcludeSemantics( - excluding: excludeSemantics, - child: new TestWidget( - label: label, - isSemanticBoundary: isSemanticsBoundary, - child: new Column( - children: [ - const Semantics( - label: 'child1', - ), - const Semantics( - label: 'child2', - ), - ], + return new Directionality( + textDirection: TextDirection.ltr, + child: new Semantics( + label: 'container', + container: true, + child: new ExcludeSemantics( + excluding: excludeSemantics, + child: new TestWidget( + label: label, + isSemanticBoundary: isSemanticsBoundary, + child: new Column( + children: [ + const Semantics( + label: 'child1', + ), + const Semantics( + label: 'child2', + ), + ], + ), ), ), ), @@ -96,6 +99,7 @@ class RenderTest extends RenderProxyBox { void _annotate(SemanticsNode node) { node.label = _label; + node.textDirection = TextDirection.ltr; } String _label; diff --git a/packages/flutter/test/widgets/semantics_11_test.dart b/packages/flutter/test/widgets/semantics_11_test.dart index bd41d685fa..7acf3f2b5e 100644 --- a/packages/flutter/test/widgets/semantics_11_test.dart +++ b/packages/flutter/test/widgets/semantics_11_test.dart @@ -72,5 +72,6 @@ class RenderTest extends RenderProxyBox { void _annotate(SemanticsNode node) { labelWasReset.add(node.label == ''); node.label = "Label ${labelWasReset.length}"; + node.textDirection = TextDirection.ltr; } } diff --git a/packages/flutter/test/widgets/semantics_1_test.dart b/packages/flutter/test/widgets/semantics_1_test.dart index 2b1b94ca08..11e7f9a5d1 100644 --- a/packages/flutter/test/widgets/semantics_1_test.dart +++ b/packages/flutter/test/widgets/semantics_1_test.dart @@ -18,6 +18,7 @@ void main() { new Container( child: new Semantics( label: 'test1', + textDirection: TextDirection.ltr, child: new Container() ) ) @@ -32,13 +33,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr), ), new Container( height: 10.0, child: const IgnorePointer( ignoring: true, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr), ) ), ], @@ -54,13 +55,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr), ), new Container( height: 10.0, child: const IgnorePointer( ignoring: false, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr), ) ), ], @@ -92,13 +93,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) ), new Container( height: 10.0, child: const IgnorePointer( ignoring: true, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) ) ), ], @@ -113,13 +114,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) ), new Container( height: 10.0, child: const IgnorePointer( ignoring: false, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) ) ), ], diff --git a/packages/flutter/test/widgets/semantics_2_test.dart b/packages/flutter/test/widgets/semantics_2_test.dart index 5d23495c33..063d8b735e 100644 --- a/packages/flutter/test/widgets/semantics_2_test.dart +++ b/packages/flutter/test/widgets/semantics_2_test.dart @@ -23,13 +23,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) ), new Container( height: 10.0, child: const IgnorePointer( ignoring: false, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) ) ), ], @@ -43,11 +43,13 @@ void main() { new TestSemantics.rootChild( id: 1, label: 'child1', + textDirection: TextDirection.ltr, rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), ), new TestSemantics.rootChild( id: 2, label: 'child2', + textDirection: TextDirection.ltr, rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), transform: new Matrix4.translationValues(0.0, 10.0, 0.0), ), @@ -61,13 +63,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) ), new Container( height: 10.0, child: const IgnorePointer( ignoring: true, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) ) ), ], @@ -83,13 +85,13 @@ void main() { children: [ new Container( height: 10.0, - child: const Semantics(label: 'child1') + child: const Semantics(label: 'child1', textDirection: TextDirection.ltr) ), new Container( height: 10.0, child: const IgnorePointer( ignoring: false, - child: const Semantics(label: 'child2') + child: const Semantics(label: 'child2', textDirection: TextDirection.ltr) ) ), ], @@ -103,11 +105,13 @@ void main() { new TestSemantics.rootChild( id: 3, label: 'child1', + textDirection: TextDirection.ltr, rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), ), new TestSemantics.rootChild( id: 2, label: 'child2', + textDirection: TextDirection.ltr, rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0), transform: new Matrix4.translationValues(0.0, 10.0, 0.0), ), diff --git a/packages/flutter/test/widgets/semantics_3_test.dart b/packages/flutter/test/widgets/semantics_3_test.dart index ad7fc33f7b..7c24528eae 100644 --- a/packages/flutter/test/widgets/semantics_3_test.dart +++ b/packages/flutter/test/widgets/semantics_3_test.dart @@ -19,6 +19,7 @@ void main() { new Container( child: new Semantics( label: 'test', + textDirection: TextDirection.ltr, child: new Container( child: const Semantics( checked: true @@ -57,7 +58,8 @@ void main() { new Container( child: new Container( child: const Semantics( - label: 'test' + label: 'test', + textDirection: TextDirection.ltr, ) ) ) @@ -76,7 +78,8 @@ void main() { checked: true, child: new Container( child: const Semantics( - label: 'test' + label: 'test', + textDirection: TextDirection.ltr, ) ) ) @@ -102,7 +105,8 @@ void main() { checked: true, child: new Container( child: const Semantics( - label: 'test' + label: 'test', + textDirection: TextDirection.ltr, ) ) ) diff --git a/packages/flutter/test/widgets/semantics_4_test.dart b/packages/flutter/test/widgets/semantics_4_test.dart index 51cfc22844..5902698c69 100644 --- a/packages/flutter/test/widgets/semantics_4_test.dart +++ b/packages/flutter/test/widgets/semantics_4_test.dart @@ -20,8 +20,9 @@ void main() { // / \ C=node with checked // C C* *=node removed next pass // - await tester.pumpWidget( - new Stack( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Stack( fit: StackFit.expand, children: [ const Semantics( @@ -43,7 +44,7 @@ void main() { ), ], ), - ); + )); expect(semantics, hasSemantics( new TestSemantics.root( @@ -79,8 +80,9 @@ void main() { // L* LC C=node with checked // *=node removed next pass // - await tester.pumpWidget( - new Stack( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Stack( fit: StackFit.expand, children: [ const Semantics( @@ -100,7 +102,7 @@ void main() { ), ], ), - ); + )); expect(semantics, hasSemantics( new TestSemantics.root( @@ -124,8 +126,9 @@ void main() { // OLC L=node with label // C=node with checked // - await tester.pumpWidget( - new Stack( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Stack( fit: StackFit.expand, children: [ const Semantics(), @@ -143,7 +146,7 @@ void main() { ), ], ), - ); + )); expect(semantics, hasSemantics( new TestSemantics.root( diff --git a/packages/flutter/test/widgets/semantics_5_test.dart b/packages/flutter/test/widgets/semantics_5_test.dart index 97320809f8..7ff598f93b 100644 --- a/packages/flutter/test/widgets/semantics_5_test.dart +++ b/packages/flutter/test/widgets/semantics_5_test.dart @@ -25,6 +25,7 @@ void main() { ), const Semantics( label: 'label', // (force a fork) + textDirection: TextDirection.ltr, ), ] ) diff --git a/packages/flutter/test/widgets/semantics_7_test.dart b/packages/flutter/test/widgets/semantics_7_test.dart index f9c5b72405..e5b8377e5a 100644 --- a/packages/flutter/test/widgets/semantics_7_test.dart +++ b/packages/flutter/test/widgets/semantics_7_test.dart @@ -18,34 +18,37 @@ void main() { label = '1'; await tester.pumpWidget( - new Stack( - fit: StackFit.expand, - children: [ - new MergeSemantics( - child: new Semantics( - checked: true, - container: true, + new Directionality( + textDirection: TextDirection.ltr, + child: new Stack( + fit: StackFit.expand, + children: [ + new MergeSemantics( child: new Semantics( + checked: true, container: true, - label: label, - ) - ) - ), - new MergeSemantics( - child: new Stack( - fit: StackFit.expand, - children: [ - const Semantics( - checked: true, - ), - new Semantics( + child: new Semantics( + container: true, label: label, ), - ] - ) - ), - ] - ) + ), + ), + new MergeSemantics( + child: new Stack( + fit: StackFit.expand, + children: [ + const Semantics( + checked: true, + ), + new Semantics( + label: label, + ), + ], + ), + ), + ], + ), + ), ); expect(semantics, hasSemantics( @@ -71,34 +74,37 @@ void main() { label = '2'; await tester.pumpWidget( - new Stack( - fit: StackFit.expand, - children: [ - new MergeSemantics( - child: new Semantics( - checked: true, - container: true, + new Directionality( + textDirection: TextDirection.ltr, + child: new Stack( + fit: StackFit.expand, + children: [ + new MergeSemantics( child: new Semantics( + checked: true, container: true, - label: label, - ) - ) - ), - new MergeSemantics( - child: new Stack( - fit: StackFit.expand, - children: [ - const Semantics( - checked: true, - ), - new Semantics( + child: new Semantics( + container: true, label: label, - ) - ] - ) - ), - ] - ) + ), + ), + ), + new MergeSemantics( + child: new Stack( + fit: StackFit.expand, + children: [ + const Semantics( + checked: true, + ), + new Semantics( + label: label, + ), + ], + ), + ), + ], + ), + ), ); expect(semantics, hasSemantics( diff --git a/packages/flutter/test/widgets/semantics_8_test.dart b/packages/flutter/test/widgets/semantics_8_test.dart index 9d9ed4f06d..f7fa9205d2 100644 --- a/packages/flutter/test/widgets/semantics_8_test.dart +++ b/packages/flutter/test/widgets/semantics_8_test.dart @@ -23,10 +23,11 @@ void main() { child: new Stack( children: [ const Semantics( - checked: true + checked: true, ), const Semantics( - label: 'label' + label: 'label', + textDirection: TextDirection.ltr, ) ] ) @@ -39,6 +40,7 @@ void main() { new TestSemantics.root( flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, label: 'label', + textDirection: TextDirection.ltr, ) )); @@ -52,7 +54,8 @@ void main() { child: new Stack( children: [ const Semantics( - label: 'label' + label: 'label', + textDirection: TextDirection.ltr, ), const Semantics( checked: true @@ -68,6 +71,7 @@ void main() { new TestSemantics.root( flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index, label: 'label', + textDirection: TextDirection.ltr, ) )); diff --git a/packages/flutter/test/widgets/semantics_9_test.dart b/packages/flutter/test/widgets/semantics_9_test.dart index 522b80ab2a..cbe0db7cf7 100644 --- a/packages/flutter/test/widgets/semantics_9_test.dart +++ b/packages/flutter/test/widgets/semantics_9_test.dart @@ -18,11 +18,13 @@ void main() { children: [ new Semantics( label: 'layer#1', + textDirection: TextDirection.ltr, child: new Container(), ), const BlockSemantics(), new Semantics( label: 'layer#2', + textDirection: TextDirection.ltr, child: new Container(), ), ], @@ -34,6 +36,7 @@ void main() { children: [ new Semantics( label: 'layer#1', + textDirection: TextDirection.ltr, child: new Container(), ), ], @@ -47,7 +50,7 @@ void main() { testWidgets('does not hides semantic nodes of siblings outside the current semantic boundary', (WidgetTester tester) async { final SemanticsTester semantics = new SemanticsTester(tester); - await tester.pumpWidget(new Stack( + await tester.pumpWidget(new Directionality(textDirection: TextDirection.ltr, child: new Stack( children: [ new Semantics( label: '#1', @@ -84,7 +87,7 @@ void main() { child: new Container(), ), ], - )); + ))); expect(semantics, includesNodeWith(label: '#1')); expect(semantics, includesNodeWith(label: '#2')); @@ -101,7 +104,7 @@ void main() { final SemanticsTester semantics = new SemanticsTester(tester); final GlobalKey stackKey = new GlobalKey(); - await tester.pumpWidget(new Stack( + await tester.pumpWidget(new Directionality(textDirection: TextDirection.ltr, child: new Stack( key: stackKey, children: [ new Semantics( @@ -119,7 +122,7 @@ void main() { child: new Container(), ), ], - )); + ))); expect(semantics, isNot(includesNodeWith(label: 'NOT#1'))); expect(semantics, includesNodeWith(label: '#2.1')); diff --git a/packages/flutter/test/widgets/semantics_debugger_test.dart b/packages/flutter/test/widgets/semantics_debugger_test.dart index 67ddbe513a..2ee479837e 100644 --- a/packages/flutter/test/widgets/semantics_debugger_test.dart +++ b/packages/flutter/test/widgets/semantics_debugger_test.dart @@ -4,10 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('SemanticsDebugger smoke test', (WidgetTester tester) async { + // This is a smoketest to verify that adding a debugger doesn't crash. await tester.pumpWidget( new Stack( @@ -18,6 +20,7 @@ void main() { ), const Semantics( label: 'label', + textDirection: TextDirection.ltr, ), ], ), @@ -33,6 +36,7 @@ void main() { ), const Semantics( label: 'label', + textDirection: TextDirection.ltr, ), ], ), @@ -50,14 +54,14 @@ void main() { new SemanticsDebugger( child: new Stack( children: [ - const Semantics(label: 'label1'), + const Semantics(label: 'label1', textDirection: TextDirection.ltr), new Positioned( key: key, left: 0.0, top: 0.0, width: 100.0, height: 100.0, - child: const Semantics(label: 'label2'), + child: const Semantics(label: 'label2', textDirection: TextDirection.ltr), ), ], ), @@ -68,7 +72,7 @@ void main() { new SemanticsDebugger( child: new Stack( children: [ - const Semantics(label: 'label1'), + const Semantics(label: 'label1', textDirection: TextDirection.ltr), new Semantics( container: true, child: new Stack( @@ -79,9 +83,9 @@ void main() { top: 0.0, width: 100.0, height: 100.0, - child: const Semantics(label: 'label2'), + child: const Semantics(label: 'label2', textDirection: TextDirection.ltr), ), - const Semantics(label: 'label3'), + const Semantics(label: 'label3', textDirection: TextDirection.ltr), ], ), ), @@ -94,7 +98,7 @@ void main() { new SemanticsDebugger( child: new Stack( children: [ - const Semantics(label: 'label1'), + const Semantics(label: 'label1', textDirection: TextDirection.ltr), new Semantics( container: true, child: new Stack( @@ -105,9 +109,9 @@ void main() { top: 0.0, width: 100.0, height: 100.0, - child: const Semantics(label: 'label2')), - const Semantics(label: 'label3'), - const Semantics(label: 'label4'), + child: const Semantics(label: 'label2', textDirection: TextDirection.ltr)), + const Semantics(label: 'label3', textDirection: TextDirection.ltr), + const Semantics(label: 'label4', textDirection: TextDirection.ltr), ], ), ), @@ -158,6 +162,47 @@ void main() { log.clear(); }); + testWidgets('SemanticsDebugger interaction test - negative', + (WidgetTester tester) async { + final List log = []; + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new SemanticsDebugger( + child: new Material( + child: new ListView( + children: [ + new RaisedButton( + onPressed: () { + log.add('top'); + }, + child: const Text('TOP', textDirection: TextDirection.ltr), + ), + new ExcludeSemantics( + child: new RaisedButton( + onPressed: () { + log.add('bottom'); + }, + child: const Text('BOTTOM', textDirection: TextDirection.ltr), + ), + ), + ], + ), + ), + ), + ), + ); + + await tester.tap(find.text('TOP')); + expect(log, equals(['top'])); + log.clear(); + + await tester.tap(find.text('BOTTOM')); + expect(log, equals([])); + log.clear(); + }); + testWidgets('SemanticsDebugger scroll test', (WidgetTester tester) async { final Key childKey = new UniqueKey(); @@ -211,7 +256,7 @@ void main() { expect(didLongPress, isFalse); didLongPress = true; }, - child: const Text('target'), + child: const Text('target', textDirection: TextDirection.ltr), ), ), ); diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart index e4d25f2eff..fcadf8af06 100644 --- a/packages/flutter/test/widgets/semantics_test.dart +++ b/packages/flutter/test/widgets/semantics_test.dart @@ -14,12 +14,14 @@ void main() { final TestSemantics expectedSemantics = new TestSemantics.root( label: 'test1', + textDirection: TextDirection.ltr, ); await tester.pumpWidget( new Container( child: new Semantics( label: 'test1', + textDirection: TextDirection.ltr, child: new Container() ) ) @@ -43,8 +45,9 @@ void main() { final SemanticsTester semantics = new SemanticsTester(tester); final GlobalKey key = new GlobalKey(); - await tester.pumpWidget( - new Container( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Container( child: new Semantics( label: 'test1', child: new Semantics( @@ -55,7 +58,7 @@ void main() { ) ) ) - ); + )); expect(semantics, hasSemantics( new TestSemantics.root( @@ -70,8 +73,9 @@ void main() { ) )); - await tester.pumpWidget( - new Container( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Container( child: new Semantics( label: 'test1', child: new Semantics( @@ -86,7 +90,7 @@ void main() { ) ) ) - ); + )); expect(semantics, hasSemantics( new TestSemantics.root( @@ -110,4 +114,90 @@ void main() { semantics.dispose(); }); + + testWidgets('Semantics and Directionality - RTL', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + final TestSemantics expectedSemantics = new TestSemantics.root( + label: 'test1', + textDirection: TextDirection.rtl, + ); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new Semantics( + label: 'test1', + child: new Container(), + ), + ), + ); + + expect(semantics, hasSemantics(expectedSemantics)); + }); + + testWidgets('Semantics and Directionality - LTR', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + final TestSemantics expectedSemantics = new TestSemantics.root( + label: 'test1', + textDirection: TextDirection.ltr, + ); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new Semantics( + label: 'test1', + child: new Container(), + ), + ), + ); + + expect(semantics, hasSemantics(expectedSemantics)); + }); + + testWidgets('Semantics and Directionality - overriding RTL with LTR', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + final TestSemantics expectedSemantics = new TestSemantics.root( + label: 'test1', + textDirection: TextDirection.ltr, + ); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.rtl, + child: new Semantics( + label: 'test1', + textDirection: TextDirection.ltr, + child: new Container(), + ), + ), + ); + + expect(semantics, hasSemantics(expectedSemantics)); + }); + + testWidgets('Semantics and Directionality - overriding LTR with RTL', (WidgetTester tester) async { + final SemanticsTester semantics = new SemanticsTester(tester); + + final TestSemantics expectedSemantics = new TestSemantics.root( + label: 'test1', + textDirection: TextDirection.rtl, + ); + + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new Semantics( + label: 'test1', + textDirection: TextDirection.rtl, + child: new Container(), + ), + ), + ); + + expect(semantics, hasSemantics(expectedSemantics)); + }); } diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index f1bb0cae84..f2fb679579 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -9,6 +9,8 @@ import 'package:meta/meta.dart'; export 'package:flutter/rendering.dart' show SemanticsData; +const String _matcherHelp = 'Try dumping the semantics with debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest) from the package:flutter/rendering.dart library to see what the semantics tree looks like.'; + /// Test semantics data that is compared against real semantics tree. /// /// Useful with [hasSemantics] and [SemanticsTester] to test the contents of the @@ -33,6 +35,7 @@ class TestSemantics { this.flags: 0, this.actions: 0, this.label: '', + this.textDirection, this.rect, this.transform, this.children: const [], @@ -49,6 +52,7 @@ class TestSemantics { this.flags: 0, this.actions: 0, this.label: '', + this.textDirection, this.transform, this.children: const [], Iterable tags, @@ -73,6 +77,7 @@ class TestSemantics { this.flags: 0, this.actions: 0, this.label: '', + this.textDirection, this.rect, Matrix4 transform, this.children: const [], @@ -98,6 +103,13 @@ class TestSemantics { /// A textual description of this node. final String label; + /// The reading direction of the [label]. + /// + /// Even if this is not set, the [hasSemantics] matcher will verify that if a + /// label is present on the [SemanticsNode], a [SemanticsNode.textDirection] + /// is also set. + final TextDirection textDirection; + /// The bounding box for this node in its coordinate system. /// /// Convenient values are available: @@ -142,18 +154,34 @@ class TestSemantics { bool _matches(SemanticsNode node, Map matchState, { bool ignoreRect: false, bool ignoreTransform: false }) { final SemanticsData nodeData = node.getSemanticsData(); - if (node == null || id != node.id - || flags != nodeData.flags - || actions != nodeData.actions - || label != nodeData.label - || !setEquals(tags, nodeData.tags) - || (!ignoreRect && rect != nodeData.rect) - || (!ignoreTransform && transform != nodeData.transform) - || children.length != (node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount)) { - matchState[TestSemantics] = this; - matchState[SemanticsNode] = node; + + bool fail(String message) { + matchState[TestSemantics] = 'message\n$_matcherHelp'; return false; } + + if (node == null) + return fail('could not find node with id $id.'); + if (id != node.id) + return fail('expected node id $id but found id ${node.id}.'); + if (flags != nodeData.flags) + return fail('expected node id $id to have flags $flags but found flags ${nodeData.flags}.'); + if (actions != nodeData.actions) + return fail('expected node id $id to have actions $actions but found actions ${nodeData.actions}.'); + if (label != nodeData.label) + return fail('expected node id $id to have label "$label" but found label "${nodeData.label}".'); + if (textDirection != null && textDirection != nodeData.textDirection) + return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".'); + if (nodeData.label != '' && nodeData.textDirection == null) + return fail('expected node id $id, which has a label, to have a textDirection, but it did not.'); + if (!ignoreRect && rect != nodeData.rect) + return fail('expected node id $id to have rect $rect but found rect ${nodeData.rect}.'); + if (!ignoreTransform && transform != nodeData.transform) + return fail('expected node id $id to have transform $transform but found transform:\n${nodeData.transform}.'); + final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount; + if (children.length != childrenCount) + return fail('expected node id $id to have ${children.length} child${ children.length == 1 ? "" : "ren" } but found $childrenCount.'); + if (children.isEmpty) return true; bool result = true; @@ -168,6 +196,11 @@ class TestSemantics { }); return result; } + + @override + String toString() { + return 'node $id, flags=$flags, actions=$actions, label="$label", textDirection=$textDirection, rect=$rect, transform=$transform, ${children.length} child${ children.length == 1 ? "" : "ren" }'; + } } /// Ensures that the given widget tester has a semantics tree to test. @@ -196,11 +229,9 @@ class SemanticsTester { } @override - String toString() => 'SemanticsTester'; + String toString() => 'SemanticsTester for ${tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode}'; } -const String _matcherHelp = 'Try dumping the semantics with debugDumpSemanticsTree(DebugSemanticsDumpOrder.inverseHitTest) from the package:flutter/rendering.dart library to see what the semantics tree looks like.'; - class _HasSemantics extends Matcher { const _HasSemantics(this._semantics, { this.ignoreRect: false, this.ignoreTransform: false }) : assert(_semantics != null), assert(ignoreRect != null), assert(ignoreTransform != null); @@ -215,32 +246,12 @@ class _HasSemantics extends Matcher { @override Description describe(Description description) { - return description.add('semantics node id ${_semantics.id}'); + return description.add('semantics node matching: $_semantics'); } @override Description describeMismatch(dynamic item, Description mismatchDescription, Map matchState, bool verbose) { - final TestSemantics testNode = matchState[TestSemantics]; - final SemanticsNode node = matchState[SemanticsNode]; - if (node == null) - return mismatchDescription.add('could not find node with id ${testNode.id}.\n$_matcherHelp'); - if (testNode.id != node.id) - return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}.\n$_matcherHelp'); - final SemanticsData data = node.getSemanticsData(); - if (testNode.flags != data.flags) - return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}.\n$_matcherHelp'); - if (testNode.actions != data.actions) - return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}.\n$_matcherHelp'); - if (testNode.label != data.label) - return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}".\n$_matcherHelp'); - if (!ignoreRect && testNode.rect != data.rect) - return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}.\n$_matcherHelp'); - if (!ignoreTransform && testNode.transform != data.transform) - return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform:.\n${data.transform}.\n$_matcherHelp'); - final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount; - if (testNode.children.length != childrenCount) - return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} children but found $childrenCount.\n$_matcherHelp'); - return mismatchDescription; + return mismatchDescription.add(matchState[TestSemantics]); } } @@ -253,10 +264,12 @@ Matcher hasSemantics(TestSemantics semantics, { class _IncludesNodeWith extends Matcher { const _IncludesNodeWith({ this.label, + this.textDirection, this.actions, }) : assert(label != null || actions != null); final String label; + final TextDirection textDirection; final List actions; @override @@ -279,6 +292,8 @@ class _IncludesNodeWith extends Matcher { bool checkNode(SemanticsNode node) { if (label != null && node.label != label) return false; + if (textDirection != null && node.textDirection != textDirection) + return false; if (actions != null) { final int expectedActions = actions.fold(0, (int value, SemanticsAction action) => value | action.index); final int actualActions = node.getSemanticsData().actions; @@ -302,17 +317,30 @@ class _IncludesNodeWith extends Matcher { String string = ''; if (label != null) { string += 'label "$label"'; + if (textDirection != null) + string += ' (${describeEnum(textDirection)})'; + if (actions != null) + string += ' and '; + } else if (textDirection != null) { + string += 'direction ${describeEnum(textDirection)}'; if (actions != null) string += ' and '; } if (actions != null) { - string += ' actions "${actions.join(', ')}"'; + string += 'actions "${actions.join(', ')}"'; } return string; } } -/// Asserts that a node in the semantics tree of [SemanticsTester] has [label] and [actions]. +/// Asserts that a node in the semantics tree of [SemanticsTester] has `label`, +/// `textDirection`, and `actions`. /// -/// If `null` is provided for either argument it will match against any value. -Matcher includesNodeWith({ String label, List actions }) => new _IncludesNodeWith(label: label, actions: actions); +/// If null is provided for an argument, it will match against any value. +Matcher includesNodeWith({ String label, TextDirection textDirection, List actions }) { + return new _IncludesNodeWith( + label: label, + textDirection: textDirection, + actions: actions, + ); +} diff --git a/packages/flutter/test/widgets/set_state_1_test.dart b/packages/flutter/test/widgets/set_state_1_test.dart index 5057390490..d9ccee44c0 100644 --- a/packages/flutter/test/widgets/set_state_1_test.dart +++ b/packages/flutter/test/widgets/set_state_1_test.dart @@ -15,7 +15,7 @@ class InsideState extends State { Widget build(BuildContext context) { return new Listener( onPointerDown: _handlePointerDown, - child: const Text('INSIDE') + child: const Text('INSIDE', textDirection: TextDirection.ltr), ); } @@ -38,7 +38,7 @@ class MiddleState extends State { Widget build(BuildContext context) { return new Listener( onPointerDown: _handlePointerDown, - child: widget.child + child: widget.child, ); } diff --git a/packages/flutter/test/widgets/set_state_2_test.dart b/packages/flutter/test/widgets/set_state_2_test.dart index 608858e3d5..f2570bb76b 100644 --- a/packages/flutter/test/widgets/set_state_2_test.dart +++ b/packages/flutter/test/widgets/set_state_2_test.dart @@ -11,7 +11,7 @@ void main() { final Builder inner = new Builder( builder: (BuildContext context) { log.add('inner'); - return const Text('inner'); + return const Text('inner', textDirection: TextDirection.ltr); } ); int value = 0; diff --git a/packages/flutter/test/widgets/set_state_3_test.dart b/packages/flutter/test/widgets/set_state_3_test.dart index be2bfef7cb..53a242557e 100644 --- a/packages/flutter/test/widgets/set_state_3_test.dart +++ b/packages/flutter/test/widgets/set_state_3_test.dart @@ -47,7 +47,7 @@ class Leaf extends StatefulWidget { class LeafState extends State { @override - Widget build(BuildContext context) => const Text("leaf"); + Widget build(BuildContext context) => const Text('leaf', textDirection: TextDirection.ltr); } void main() { diff --git a/packages/flutter/test/widgets/set_state_4_test.dart b/packages/flutter/test/widgets/set_state_4_test.dart index c74afb43d9..0da0619755 100644 --- a/packages/flutter/test/widgets/set_state_4_test.dart +++ b/packages/flutter/test/widgets/set_state_4_test.dart @@ -16,7 +16,7 @@ class ChangerState extends State { void test2() { setState(() async { }); } @override - Widget build(BuildContext context) => const Text('test'); + Widget build(BuildContext context) => const Text('test', textDirection: TextDirection.ltr); } void main() { diff --git a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart index a2990b3ced..a4dd0c9af6 100644 --- a/packages/flutter/test/widgets/sliver_fill_viewport_test.dart +++ b/packages/flutter/test/widgets/sliver_fill_viewport_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/widgets.dart'; void main() { testWidgets('SliverFillRemaining control test', (WidgetTester tester) async { final List children = new List.generate(20, (int i) { - return new Container(child: new Text('$i')); + return new Container(child: new Text('$i', textDirection: TextDirection.ltr)); }); await tester.pumpWidget( @@ -107,6 +107,11 @@ void main() { ' │ parentData: (can use size)\n' ' │ constraints: BoxConstraints(w=800.0, h=600.0)\n' ' │ size: Size(800.0, 600.0)\n' + ' │ textAlign: start\n' + ' │ textDirection: ltr\n' + ' │ softWrap: wrapping at box width\n' + ' │ overflow: clip\n' + ' │ maxLines: unlimited\n' ' ╘═╦══ text ═══\n' ' ║ TextSpan:\n' ' ║ \n' diff --git a/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart b/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart index b2bad6a88a..943ba1073f 100644 --- a/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart +++ b/packages/flutter/test/widgets/sliver_prototype_item_extent_test.dart @@ -16,7 +16,7 @@ class TestItem extends StatelessWidget { width: width, height: height, alignment: FractionalOffset.center, - child: new Text('Item $item'), + child: new Text('Item $item', textDirection: TextDirection.ltr), ); } } diff --git a/packages/flutter/test/widgets/sliver_semantics_test.dart b/packages/flutter/test/widgets/sliver_semantics_test.dart index 2057e820d8..846e53cfa9 100644 --- a/packages/flutter/test/widgets/sliver_semantics_test.dart +++ b/packages/flutter/test/widgets/sliver_semantics_test.dart @@ -23,58 +23,60 @@ void main() { child: new Text('Item $i'), ); }); - await tester.pumpWidget(new Directionality( - textDirection: TextDirection.ltr, - child: new MediaQuery( - data: const MediaQueryData(), - child: new CustomScrollView( - controller: scrollController, - slivers: [ - const SliverAppBar( - pinned: true, - expandedHeight: appBarExpandedHeight, - title: const Text('Semantics Test with Slivers'), - ), - new SliverList( - delegate: new SliverChildListDelegate(listChildren), - ), - ], + await tester.pumpWidget( + new Directionality( + textDirection: TextDirection.ltr, + child: new MediaQuery( + data: const MediaQueryData(), + child: new CustomScrollView( + controller: scrollController, + slivers: [ + const SliverAppBar( + pinned: true, + expandedHeight: appBarExpandedHeight, + title: const Text('Semantics Test with Slivers'), + ), + new SliverList( + delegate: new SliverChildListDelegate(listChildren), + ), + ], + ), ), ), - )); + ); // AppBar is child of node with semantic scroll actions. expect(semantics, hasSemantics( - new TestSemantics.root( - children: [ - new TestSemantics.rootChild( - id: 1, - tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], - children: [ - new TestSemantics( - id: 5, - actions: SemanticsAction.scrollUp.index, - children: [ - new TestSemantics( - id: 2, - label: 'Item 0', - ), - new TestSemantics( - id: 3, - label: 'Item 1', - ), - new TestSemantics( - id: 4, - label: 'Semantics Test with Slivers', - ), - ], - ), - ], - ) - ], - ), - ignoreRect: true, - ignoreTransform: true, + new TestSemantics.root( + children: [ + new TestSemantics.rootChild( + id: 1, + tags: [RenderSemanticsGestureHandler.useTwoPaneSemantics], + children: [ + new TestSemantics( + id: 5, + actions: SemanticsAction.scrollUp.index, + children: [ + new TestSemantics( + id: 2, + label: 'Item 0', + ), + new TestSemantics( + id: 3, + label: 'Item 1', + ), + new TestSemantics( + id: 4, + label: 'Semantics Test with Slivers', + ), + ], + ), + ], + ) + ], + ), + ignoreRect: true, + ignoreTransform: true, )); // Scroll down far enough to reach the pinned state of the app bar. @@ -177,7 +179,7 @@ void main() { return new SliverToBoxAdapter( child: new Container( height: containerHeight, - child: new Text('Item $i'), + child: new Text('Item $i', textDirection: TextDirection.ltr), ), ); }); @@ -210,10 +212,12 @@ void main() { new TestSemantics( id: 10, label: 'Item 2', + textDirection: TextDirection.ltr, ), new TestSemantics( id: 11, label: 'Item 1', + textDirection: TextDirection.ltr, ), ], ), @@ -261,22 +265,27 @@ void main() { new TestSemantics( id: 14, label: 'Item 4', + textDirection: TextDirection.ltr, ), new TestSemantics( id: 15, label: 'Item 3', + textDirection: TextDirection.ltr, ), new TestSemantics( id: 16, label: 'Item 2', + textDirection: TextDirection.ltr, ), new TestSemantics( id: 17, label: 'Item 1', + textDirection: TextDirection.ltr, ), new TestSemantics( id: 18, label: 'Item 0', + textDirection: TextDirection.ltr, ), ], ), @@ -297,7 +306,7 @@ void main() { final List listChildren = new List.generate(10, (int i) { return new Container( height: 200.0, - child: new Text('Item $i'), + child: new Text('Item $i', textDirection: TextDirection.ltr), ); }); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); @@ -380,7 +389,7 @@ void main() { return new SliverToBoxAdapter( child: new Container( height: 200.0, - child: new Text('Item $i'), + child: new Text('Item $i', textDirection: TextDirection.ltr), ), ); }); @@ -458,7 +467,7 @@ void main() { final List listChildren = new List.generate(10, (int i) { return new Container( height: 200.0, - child: new Text('Item $i'), + child: new Text('Item $i', textDirection: TextDirection.ltr), ); }); final ScrollController controller = new ScrollController(initialScrollOffset: 280.0); @@ -543,7 +552,7 @@ void main() { return new SliverToBoxAdapter( child: new Container( height: 200.0, - child: new Text('Item $i'), + child: new Text('Item $i', textDirection: TextDirection.ltr), ), ); }); @@ -625,13 +634,13 @@ void main() { final List forwardChildren = new List.generate(10, (int i) { return new Container( height: 200.0, - child: new Text('Forward Item $i'), + child: new Text('Forward Item $i', textDirection: TextDirection.ltr), ); }); final List backwardChildren = new List.generate(10, (int i) { return new Container( height: 200.0, - child: new Text('Backward Item $i'), + child: new Text('Backward Item $i', textDirection: TextDirection.ltr), ); }); await tester.pumpWidget(new Directionality( @@ -652,7 +661,7 @@ void main() { pinned: true, expandedHeight: 100.0, flexibleSpace: const FlexibleSpaceBar( - title: const Text('Backward app bar'), + title: const Text('Backward app bar', textDirection: TextDirection.ltr), ), ), new SliverAppBar( @@ -660,7 +669,7 @@ void main() { key: forwardAppBarKey, expandedHeight: 100.0, flexibleSpace: const FlexibleSpaceBar( - title: const Text('Forward app bar'), + title: const Text('Forward app bar', textDirection: TextDirection.ltr), ), ), new SliverList( diff --git a/packages/flutter/test/widgets/slivers_block_global_key_test.dart b/packages/flutter/test/widgets/slivers_block_global_key_test.dart index b1df7cdca2..696d984d1c 100644 --- a/packages/flutter/test/widgets/slivers_block_global_key_test.dart +++ b/packages/flutter/test/widgets/slivers_block_global_key_test.dart @@ -19,7 +19,7 @@ class _GenerationTextState extends State { _GenerationTextState() : generation = globalGeneration; final int generation; @override - Widget build(BuildContext context) => new Text('${widget.value}:$generation '); + Widget build(BuildContext context) => new Text('${widget.value}:$generation ', textDirection: TextDirection.ltr); } Future test(WidgetTester tester, double offset, List keys) { diff --git a/packages/flutter/test/widgets/stack_test.dart b/packages/flutter/test/widgets/stack_test.dart index 76181d3b20..31c984060c 100644 --- a/packages/flutter/test/widgets/stack_test.dart +++ b/packages/flutter/test/widgets/stack_test.dart @@ -150,7 +150,7 @@ void main() { itemsPainted = []; final List items = new List.generate(itemCount, (int i) { return new CustomPaint( - child: new Text('$i'), + child: new Text('$i', textDirection: TextDirection.ltr), painter: new TestCallbackPainter( onPaint: () { itemsPainted.add(i); } ) @@ -180,7 +180,7 @@ void main() { Widget buildFrame(int index) { itemsTapped = []; final List items = new List.generate(itemCount, (int i) { - return new GestureDetector(child: new Text('$i'), onTap: () { itemsTapped.add(i); }); + return new GestureDetector(child: new Text('$i', textDirection: TextDirection.ltr), onTap: () { itemsTapped.add(i); }); }); return new Center(child: new IndexedStack(children: items, key: key, index: index)); } diff --git a/packages/flutter/test/widgets/syncing_test.dart b/packages/flutter/test/widgets/syncing_test.dart index 13bb2b8a47..033cc7c6a5 100644 --- a/packages/flutter/test/widgets/syncing_test.dart +++ b/packages/flutter/test/widgets/syncing_test.dart @@ -112,8 +112,8 @@ void main() { }); testWidgets('swap instances around', (WidgetTester tester) async { - final Widget a = const TestWidget(persistentState: 0x61, syncedState: 0x41, child: const Text('apple')); - final Widget b = const TestWidget(persistentState: 0x62, syncedState: 0x42, child: const Text('banana')); + final Widget a = const TestWidget(persistentState: 0x61, syncedState: 0x41, child: const Text('apple', textDirection: TextDirection.ltr)); + final Widget b = const TestWidget(persistentState: 0x62, syncedState: 0x42, child: const Text('banana', textDirection: TextDirection.ltr)); await tester.pumpWidget(new Column()); final GlobalKey keyA = new GlobalKey(); diff --git a/packages/flutter/test/widgets/table_test.dart b/packages/flutter/test/widgets/table_test.dart index 4662a5c16e..93ea64e15a 100644 --- a/packages/flutter/test/widgets/table_test.dart +++ b/packages/flutter/test/widgets/table_test.dart @@ -20,8 +20,9 @@ class TestStatefulWidgetState extends State { void main() { testWidgets('Table widget - control test', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -40,7 +41,7 @@ void main() { ), ] ) - ); + )); final RenderBox boxA = tester.renderObject(find.text('AAAAAA')); final RenderBox boxD = tester.renderObject(find.text('D')); final RenderBox boxG = tester.renderObject(find.text('G')); @@ -51,8 +52,9 @@ void main() { }); testWidgets('Table widget - changing table dimensions', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -71,13 +73,14 @@ void main() { ), ] ) - ); + )); final RenderBox boxA1 = tester.renderObject(find.text('A')); final RenderBox boxG1 = tester.renderObject(find.text('G')); expect(boxA1, isNotNull); expect(boxG1, isNotNull); - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -91,7 +94,7 @@ void main() { ), ] ) - ); + )); final RenderBox boxA2 = tester.renderObject(find.text('a')); final RenderBox boxG2 = tester.renderObject(find.text('g')); expect(boxA2, isNotNull); @@ -101,8 +104,9 @@ void main() { }); testWidgets('Table widget - repump test', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -121,9 +125,10 @@ void main() { ), ] ) - ); - await tester.pumpWidget( - new Table( + )); + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -142,7 +147,7 @@ void main() { ), ] ) - ); + )); final RenderBox boxA = tester.renderObject(find.text('AAA')); final RenderBox boxD = tester.renderObject(find.text('D')); final RenderBox boxG = tester.renderObject(find.text('G')); @@ -153,8 +158,9 @@ void main() { }); testWidgets('Table widget - intrinsic sizing test', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( defaultColumnWidth: const IntrinsicColumnWidth(), children: [ new TableRow( @@ -174,7 +180,7 @@ void main() { ), ] ) - ); + )); final RenderBox boxA = tester.renderObject(find.text('AAA')); final RenderBox boxD = tester.renderObject(find.text('D')); final RenderBox boxG = tester.renderObject(find.text('G')); @@ -186,8 +192,9 @@ void main() { }); testWidgets('Table widget - intrinsic sizing test, resizing', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( defaultColumnWidth: const IntrinsicColumnWidth(), children: [ new TableRow( @@ -207,9 +214,10 @@ void main() { ), ] ) - ); - await tester.pumpWidget( - new Table( + )); + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( defaultColumnWidth: const IntrinsicColumnWidth(), children: [ new TableRow( @@ -229,7 +237,7 @@ void main() { ), ] ) - ); + )); final RenderBox boxA = tester.renderObject(find.text('A')); final RenderBox boxD = tester.renderObject(find.text('D')); final RenderBox boxG = tester.renderObject(find.text('G')); @@ -241,8 +249,9 @@ void main() { }); testWidgets('Table widget - intrinsic sizing test, changing column widths', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -261,9 +270,10 @@ void main() { ), ] ) - ); - await tester.pumpWidget( - new Table( + )); + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( defaultColumnWidth: const IntrinsicColumnWidth(), children: [ new TableRow( @@ -283,7 +293,7 @@ void main() { ), ] ) - ); + )); final RenderBox boxA = tester.renderObject(find.text('AAA')); final RenderBox boxD = tester.renderObject(find.text('D')); final RenderBox boxG = tester.renderObject(find.text('G')); @@ -296,8 +306,9 @@ void main() { testWidgets('Table widget - moving test', (WidgetTester tester) async { final List contexts = []; - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( key: const ValueKey(1), @@ -317,9 +328,10 @@ void main() { ), ] ) - ); - await tester.pumpWidget( - new Table( + )); + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( children: [ @@ -339,14 +351,15 @@ void main() { ), ] ) - ); + )); expect(contexts.length, equals(2)); expect(contexts[0], equals(contexts[1])); }); testWidgets('Table widget - keyed rows', (WidgetTester tester) async { - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( key: const ValueKey(1), @@ -364,7 +377,7 @@ void main() { ), ] ) - ); + )); final TestStatefulWidgetState state11 = tester.state(find.byKey(const ValueKey(11))); final TestStatefulWidgetState state12 = tester.state(find.byKey(const ValueKey(12))); @@ -376,8 +389,9 @@ void main() { expect(state21.mounted, isTrue); expect(state22.mounted, isTrue); - await tester.pumpWidget( - new Table( + await tester.pumpWidget(new Directionality( + textDirection: TextDirection.ltr, + child: new Table( children: [ new TableRow( key: const ValueKey(2), @@ -388,7 +402,7 @@ void main() { ), ] ) - ); + )); expect(state11.mounted, isFalse); expect(state12.mounted, isFalse); @@ -499,27 +513,30 @@ void main() { testWidgets('Table widget diagnostics', (WidgetTester tester) async { GlobalKey key0; - final Table table = new Table( + final Widget table = new Directionality( + textDirection: TextDirection.ltr, + child: new Table( key: key0 = new GlobalKey(), defaultColumnWidth: const IntrinsicColumnWidth(), children: [ new TableRow( children: [ - const Text('A'), const Text('B'), const Text('C') - ] + const Text('A'), const Text('B'), const Text('C'), + ], ), new TableRow( children: [ - const Text('D'), const Text('EEE'), const Text('F') - ] + const Text('D'), const Text('EEE'), const Text('F'), + ], ), new TableRow( children: [ - const Text('G'), const Text('H'), const Text('III') - ] + const Text('G'), const Text('H'), const Text('III'), + ], ), - ] - ); + ], + ), + ); await tester.pumpWidget(table); final RenderObjectElement element = key0.currentContext; expect(element, hasAGoodToStringDeep); diff --git a/packages/flutter/test/widgets/text_test.dart b/packages/flutter/test/widgets/text_test.dart index a58f79a264..89c752a665 100644 --- a/packages/flutter/test/widgets/text_test.dart +++ b/packages/flutter/test/widgets/text_test.dart @@ -10,7 +10,7 @@ void main() { await tester.pumpWidget(const MediaQuery( data: const MediaQueryData(textScaleFactor: 1.5), child: const Center( - child: const Text('Hello') + child: const Text('Hello', textDirection: TextDirection.ltr) ) )); @@ -19,7 +19,7 @@ void main() { expect(text.textScaleFactor, 1.5); await tester.pumpWidget(const Center( - child: const Text('Hello') + child: const Text('Hello', textDirection: TextDirection.ltr) )); text = tester.firstWidget(find.byType(RichText)); @@ -27,7 +27,7 @@ void main() { expect(text.textScaleFactor, 1.0); await tester.pumpWidget(const Center( - child: const Text('Hello', textScaleFactor: 3.0) + child: const Text('Hello', textScaleFactor: 3.0, textDirection: TextDirection.ltr) )); text = tester.firstWidget(find.byType(RichText)); diff --git a/packages/flutter/test/widgets/tracking_scroll_controller_test.dart b/packages/flutter/test/widgets/tracking_scroll_controller_test.dart index 7ebc4050be..3ff890bd6c 100644 --- a/packages/flutter/test/widgets/tracking_scroll_controller_test.dart +++ b/packages/flutter/test/widgets/tracking_scroll_controller_test.dart @@ -17,13 +17,15 @@ void main() { child: new PageView.builder( itemBuilder: (BuildContext context, int index) { return new ListView( - controller: controller, - children: new List.generate( - 10, - (int i) => new Container( - height: listItemHeight, - child: new Text("Page$index-Item$i")), - ).toList()); + controller: controller, + children: new List.generate( + 10, + (int i) => new Container( + height: listItemHeight, + child: new Text('Page$index-Item$i'), + ), + ).toList(), + ); }, ), ), @@ -37,8 +39,7 @@ void main() { controller.jumpTo(listItemHeight + 10); await (tester.pumpAndSettle()); - await tester.fling( - find.text('Page0-Item1'), const Offset(-100.0, 0.0), 10000.0); + await tester.fling(find.text('Page0-Item1'), const Offset(-100.0, 0.0), 10000.0); await (tester.pumpAndSettle()); expect(find.text('Page0-Item1'), findsNothing); @@ -46,8 +47,7 @@ void main() { expect(find.text('Page2-Item0'), findsNothing); expect(find.text('Page2-Item1'), findsNothing); - await tester.fling( - find.text('Page1-Item1'), const Offset(-100.0, 0.0), 10000.0); + await tester.fling(find.text('Page1-Item1'), const Offset(-100.0, 0.0), 10000.0); await (tester.pumpAndSettle()); expect(find.text('Page0-Item1'), findsNothing); @@ -55,7 +55,7 @@ void main() { expect(find.text('Page2-Item0'), findsNothing); expect(find.text('Page2-Item1'), findsOneWidget); - await tester.pumpWidget(const Text("Another page")); + await tester.pumpWidget(const Text('Another page', textDirection: TextDirection.ltr)); expect(controller.initialScrollOffset, 0.0); }); diff --git a/packages/flutter/test/widgets/transitions_test.dart b/packages/flutter/test/widgets/transitions_test.dart index e1e410df05..875d562d36 100644 --- a/packages/flutter/test/widgets/transitions_test.dart +++ b/packages/flutter/test/widgets/transitions_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('toString control test', (WidgetTester tester) async { final Widget widget = const FadeTransition( opacity: kAlwaysCompleteAnimation, - child: const Text('Ready'), + child: const Text('Ready', textDirection: TextDirection.ltr), ); expect(widget.toString, isNot(throwsException)); }); @@ -57,7 +57,7 @@ void main() { final DecoratedBoxTransition transitionUnderTest = new DecoratedBoxTransition( decoration: decorationTween.animate(controller), - child: const Text("Doesn't matter"), + child: const Text('Doesn\'t matter', textDirection: TextDirection.ltr), ); await tester.pumpWidget(transitionUnderTest); @@ -109,7 +109,7 @@ void main() { new DecoratedBoxTransition( decoration: curvedDecorationAnimation, position: DecorationPosition.foreground, - child: const Text("Doesn't matter"), + child: const Text('Doesn\'t matter', textDirection: TextDirection.ltr), ); await tester.pumpWidget(transitionUnderTest); diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index e10deba75f..830772fb67 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -13,9 +13,9 @@ void main() { await tester.pumpWidget( new Stack( children: [ - const Text('a'), - const Text('b'), - const Text('c'), + const Text('a', textDirection: TextDirection.ltr), + const Text('b', textDirection: TextDirection.ltr), + const Text('c', textDirection: TextDirection.ltr), ], ), ); @@ -25,9 +25,9 @@ void main() { selectButtonBuilder: null, child: new Stack( children: [ - const Text('a'), - const Text('b'), - const Text('c'), + const Text('a', textDirection: TextDirection.ltr), + const Text('b', textDirection: TextDirection.ltr), + const Text('c', textDirection: TextDirection.ltr), ], ), ), @@ -179,7 +179,7 @@ void main() { expect(didLongPress, isFalse); didLongPress = true; }, - child: const Text('target'), + child: const Text('target', textDirection: TextDirection.ltr), ), ), ); @@ -202,9 +202,9 @@ void main() { top: 0.0, width: width, height: 100.0, - child: new Text(width.toString()), + child: new Text(width.toString(), textDirection: TextDirection.ltr), ), - ] + ], ); } await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/wrap_test.dart b/packages/flutter/test/widgets/wrap_test.dart index 9440522bb2..fb6ec9f336 100644 --- a/packages/flutter/test/widgets/wrap_test.dart +++ b/packages/flutter/test/widgets/wrap_test.dart @@ -817,7 +817,7 @@ void main() { child: new Wrap( textDirection: TextDirection.ltr, children: [ - const Text('X'), + const Text('X', textDirection: TextDirection.ltr), ], ), ), diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index f544df7844..2f84ec7b08 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -269,20 +269,22 @@ abstract class TestWidgetsFlutterBinding extends BindingBase static const TextStyle _kMessageStyle = const TextStyle( color: const Color(0xFF917FFF), - fontSize: 40.0 + fontSize: 40.0, ); static final Widget _kPreTestMessage = const Center( child: const Text( 'Test starting...', - style: _kMessageStyle + style: _kMessageStyle, + textDirection: TextDirection.ltr, ) ); static final Widget _kPostTestMessage = const Center( child: const Text( 'Test finished.', - style: _kMessageStyle + style: _kMessageStyle, + textDirection: TextDirection.ltr, ) ); @@ -1019,7 +1021,8 @@ class _LiveTestRenderView extends RenderView { _label = null; return; } - _label ??= new TextPainter(textAlign: TextAlign.left); + // TODO(ianh): Figure out if the test name is actually RTL. + _label ??= new TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr); _label.text = new TextSpan(text: value, style: _labelStyle); _label.layout(); if (onNeedPaint != null) diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index e900de869c..c9970d3a4f 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -5,10 +5,15 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +const List fooBarTexts = const [ + const Text('foo', textDirection: TextDirection.ltr), + const Text('bar', textDirection: TextDirection.ltr), +]; + void main() { group('findsOneWidget', () { testWidgets('finds exactly one widget', (WidgetTester tester) async { - await tester.pumpWidget(const Text('foo')); + await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr)); expect(find.text('foo'), findsOneWidget); }); @@ -34,7 +39,7 @@ void main() { }); testWidgets('fails with a descriptive message', (WidgetTester tester) async { - await tester.pumpWidget(const Text('foo')); + await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr)); TestFailure failure; try { @@ -52,7 +57,7 @@ void main() { }); testWidgets('fails with a descriptive message when skipping', (WidgetTester tester) async { - await tester.pumpWidget(const Text('foo')); + await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr)); TestFailure failure; try { @@ -70,7 +75,7 @@ void main() { }); testWidgets('pumping', (WidgetTester tester) async { - await tester.pumpWidget(const Text('foo')); + await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr)); int count; final AnimationController test = new AnimationController( @@ -106,7 +111,7 @@ void main() { group('find.byElementPredicate', () { testWidgets('fails with a custom description in the message', (WidgetTester tester) async { - await tester.pumpWidget(const Text('foo')); + await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr)); final String customDescription = 'custom description'; TestFailure failure; @@ -123,7 +128,7 @@ void main() { group('find.byWidgetPredicate', () { testWidgets('fails with a custom description in the message', (WidgetTester tester) async { - await tester.pumpWidget(const Text('foo')); + await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr)); final String customDescription = 'custom description'; TestFailure failure; @@ -143,7 +148,7 @@ void main() { await tester.pumpWidget(new Row( textDirection: TextDirection.ltr, children: [ - new Column(children: [const Text('foo'), const Text('bar')]), + new Column(children: fooBarTexts), ], )); @@ -157,8 +162,8 @@ void main() { await tester.pumpWidget(new Row( textDirection: TextDirection.ltr, children: [ - new Column(children: [const Text('foo'), const Text('bar')]), - new Column(children: [const Text('foo'), const Text('bar')]), + new Column(children: fooBarTexts), + new Column(children: fooBarTexts), ], )); @@ -172,8 +177,8 @@ void main() { await tester.pumpWidget(new Row( textDirection: TextDirection.ltr, children: [ - new Column(children: [const Text('foo')]), - const Text('bar'), + new Column(children: [const Text('foo', textDirection: TextDirection.ltr)]), + const Text('bar', textDirection: TextDirection.ltr), ], )); @@ -198,7 +203,7 @@ void main() { await tester.pumpWidget(new Row( textDirection: TextDirection.ltr, children: [ - new Column(children: [const Text('foo'), const Text('bar')]), + new Column(children: fooBarTexts), ], )); @@ -212,7 +217,7 @@ void main() { await tester.pumpWidget(new Row( textDirection: TextDirection.ltr, children: [ - new Column(children: [const Text('foo'), const Text('bar')]), + new Column(children: fooBarTexts), ], ));