TextPainter RTL (#11888)

diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index a0483e3..7c8fb53 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 0000000..6715517
--- /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 0000000..d26ee5a
--- /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 1ad8675..93f7f5b 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 864e8f1..e1e068d 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 b2f67f3..83d3bb5 100644
--- a/examples/layers/raw/hello_world.dart
+++ b/examples/layers/raw/hello_world.dart
@@ -11,7 +11,9 @@
   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 3c3e92f..15029c7 100644
--- a/examples/layers/raw/text.dart
+++ b/examples/layers/raw/text.dart
@@ -52,7 +52,13 @@
 
 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 dc38d85..0084eba 100644
--- a/examples/layers/rendering/flex_layout.dart
+++ b/examples/layers/rendering/flex_layout.dart
@@ -14,24 +14,36 @@
 
   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 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 2c772d1..69a4dc2 100644
--- a/examples/layers/rendering/hello_world.dart
+++ b/examples/layers/rendering/hello_world.dart
@@ -16,7 +16,14 @@
       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 efa793b..891e509 100644
--- a/examples/layers/rendering/touch_input.dart
+++ b/examples/layers/rendering/touch_input.dart
@@ -104,8 +104,9 @@
   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 @@
     children: <RenderBox>[
       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 0000000..b516506
--- /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 0000000..979180a
--- /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 0000000..c46b114
--- /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 0000000..e289f2b
--- /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 0000000..1ba1d38
--- /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 0000000..09065d0
--- /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 0000000..c6bcf0a
--- /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 0000000..1d1f6fe
--- /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 0000000..df8932b
--- /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 0000000..78ceb99
--- /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 0000000..dae629f
--- /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 0000000..ed5efc1
--- /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 0000000..fa6f4ce
--- /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 0000000..2744ebb
--- /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 0000000..44f8422
--- /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 0000000..0d6cb4e
--- /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 0000000..f7540f8
--- /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 0000000..4171497
--- /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 0000000..6cb3bd5
--- /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 0000000..e24858d
--- /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 0000000..3fc1a05
--- /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 1ad8675..93f7f5b 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 71ba4da..8b2a404 100644
--- a/examples/layers/widgets/spinning_mixed.dart
+++ b/examples/layers/widgets/spinning_mixed.dart
@@ -37,40 +37,43 @@
 void attachWidgetTreeToRenderTree(RenderProxyBox container) {
   element = new RenderObjectToWidgetAdapter<RenderBox>(
     container: container,
-    child: new Container(
-      height: 300.0,
-      child: new Column(
-        children: <Widget>[
-          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: <Widget>[
-                  new RaisedButton(
-                    child: new Row(
-                      children: <Widget>[
-                        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: <Widget>[
+            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: <Widget>[
+                    new RaisedButton(
+                      child: new Row(
+                        children: <Widget>[
+                          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 5d06b77..18c2099 100644
--- a/packages/flutter/lib/foundation.dart
+++ b/packages/flutter/lib/foundation.dart
@@ -46,3 +46,4 @@
 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 0000000..a51e907
--- /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:
+///
+///  * <http://unicode.org/reports/tr9/>, 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 db40342..426f9ac 100644
--- a/packages/flutter/lib/src/material/slider.dart
+++ b/packages/flutter/lib/src/material/slider.dart
@@ -295,6 +295,7 @@
     @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 @@
        _textTheme = textTheme,
        _onChanged = onChanged,
        _textDirection = textDirection {
-    this.label = label;
+    _updateLabelPainter();
     final GestureArenaTeam team = new GestureArenaTeam();
     _drag = new HorizontalDragGestureRecognizer()
       ..team = team
@@ -356,19 +357,7 @@
     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 @@
   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 @@
           ..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 0f3e013..528cad1 100644
--- a/packages/flutter/lib/src/material/text_field.dart
+++ b/packages/flutter/lib/src/material/text_field.dart
@@ -69,8 +69,8 @@
   /// 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 @@
     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 @@
     this.onSubmitted,
     this.inputFormatters,
   }) : assert(keyboardType != null),
+       assert(textAlign != null),
        assert(autofocus != null),
        assert(obscureText != null),
        assert(autocorrect != null),
@@ -125,6 +126,8 @@
   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 cca45f0..f1ce572 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -356,7 +356,8 @@
     // 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 d647613..d8a027b 100644
--- a/packages/flutter/lib/src/painting/flutter_logo.dart
+++ b/packages/flutter/lib/src/painting/flutter_logo.dart
@@ -247,9 +247,10 @@
           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 03a3ca9..3fee2d9 100644
--- a/packages/flutter/lib/src/painting/text_painter.dart
+++ b/packages/flutter/lib/src/painting/text_painter.dart
@@ -33,21 +33,26 @@
 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 @@
   /// 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 @@
   /// 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 @@
     _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 @@
 
   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 @@
   /// 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 @@
   /// 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 @@
   }
 
   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 2c8cdf2..747307d 100644
--- a/packages/flutter/lib/src/painting/text_style.dart
+++ b/packages/flutter/lib/src/painting/text_style.dart
@@ -369,6 +369,7 @@
   /// 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 @@
     assert(maxLines == null || maxLines > 0);
     return new ui.ParagraphStyle(
       textAlign: textAlign,
+      textDirection: textDirection,
       fontWeight: fontWeight,
       fontStyle: fontStyle,
       fontFamily: fontFamily,
@@ -418,7 +420,7 @@
   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 994e129..3439e01 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -87,6 +87,10 @@
 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 @@
   /// ViewportOffset.zero] if you have no need for scrolling.
   RenderEditable({
     TextSpan text,
-    TextAlign textAlign,
+    @required TextDirection textDirection,
+    TextAlign textAlign: TextAlign.start,
     Color cursorColor,
     ValueNotifier<bool> showCursor,
     int maxLines: 1,
@@ -107,10 +112,17 @@
     @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<bool>(false),
        _maxLines = maxLines,
@@ -146,7 +158,7 @@
     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 @@
   }
 
   /// 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 aa49614..3f93ad1 100644
--- a/packages/flutter/lib/src/rendering/flex.dart
+++ b/packages/flutter/lib/src/rendering/flex.dart
@@ -993,12 +993,13 @@
       }
       context.canvas.drawRect(markerRect, _debugMarkerPaint);
 
-      _debugMarkerLabel ??= new TextPainter();
-      _debugMarkerLabel.text = new TextSpan( // this is a no-op if the label hasn't changed
+      _debugMarkerLabel ??= new TextPainter()
+        ..textDirection = TextDirection.ltr; // This label is in English.
+      _debugMarkerLabel.text = new TextSpan( // This is a no-op if the label hasn't changed.
         text: label,
         style: _debugMarkerTextStyle,
       );
-      _debugMarkerLabel.layout(); // this is a no-op if the label hasn't changed
+      _debugMarkerLabel.layout(); // This is a no-op if the label hasn't changed.
 
       // TODO(ianh): RTL support
       switch (direction) {
diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart
index 72a68d9..5899eb3 100644
--- a/packages/flutter/lib/src/rendering/object.dart
+++ b/packages/flutter/lib/src/rendering/object.dart
@@ -2867,11 +2867,11 @@
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder description) {
     description.add(new DiagnosticsProperty<dynamic>('creator', debugCreator, defaultValue: null));
-    description.add(new DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? "can use size" : null));
-    description.add(new DiagnosticsProperty<Constraints>('constraints', constraints));
+    description.add(new DiagnosticsProperty<ParentData>('parentData', parentData, tooltip: _debugCanParentUseSize == true ? 'can use size' : null, ifNull: 'MISSING'));
+    description.add(new DiagnosticsProperty<Constraints>('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<OffsetLayer>('layer', _layer, defaultValue: null));
-    description.add(new DiagnosticsProperty<SemanticsNode>('_semantics', _semantics, defaultValue: null));
+    description.add(new DiagnosticsProperty<SemanticsNode>('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 14d9f61..83b1304 100644
--- a/packages/flutter/lib/src/rendering/paragraph.dart
+++ b/packages/flutter/lib/src/rendering/paragraph.dart
@@ -32,19 +32,22 @@
 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 @@
        _textPainter = new TextPainter(
          text: text,
          textAlign: textAlign,
+         textDirection: textDirection,
          textScaleFactor: textScaleFactor,
          maxLines: maxLines,
          ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
@@ -84,12 +88,35 @@
   /// 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 @@
   }
 
   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 @@
           _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 @@
 
   void _annotate(SemanticsNode node) {
     node.label = text.toPlainText();
+    node.textDirection = textDirection;
   }
 
   @override
   List<DiagnosticsNode> debugDescribeChildren() {
     return <DiagnosticsNode>[text.toDiagnosticsNode(name: 'text', style: DiagnosticsTreeStyle.transition)];
   }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder description) {
+    super.debugFillProperties(description);
+    description.add(new EnumProperty<TextAlign>('textAlign', textAlign));
+    description.add(new EnumProperty<TextDirection>('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<TextOverflow>('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 9b6d0cc..56492c6 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -3114,17 +3114,21 @@
   /// 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 @@
     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 @@
       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 f1c27b6..17e5c52 100644
--- a/packages/flutter/lib/src/rendering/semantics.dart
+++ b/packages/flutter/lib/src/rendering/semantics.dart
@@ -92,16 +92,20 @@
   /// 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 @@
   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 @@
     }
     if (label.isNotEmpty)
       buffer.write('; "$label"');
+    if (textDirection != null)
+      buffer.write('; $textDirection');
     buffer.write(')');
     return buffer.toString();
   }
@@ -161,13 +172,14 @@
     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 @@
   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 @@
     }
   }
 
+  /// 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<SemanticsTag> _tags = new Set<SemanticsTag>();
 
   /// Tags the [SemanticsNode] with [tag].
@@ -385,6 +410,7 @@
     if (hadInheritedMergeAllDescendantsIntoThisNode)
       _inheritedMergeAllDescendantsIntoThisNodeValue = true;
     _label = '';
+    _textDirection = null;
     _tags.clear();
     _markDirty();
   }
@@ -598,18 +624,31 @@
     int flags = _flags;
     int actions = _actions;
     String label = _label;
+    TextDirection textDirection = _textDirection;
     final Set<SemanticsTag> tags = new Set<SemanticsTag>.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 @@
       flags: flags,
       actions: actions,
       label: label,
+      textDirection: textDirection,
       rect: rect,
       transform: transform,
       tags: tags,
@@ -650,6 +690,7 @@
       actions: data.actions,
       rect: data.rect,
       label: data.label,
+      textDirection: data.textDirection,
       transform: data.transform?.storage ?? _kIdentityTransform,
       children: children,
     );
@@ -696,6 +737,8 @@
       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 de1f752..037c67c 100644
--- a/packages/flutter/lib/src/rendering/sliver_grid.dart
+++ b/packages/flutter/lib/src/rendering/sliver_grid.dart
@@ -553,7 +553,8 @@
     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 8a75f89..1d8c22c 100644
--- a/packages/flutter/lib/src/widgets/banner.dart
+++ b/packages/flutter/lib/src/widgets/banner.dart
@@ -41,13 +41,15 @@
 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 @@
   /// 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 @@
     _textPainter = new TextPainter(
       text: new TextSpan(style: textStyle, text: message),
       textAlign: TextAlign.center,
+      textDirection: textDirection,
     );
     _prepared = true;
   }
@@ -169,6 +182,7 @@
     Key key,
     this.child,
     @required this.message,
+    this.textDirection,
     @required this.location,
     this.color: _kColor,
     this.textStyle: _kTextStyle,
@@ -184,6 +198,18 @@
   /// 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 @@
     return new CustomPaint(
       foregroundPainter: new BannerPainter(
         message: message,
+        textDirection: textDirection ?? Directionality.of(context),
         location: location,
         color: color,
         textStyle: textStyle,
@@ -210,6 +237,7 @@
   void debugFillProperties(DiagnosticPropertiesBuilder description) {
     super.debugFillProperties(description);
     description.add(new StringProperty('message', message, showName: false));
+    description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
     description.add(new EnumProperty<BannerLocation>('location', location));
     description.add(new DiagnosticsProperty<Color>('color', color, showName: false));
     textStyle?.debugFillProperties(description, prefix: 'text ');
@@ -236,7 +264,9 @@
       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 2090de2..250f4b5 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -3704,20 +3704,25 @@
 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 @@
   /// 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 @@
 
   @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 @@
     renderObject
       ..text = text
       ..textAlign = textAlign
+      ..textDirection = textDirection ?? Directionality.of(context)
       ..softWrap = softWrap
       ..overflow = overflow
       ..textScaleFactor = textScaleFactor
@@ -4289,6 +4314,7 @@
     this.checked,
     this.selected,
     this.label,
+    this.textDirection,
   }) : assert(container != null),
        super(key: key, child: child);
 
@@ -4317,8 +4343,20 @@
   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 @@
       checked: checked,
       selected: selected,
       label: label,
+      textDirection: _getTextDirection(context),
     );
   }
 
@@ -4335,7 +4374,8 @@
       ..container = container
       ..checked = checked
       ..selected = selected
-      ..label = label;
+      ..label = label
+      ..textDirection = _getTextDirection(context);
   }
 
   @override
@@ -4344,7 +4384,8 @@
     description.add(new DiagnosticsProperty<bool>('container', container));
     description.add(new DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
     description.add(new DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
-    description.add(new StringProperty('label', label));
+    description.add(new StringProperty('label', label, defaultValue: ''));
+    description.add(new EnumProperty<TextDirection>('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 6aba833..aba7051 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -144,8 +144,8 @@
   /// 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 @@
     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 @@
        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 @@
   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 @@
     description.add(new DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
     style?.debugFillProperties(description);
     description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
+    description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
     description.add(new DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
     description.add(new IntProperty('maxLines', maxLines, defaultValue: 1));
     description.add(new DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
@@ -575,6 +595,12 @@
     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 @@
             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 @@
     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 @@
   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 @@
       selectionColor: selectionColor,
       textScaleFactor: textScaleFactor,
       textAlign: textAlign,
+      textDirection: textDirection,
       selection: value.selection,
       offset: offset,
       onSelectionChanged: onSelectionChanged,
@@ -669,6 +700,7 @@
       ..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 f92a571..d652c9f 100644
--- a/packages/flutter/lib/src/widgets/icon.dart
+++ b/packages/flutter/lib/src/widgets/icon.dart
@@ -17,6 +17,10 @@
 /// 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 @@
 
   @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 @@
         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 f6e1c2e..779aeb9 100644
--- a/packages/flutter/lib/src/widgets/semantics_debugger.dart
+++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart
@@ -228,15 +228,29 @@
   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 @@
   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 ca2599f..916f148 100644
--- a/packages/flutter/lib/src/widgets/text.dart
+++ b/packages/flutter/lib/src/widgets/text.dart
@@ -200,6 +200,7 @@
     Key key,
     this.style,
     this.textAlign,
+    this.textDirection,
     this.softWrap,
     this.overflow,
     this.textScaleFactor,
@@ -220,6 +221,21 @@
   /// 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 @@
     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 26a6314..e9bd1f2 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -361,12 +361,14 @@
     @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 @@
       overlayRect: overlayRect,
       selected: new _TransformedRect(selected),
       tooltip: selected.toString(),
+      textDirection: TextDirection.ltr,
       candidates: candidates,
     );
 
@@ -497,15 +500,22 @@
     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 @@
         ..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 75f1259..086a248 100644
--- a/packages/flutter/test/cupertino/button_test.dart
+++ b/packages/flutter/test/cupertino/button_test.dart
@@ -14,7 +14,7 @@
 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 @@
   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 @@
 
   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 @@
   });
 
   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 @@
   });
 
   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 @@
     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 @@
   });
 
   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 @@
   });
 
   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 @@
 
   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 @@
     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 4cfa288..f7ef03a 100644
--- a/packages/flutter/test/cupertino/dialog_test.dart
+++ b/packages/flutter/test/cupertino/dialog_test.dart
@@ -64,10 +64,10 @@
   });
 
   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 @@
   });
 
   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 @@
   });
 
   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 @@
     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 180116a..213ad33 100644
--- a/packages/flutter/test/material/buttons_test.dart
+++ b/packages/flutter/test/material/buttons_test.dart
@@ -14,14 +14,17 @@
   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 @@
     );
 
     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 @@
     );
 
     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 @@
     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 1d5466e..05a32f9 100644
--- a/packages/flutter/test/material/circle_avatar_test.dart
+++ b/packages/flutter/test/material/circle_avatar_test.dart
@@ -10,7 +10,7 @@
   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 @@
   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 @@
       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 @@
     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 e4a02b7..94fd279 100644
--- a/packages/flutter/test/material/expand_icon_test.dart
+++ b/packages/flutter/test/material/expand_icon_test.dart
@@ -10,14 +10,12 @@
     bool expanded = false;
 
     await tester.pumpWidget(
-      new Material(
-        child: new Center(
+      wrap(
           child: new ExpandIcon(
             onPressed: (bool isExpanded) {
               expanded = !expanded;
             }
           )
-        )
       )
     );
 
@@ -31,12 +29,10 @@
 
   testWidgets('ExpandIcon disabled', (WidgetTester tester) async {
     await tester.pumpWidget(
-      const Material(
-        child: const Center(
+      wrap(
           child: const ExpandIcon(
             onPressed: null
           )
-        )
       )
     );
 
@@ -48,27 +44,23 @@
     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 @@
     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 b8676d5..9977316 100644
--- a/packages/flutter/test/material/feedback_test.dart
+++ b/packages/flutter/test/material/feedback_test.dart
@@ -154,7 +154,7 @@
     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 e4b2b46..c14eac3 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_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 5cf53a6..2bce526 100644
--- a/packages/flutter/test/material/grid_title_test.dart
+++ b/packages/flutter/test/material/grid_title_test.dart
@@ -38,7 +38,12 @@
     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 6d7a2f5..7813094 100644
--- a/packages/flutter/test/material/icon_button_test.dart
+++ b/packages/flutter/test/material/icon_button_test.dart
@@ -24,13 +24,11 @@
 
   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 @@
 
   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 @@
 
   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 @@
 
   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 @@
 
   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 @@
     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 @@
     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 @@
     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 b60a67f..e5b784f 100644
--- a/packages/flutter/test/material/refresh_indicator_test.dart
+++ b/packages/flutter/test/material/refresh_indicator_test.dart
@@ -23,9 +23,8 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 2cfa2d0..f2ea7f9 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -1260,7 +1260,7 @@
   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 @@
   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 @@
   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 f592e81..0ae17a6 100644
--- a/packages/flutter/test/material/tooltip_test.dart
+++ b/packages/flutter/test/material/tooltip_test.dart
@@ -32,34 +32,37 @@
   testWidgets('Does tooltip end up in the right place - center', (WidgetTester tester) async {
     final GlobalKey key = new GlobalKey();
     await tester.pumpWidget(
-      new Overlay(
-        initialEntries: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
   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: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
   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: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
   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: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
   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: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
   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: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
   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: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
 
     final GlobalKey key = new GlobalKey();
     await tester.pumpWidget(
-      new Overlay(
-        initialEntries: <OverlayEntry>[
-          new OverlayEntry(
-            builder: (BuildContext context) {
-              return new Stack(
-                children: <Widget>[
-                  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: <OverlayEntry>[
+            new OverlayEntry(
+              builder: (BuildContext context) {
+                return new Stack(
+                  children: <Widget>[
+                    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 @@
 
   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 3db5ea4..7a86425 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 @@
 
 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 @@
     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 @@
           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 29d5a65..438c4f3 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:test/test.dart';
 
 void main() {
-  test("TextStyle control test", () {
+  test('TextStyle control test', () {
     expect(
       const TextStyle(inherit: false).toString(),
       equals('TextStyle(inherit: false, <no style specified>)'),
@@ -117,9 +117,17 @@
 
     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 8b2d556..d6b81c0 100644
--- a/packages/flutter/test/rendering/box_test.dart
+++ b/packages/flutter/test/rendering/box_test.dart
@@ -73,8 +73,8 @@
     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'
         '     <no decorations specified>\n'
diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart
index 9dd3c57..2fd8849 100644
--- a/packages/flutter/test/rendering/editable_test.dart
+++ b/packages/flutter/test/rendering/editable_test.dart
@@ -12,6 +12,8 @@
         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 @@
       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<bool>#00000(false)\n'
diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart
index c4423234..89fe515 100644
--- a/packages/flutter/test/rendering/flex_test.dart
+++ b/packages/flutter/test/rendering/flex_test.dart
@@ -91,8 +91,8 @@
       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 63e03f0..cd0e947 100644
--- a/packages/flutter/test/rendering/overflow_test.dart
+++ b/packages/flutter/test/rendering/overflow_test.dart
@@ -9,13 +9,16 @@
 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 @@
     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 cd9c6bb..644d709 100644
--- a/packages/flutter/test/rendering/paragraph_intrinsics_test.dart
+++ b/packages/flutter/test/rendering/paragraph_intrinsics_test.dart
@@ -10,8 +10,9 @@
     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: <RenderBox>[
diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart
index 1b67e88..380fb25 100644
--- a/packages/flutter/test/rendering/paragraph_test.dart
+++ b/packages/flutter/test/rendering/paragraph_test.dart
@@ -14,7 +14,10 @@
 
 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 @@
   });
 
   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 @@
   });
 
   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<ui.TextBox> boxes = paragraph.getBoxesForSelection(
@@ -61,7 +70,10 @@
   });
 
   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 @@
               '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 @@
             // 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 @@
         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 @@
   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 82ca3bb..90afb11 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 @@
     
 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 91b05dc..1525a5a 100644
--- a/packages/flutter/test/rendering/slivers_test.dart
+++ b/packages/flutter/test/rendering/slivers_test.dart
@@ -19,8 +19,8 @@
       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 650b6d3..bc0329d 100644
--- a/packages/flutter/test/rendering/wrap_test.dart
+++ b/packages/flutter/test/rendering/wrap_test.dart
@@ -13,8 +13,8 @@
       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 dcd89cd..f0c1c80 100644
--- a/packages/flutter/test/widgets/animated_container_test.dart
+++ b/packages/flutter/test/widgets/animated_container_test.dart
@@ -230,7 +230,7 @@
           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 @@
           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 @@
           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 ed09f63..7dd7824 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 @@
 
   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 @@
     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 54e8167..08bde08 100644
--- a/packages/flutter/test/widgets/animated_list_test.dart
+++ b/packages/flutter/test/widgets/animated_list_test.dart
@@ -121,7 +121,7 @@
         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 ad5bade..ff1a96f 100644
--- a/packages/flutter/test/widgets/async_test.dart
+++ b/packages/flutter/test/widgets/async_test.dart
@@ -9,7 +9,7 @@
 
 void main() {
   Widget snapshotText(BuildContext context, AsyncSnapshot<String> 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 @@
   List<String> afterDisconnected(List<String> current) => current..add('disc');
 
   @override
-  Widget build(BuildContext context, List<String> currentSummary) => new Text(currentSummary.join(', '));
+  Widget build(BuildContext context, List<String> 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 c52e616..0208dda 100644
--- a/packages/flutter/test/widgets/banner_test.dart
+++ b/packages/flutter/test/widgets/banner_test.dart
@@ -19,7 +19,8 @@
 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 @@
 
   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 @@
 
   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 @@
 
   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 23216b0..3dfdbb3 100644
--- a/packages/flutter/test/widgets/baseline_test.dart
+++ b/packages/flutter/test/widgets/baseline_test.dart
@@ -14,7 +14,7 @@
             fontFamily: 'Ahem',
             fontSize: 100.0,
           ),
-          child: const Text('X'),
+          child: const Text('X', textDirection: TextDirection.ltr),
         ),
       ),
     );
@@ -32,7 +32,7 @@
               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 6dffdea..c5cc92d 100644
--- a/packages/flutter/test/widgets/clamp_overscrolls_test.dart
+++ b/packages/flutter/test/widgets/clamp_overscrolls_test.dart
@@ -21,9 +21,9 @@
         crossAxisAlignment: CrossAxisAlignment.start,
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          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 28da826..e5909c1 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 @@
 
 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 @@
 
     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 6ebe430..00b7e0b 100644
--- a/packages/flutter/test/widgets/dismissible_test.dart
+++ b/packages/flutter/test/widgets/dismissible_test.dart
@@ -14,48 +14,51 @@
 Widget background;
 
 Widget buildTest({ double startToEndThreshold }) {
-  return new StatefulBuilder(
-    builder: (BuildContext context, StateSetter setState) {
-      Widget buildDismissibleItem(int item) {
-        return new Dismissible(
-          key: new ValueKey<int>(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<int>(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, double>{}
-              : <DismissDirection, double>{DismissDirection.startToEnd: startToEndThreshold},
+            },
+            background: background,
+            dismissThresholds: startToEndThreshold == null
+                ? <DismissDirection, double>{}
+                : <DismissDirection, double>{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: <int>[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: <int>[0, 1, 2, 3, 4]
-              .where((int i) => !dismissedItems.contains(i))
-              .map(buildDismissibleItem).toList(),
-          ),
-        ),
-      );
-    },
+      },
+    ),
   );
 }
 
@@ -287,18 +290,23 @@
   // 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: <Widget>[
-            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: <Widget>[
+                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 08f4a97..bb53504 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 @@
   }
 
   @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 126e5ef..f93df04 100644
--- a/packages/flutter/test/widgets/flex_test.dart
+++ b/packages/flutter/test/widgets/flex_test.dart
@@ -28,7 +28,7 @@
                       width: 100.0,
                       height: 100.0,
                       child: const Center(
-                        child: const Text('X'),
+                        child: const Text('X', textDirection: TextDirection.ltr),
                       ),
                     ),
                   ),
@@ -63,8 +63,8 @@
       new Row(
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          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 237ba63..18991e8 100644
--- a/packages/flutter/test/widgets/flow_test.dart
+++ b/packages/flutter/test/widgets/flow_test.dart
@@ -44,7 +44,7 @@
           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 282d1f2..874ef90 100644
--- a/packages/flutter/test/widgets/focus_test.dart
+++ b/packages/flutter/test/widgets/focus_test.dart
@@ -47,7 +47,7 @@
       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 2b83611..4e27812 100644
--- a/packages/flutter/test/widgets/form_test.dart
+++ b/packages/flutter/test/widgets/form_test.dart
@@ -11,12 +11,15 @@
     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 @@
     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 @@
     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 @@
     final GlobalKey<FormFieldState<String>> inputKey = new GlobalKey<FormFieldState<String>>();
 
     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 @@
     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 2e7346e..12f1276 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 @@
   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 d069d0b..7afa44d 100644
--- a/packages/flutter/test/widgets/hit_testing_test.dart
+++ b/packages/flutter/test/widgets/hit_testing_test.dart
@@ -8,7 +8,7 @@
 
 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 98b1352..6f6fede 100644
--- a/packages/flutter/test/widgets/hyperlink_test.dart
+++ b/packages/flutter/test/widgets/hyperlink_test.dart
@@ -26,6 +26,7 @@
       new Center(
         child: new RichText(
           key: textKey,
+          textDirection: TextDirection.ltr,
           text: new TextSpan(
             children: <TextSpan>[
               new TextSpan(
diff --git a/packages/flutter/test/widgets/icon_test.dart b/packages/flutter/test/widgets/icon_test.dart
index bfbe94e..3ad61de 100644
--- a/packages/flutter/test/widgets/icon_test.dart
+++ b/packages/flutter/test/widgets/icon_test.dart
@@ -8,13 +8,16 @@
 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 @@
 
   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 @@
 
   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 @@
 
   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 @@
 
   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 @@
 
   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 @@
 
   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 5ae15a1..b9306d0 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 @@
   @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 d193104..99cb5c8 100644
--- a/packages/flutter/test/widgets/inherited_test.dart
+++ b/packages/flutter/test/widgets/inherited_test.dart
@@ -129,7 +129,7 @@
                           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 @@
                         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 @@
                           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 @@
                         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 @@
       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 @@
       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 3ccfb61..26e7e78 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 @@
         key: new ValueKey<int>(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 @@
             itemExtent: 200.0,
             itemBuilder: itemBuilder,
           ),
-          right: const Text('Not Today')
+          right: const Text('Not Today'),
         ),
       );
     }
@@ -190,7 +190,7 @@
 
     final IndexedWidgetBuilder itemBuilder = (BuildContext context, int index) {
       callbackTracker.add(index);
-      return new Text('$index', key: new ValueKey<int>(index));
+      return new Text('$index', key: new ValueKey<int>(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 1391ba4..9889146 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<int> items = const <int>[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 298501c..8878ecb 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 @@
         textDirection: TextDirection.ltr,
         child: new ListView(
           controller: controller,
-          children: <Widget>[const Text("A"), const Text("B"), const Text("C")],
+          children: <Widget>[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 4e13371..7623528 100644
--- a/packages/flutter/test/widgets/list_view_test.dart
+++ b/packages/flutter/test/widgets/list_view_test.dart
@@ -189,7 +189,7 @@
             child: new ListView(
               padding: const EdgeInsets.all(8.0),
               children: <Widget>[
-                const Text('padded'),
+                const Text('padded', textDirection: TextDirection.ltr),
               ],
             ),
           ),
@@ -225,7 +225,7 @@
     final TestSliverChildListDelegate delegate = new TestSliverChildListDelegate(
       new List<Widget>.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 b510721..bdf0487 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 @@
         key: new ValueKey<int>(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 @@
         key: new ValueKey<int>(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 @@
         key: new ValueKey<int>(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 @@
         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 @@
         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 9523ef2..71d5936 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 @@
     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 11c7b60..16c7c7d 100644
--- a/packages/flutter/test/widgets/listener_test.dart
+++ b/packages/flutter/test/widgets/listener_test.dart
@@ -24,7 +24,7 @@
               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 18974fa..90575e0 100644
--- a/packages/flutter/test/widgets/localizations_test.dart
+++ b/packages/flutter/test/widgets/localizations_test.dart
@@ -130,7 +130,7 @@
       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 a2e9bdf..b2d628f 100644
--- a/packages/flutter/test/widgets/modal_barrier_test.dart
+++ b/packages/flutter/test/widgets/modal_barrier_test.dart
@@ -23,7 +23,7 @@
       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 9c1e3df..f7619be 100644
--- a/packages/flutter/test/widgets/overlay_test.dart
+++ b/packages/flutter/test/widgets/overlay_test.dart
@@ -141,7 +141,7 @@
           ' ╎ │   _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 @@
           ' ╎       _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#00000] ←\n'
           ' ╎       TickerMode ← _Theatre ← Overlay-[GlobalKey#00000] ← [root]\n'
           ' ╎     parentData: <none>\n'
-          ' ╎     constraints: null\n'
+          ' ╎     constraints: MISSING\n'
           ' ╎     size: MISSING\n'
           ' ╎     additionalConstraints: BoxConstraints(biggest)\n'
           ' ╎\n'
@@ -160,7 +160,7 @@
           '   │   _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 @@
           '         _OverlayEntry-[LabeledGlobalKey<_OverlayEntryState>#00000] ←\n'
           '         TickerMode ← _Theatre ← Overlay-[GlobalKey#00000] ← [root]\n'
           '       parentData: <none>\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 de53906..ebf3242 100644
--- a/packages/flutter/test/widgets/pageable_list_test.dart
+++ b/packages/flutter/test/widgets/pageable_list_test.dart
@@ -63,10 +63,14 @@
 
 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 602b4ef..0c786b5 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 DummyStatefulWidgetState extends State<DummyStatefulWidget> {
   @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 901cfa1..6f9de6d 100644
--- a/packages/flutter/test/widgets/run_app_test.dart
+++ b/packages/flutter/test/widgets/run_app_test.dart
@@ -11,9 +11,9 @@
       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 ffb7581..bbe3d39 100644
--- a/packages/flutter/test/widgets/scrollable_animations_test.dart
+++ b/packages/flutter/test/widgets/scrollable_animations_test.dart
@@ -10,7 +10,7 @@
   testWidgets('Does not animate if already at target position', (WidgetTester tester) async {
     final List<Widget> textWidgets = <Widget>[];
     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 @@
   testWidgets('Does not animate if already at target position within tolerance', (WidgetTester tester) async {
     final List<Widget> textWidgets = <Widget>[];
     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 @@
   testWidgets('Animates if going to a position outside of tolerance', (WidgetTester tester) async {
     final List<Widget> textWidgets = <Widget>[];
     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 f2192c0..6252a60 100644
--- a/packages/flutter/test/widgets/scrollable_semantics_test.dart
+++ b/packages/flutter/test/widgets/scrollable_semantics_test.dart
@@ -47,7 +47,7 @@
     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 7650ef2..5ed047d 100644
--- a/packages/flutter/test/widgets/semantics_10_test.dart
+++ b/packages/flutter/test/widgets/semantics_10_test.dart
@@ -16,22 +16,22 @@
     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, <String>['markNeedsSemanticsUpdate(onlyChanges: true)', 'markNeedsSemanticsUpdate(onlyChanges: false)']);
@@ -41,23 +41,26 @@
 }
 
 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: <Widget>[
-            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: <Widget>[
+              const Semantics(
+                label: 'child1',
+              ),
+              const Semantics(
+                label: 'child2',
+              ),
+            ],
+          ),
         ),
       ),
     ),
@@ -96,6 +99,7 @@
 
   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 bd41d68..7acf3f2 100644
--- a/packages/flutter/test/widgets/semantics_11_test.dart
+++ b/packages/flutter/test/widgets/semantics_11_test.dart
@@ -72,5 +72,6 @@
   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 2b1b94c..11e7f9a 100644
--- a/packages/flutter/test/widgets/semantics_1_test.dart
+++ b/packages/flutter/test/widgets/semantics_1_test.dart
@@ -18,6 +18,7 @@
       new Container(
         child: new Semantics(
           label: 'test1',
+          textDirection: TextDirection.ltr,
           child: new Container()
         )
       )
@@ -32,13 +33,13 @@
         children: <Widget>[
           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 @@
         children: <Widget>[
           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 @@
         children: <Widget>[
           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 @@
         children: <Widget>[
           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 5d23495..063d8b7 100644
--- a/packages/flutter/test/widgets/semantics_2_test.dart
+++ b/packages/flutter/test/widgets/semantics_2_test.dart
@@ -23,13 +23,13 @@
         children: <Widget>[
           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 @@
           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 @@
         children: <Widget>[
           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 @@
         children: <Widget>[
           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 @@
           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 ad7fc33..7c24528 100644
--- a/packages/flutter/test/widgets/semantics_3_test.dart
+++ b/packages/flutter/test/widgets/semantics_3_test.dart
@@ -19,6 +19,7 @@
       new Container(
         child: new Semantics(
           label: 'test',
+          textDirection: TextDirection.ltr,
           child: new Container(
             child: const Semantics(
               checked: true
@@ -57,7 +58,8 @@
       new Container(
         child: new Container(
           child: const Semantics(
-            label: 'test'
+            label: 'test',
+            textDirection: TextDirection.ltr,
           )
         )
       )
@@ -76,7 +78,8 @@
           checked: true,
           child: new Container(
             child: const Semantics(
-              label: 'test'
+              label: 'test',
+              textDirection: TextDirection.ltr,
             )
           )
         )
@@ -102,7 +105,8 @@
           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 51cfc22..5902698 100644
--- a/packages/flutter/test/widgets/semantics_4_test.dart
+++ b/packages/flutter/test/widgets/semantics_4_test.dart
@@ -20,8 +20,9 @@
     //     / \     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: <Widget>[
           const Semantics(
@@ -43,7 +44,7 @@
           ),
         ],
       ),
-    );
+    ));
 
     expect(semantics, hasSemantics(
       new TestSemantics.root(
@@ -79,8 +80,9 @@
     //  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: <Widget>[
           const Semantics(
@@ -100,7 +102,7 @@
           ),
         ],
       ),
-    );
+    ));
 
     expect(semantics, hasSemantics(
       new TestSemantics.root(
@@ -124,8 +126,9 @@
     //    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: <Widget>[
           const Semantics(),
@@ -143,7 +146,7 @@
           ),
         ],
       ),
-    );
+    ));
 
     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 9732080..7ff598f 100644
--- a/packages/flutter/test/widgets/semantics_5_test.dart
+++ b/packages/flutter/test/widgets/semantics_5_test.dart
@@ -25,6 +25,7 @@
           ),
           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 f9c5b72..e5b8377 100644
--- a/packages/flutter/test/widgets/semantics_7_test.dart
+++ b/packages/flutter/test/widgets/semantics_7_test.dart
@@ -18,34 +18,37 @@
 
     label = '1';
     await tester.pumpWidget(
-      new Stack(
-        fit: StackFit.expand,
-        children: <Widget>[
-          new MergeSemantics(
-            child: new Semantics(
-              checked: true,
-              container: true,
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Stack(
+          fit: StackFit.expand,
+          children: <Widget>[
+            new MergeSemantics(
               child: new Semantics(
+                checked: true,
                 container: true,
-                label: label,
-              )
-            )
-          ),
-          new MergeSemantics(
-            child: new Stack(
-              fit: StackFit.expand,
-              children: <Widget>[
-                const Semantics(
-                  checked: true,
-                ),
-                new Semantics(
+                child: new Semantics(
+                  container: true,
                   label: label,
                 ),
-              ]
-            )
-          ),
-        ]
-      )
+              ),
+            ),
+            new MergeSemantics(
+              child: new Stack(
+                fit: StackFit.expand,
+                children: <Widget>[
+                  const Semantics(
+                    checked: true,
+                  ),
+                  new Semantics(
+                    label: label,
+                  ),
+                ],
+              ),
+            ),
+          ],
+        ),
+      ),
     );
 
     expect(semantics, hasSemantics(
@@ -71,34 +74,37 @@
 
     label = '2';
     await tester.pumpWidget(
-      new Stack(
-        fit: StackFit.expand,
-        children: <Widget>[
-          new MergeSemantics(
-            child: new Semantics(
-              checked: true,
-              container: true,
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new Stack(
+          fit: StackFit.expand,
+          children: <Widget>[
+            new MergeSemantics(
               child: new Semantics(
+                checked: true,
                 container: true,
-                label: label,
-              )
-            )
-          ),
-          new MergeSemantics(
-            child: new Stack(
-              fit: StackFit.expand,
-              children: <Widget>[
-                const Semantics(
-                  checked: true,
-                ),
-                new Semantics(
+                child: new Semantics(
+                  container: true,
                   label: label,
-                )
-              ]
-            )
-          ),
-        ]
-      )
+                ),
+              ),
+            ),
+            new MergeSemantics(
+              child: new Stack(
+                fit: StackFit.expand,
+                children: <Widget>[
+                  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 9d9ed4f..f7fa920 100644
--- a/packages/flutter/test/widgets/semantics_8_test.dart
+++ b/packages/flutter/test/widgets/semantics_8_test.dart
@@ -23,10 +23,11 @@
             child: new Stack(
               children: <Widget>[
                 const Semantics(
-                  checked: true
+                  checked: true,
                 ),
                 const Semantics(
-                  label: 'label'
+                  label: 'label',
+                  textDirection: TextDirection.ltr,
                 )
               ]
             )
@@ -39,6 +40,7 @@
       new TestSemantics.root(
         flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
         label: 'label',
+        textDirection: TextDirection.ltr,
       )
     ));
 
@@ -52,7 +54,8 @@
             child: new Stack(
               children: <Widget>[
                 const Semantics(
-                  label: 'label'
+                  label: 'label',
+                  textDirection: TextDirection.ltr,
                 ),
                 const Semantics(
                   checked: true
@@ -68,6 +71,7 @@
       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 522b80a..cbe0db7 100644
--- a/packages/flutter/test/widgets/semantics_9_test.dart
+++ b/packages/flutter/test/widgets/semantics_9_test.dart
@@ -18,11 +18,13 @@
         children: <Widget>[
           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 @@
         children: <Widget>[
           new Semantics(
             label: 'layer#1',
+            textDirection: TextDirection.ltr,
             child: new Container(),
           ),
         ],
@@ -47,7 +50,7 @@
     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: <Widget>[
           new Semantics(
             label: '#1',
@@ -84,7 +87,7 @@
             child: new Container(),
           ),
         ],
-      ));
+      )));
 
       expect(semantics, includesNodeWith(label: '#1'));
       expect(semantics, includesNodeWith(label: '#2'));
@@ -101,7 +104,7 @@
       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: <Widget>[
           new Semantics(
@@ -119,7 +122,7 @@
             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 67ddbe5..2ee4798 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 @@
           ),
           const Semantics(
             label: 'label',
+            textDirection: TextDirection.ltr,
           ),
         ],
       ),
@@ -33,6 +36,7 @@
             ),
             const Semantics(
               label: 'label',
+              textDirection: TextDirection.ltr,
             ),
           ],
         ),
@@ -50,14 +54,14 @@
       new SemanticsDebugger(
         child: new Stack(
           children: <Widget>[
-            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 @@
       new SemanticsDebugger(
         child: new Stack(
           children: <Widget>[
-            const Semantics(label: 'label1'),
+            const Semantics(label: 'label1', textDirection: TextDirection.ltr),
             new Semantics(
               container: true,
               child: new Stack(
@@ -79,9 +83,9 @@
                     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 @@
       new SemanticsDebugger(
         child: new Stack(
           children: <Widget>[
-            const Semantics(label: 'label1'),
+            const Semantics(label: 'label1', textDirection: TextDirection.ltr),
             new Semantics(
               container: true,
               child: new Stack(
@@ -105,9 +109,9 @@
                       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 @@
     log.clear();
   });
 
+  testWidgets('SemanticsDebugger interaction test - negative',
+      (WidgetTester tester) async {
+    final List<String> log = <String>[];
+
+    await tester.pumpWidget(
+      new Directionality(
+        textDirection: TextDirection.ltr,
+        child: new SemanticsDebugger(
+          child: new Material(
+            child: new ListView(
+              children: <Widget>[
+                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(<String>['top']));
+    log.clear();
+
+    await tester.tap(find.text('BOTTOM'));
+    expect(log, equals(<String>[]));
+    log.clear();
+  });
+
   testWidgets('SemanticsDebugger scroll test', (WidgetTester tester) async {
     final Key childKey = new UniqueKey();
 
@@ -211,7 +256,7 @@
             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 e4d25f2..fcadf8a 100644
--- a/packages/flutter/test/widgets/semantics_test.dart
+++ b/packages/flutter/test/widgets/semantics_test.dart
@@ -14,12 +14,14 @@
 
     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 @@
     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 @@
           )
         )
       )
-    );
+    ));
 
     expect(semantics, hasSemantics(
       new TestSemantics.root(
@@ -70,8 +73,9 @@
       )
     ));
 
-    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 @@
           )
         )
       )
-    );
+    ));
 
     expect(semantics, hasSemantics(
       new TestSemantics.root(
@@ -110,4 +114,90 @@
 
     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 f1bb0ca..f2fb679 100644
--- a/packages/flutter/test/widgets/semantics_tester.dart
+++ b/packages/flutter/test/widgets/semantics_tester.dart
@@ -9,6 +9,8 @@
 
 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 @@
     this.flags: 0,
     this.actions: 0,
     this.label: '',
+    this.textDirection,
     this.rect,
     this.transform,
     this.children: const <TestSemantics>[],
@@ -49,6 +52,7 @@
     this.flags: 0,
     this.actions: 0,
     this.label: '',
+    this.textDirection,
     this.transform,
     this.children: const <TestSemantics>[],
     Iterable<SemanticsTag> tags,
@@ -73,6 +77,7 @@
     this.flags: 0,
     this.actions: 0,
     this.label: '',
+    this.textDirection,
     this.rect,
     Matrix4 transform,
     this.children: const <TestSemantics>[],
@@ -98,6 +103,13 @@
   /// 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 @@
 
   bool _matches(SemanticsNode node, Map<dynamic, dynamic> 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 @@
     });
     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 @@
   }
 
   @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 @@
 
   @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<dynamic, dynamic> 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 @@
 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<SemanticsAction> actions;
 
   @override
@@ -279,6 +292,8 @@
   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 @@
     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<SemanticsAction> 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<SemanticsAction> 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 5057390..d9ccee4 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 @@
   Widget build(BuildContext context) {
     return new Listener(
       onPointerDown: _handlePointerDown,
-      child: const Text('INSIDE')
+      child: const Text('INSIDE', textDirection: TextDirection.ltr),
     );
   }
 
@@ -38,7 +38,7 @@
   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 608858e..f2570bb 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 @@
     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 be2bfef..53a2425 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 LeafState extends State<Leaf> {
   @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 c74afb4..0da0619 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 @@
   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 a2990b3..a4dd0c9 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 @@
 void main() {
   testWidgets('SliverFillRemaining control test', (WidgetTester tester) async {
     final List<Widget> children = new List<Widget>.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 @@
         '     │ parentData: <none> (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'
         '       ║   <all styles inherited>\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 b2bad6a..943ba10 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 @@
       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 2057e82..846e53c 100644
--- a/packages/flutter/test/widgets/sliver_semantics_test.dart
+++ b/packages/flutter/test/widgets/sliver_semantics_test.dart
@@ -23,58 +23,60 @@
         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: <Widget>[
-            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: <Widget>[
+              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: <TestSemantics>[
-            new TestSemantics.rootChild(
-              id: 1,
-              tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
-              children: <TestSemantics>[
-                new TestSemantics(
-                  id: 5,
-                  actions: SemanticsAction.scrollUp.index,
-                  children: <TestSemantics>[
-                    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: <TestSemantics>[
+          new TestSemantics.rootChild(
+            id: 1,
+            tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
+            children: <TestSemantics>[
+              new TestSemantics(
+                id: 5,
+                actions: SemanticsAction.scrollUp.index,
+                children: <TestSemantics>[
+                  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 @@
       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 @@
                   new TestSemantics(
                     id: 10,
                     label: 'Item 2',
+                    textDirection: TextDirection.ltr,
                   ),
                   new TestSemantics(
                     id: 11,
                     label: 'Item 1',
+                    textDirection: TextDirection.ltr,
                   ),
                 ],
               ),
@@ -261,22 +265,27 @@
                   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 @@
     final List<Widget> listChildren = new List<Widget>.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 @@
       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 @@
     final List<Widget> listChildren = new List<Widget>.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 @@
       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 @@
     final List<Widget> forwardChildren = new List<Widget>.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<Widget> backwardChildren = new List<Widget>.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 @@
                   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 @@
                   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 b1df7cd..696d984 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 @@
   _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<Null> test(WidgetTester tester, double offset, List<int> keys) {
diff --git a/packages/flutter/test/widgets/stack_test.dart b/packages/flutter/test/widgets/stack_test.dart
index 76181d3..31c9840 100644
--- a/packages/flutter/test/widgets/stack_test.dart
+++ b/packages/flutter/test/widgets/stack_test.dart
@@ -150,7 +150,7 @@
       itemsPainted = <int>[];
       final List<Widget> items = new List<Widget>.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 @@
     Widget buildFrame(int index) {
       itemsTapped = <int>[];
       final List<Widget> items = new List<Widget>.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 13bb2b8..033cc7c 100644
--- a/packages/flutter/test/widgets/syncing_test.dart
+++ b/packages/flutter/test/widgets/syncing_test.dart
@@ -112,8 +112,8 @@
   });
 
   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 4662a5c..93ea64e 100644
--- a/packages/flutter/test/widgets/table_test.dart
+++ b/packages/flutter/test/widgets/table_test.dart
@@ -20,8 +20,9 @@
 
 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: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -40,7 +41,7 @@
           ),
         ]
       )
-    );
+    ));
     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 @@
   });
 
   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: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -71,13 +73,14 @@
           ),
         ]
       )
-    );
+    ));
     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: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -91,7 +94,7 @@
           ),
         ]
       )
-    );
+    ));
     final RenderBox boxA2 = tester.renderObject(find.text('a'));
     final RenderBox boxG2 = tester.renderObject(find.text('g'));
     expect(boxA2, isNotNull);
@@ -101,8 +104,9 @@
   });
 
   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: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -121,9 +125,10 @@
           ),
         ]
       )
-    );
-    await tester.pumpWidget(
-      new Table(
+    ));
+    await tester.pumpWidget(new Directionality(
+      textDirection: TextDirection.ltr,
+      child: new Table(
         children: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -142,7 +147,7 @@
           ),
         ]
       )
-    );
+    ));
     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 @@
   });
 
   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: <TableRow>[
           new TableRow(
@@ -174,7 +180,7 @@
           ),
         ]
       )
-    );
+    ));
     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 @@
   });
 
   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: <TableRow>[
           new TableRow(
@@ -207,9 +214,10 @@
           ),
         ]
       )
-    );
-    await tester.pumpWidget(
-      new Table(
+    ));
+    await tester.pumpWidget(new Directionality(
+      textDirection: TextDirection.ltr,
+      child: new Table(
         defaultColumnWidth: const IntrinsicColumnWidth(),
         children: <TableRow>[
           new TableRow(
@@ -229,7 +237,7 @@
           ),
         ]
       )
-    );
+    ));
     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 @@
   });
 
   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: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -261,9 +270,10 @@
           ),
         ]
       )
-    );
-    await tester.pumpWidget(
-      new Table(
+    ));
+    await tester.pumpWidget(new Directionality(
+      textDirection: TextDirection.ltr,
+      child: new Table(
         defaultColumnWidth: const IntrinsicColumnWidth(),
         children: <TableRow>[
           new TableRow(
@@ -283,7 +293,7 @@
           ),
         ]
       )
-    );
+    ));
     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 @@
 
   testWidgets('Table widget - moving test', (WidgetTester tester) async {
     final List<BuildContext> contexts = <BuildContext>[];
-    await tester.pumpWidget(
-      new Table(
+    await tester.pumpWidget(new Directionality(
+      textDirection: TextDirection.ltr,
+      child: new Table(
         children: <TableRow>[
           new TableRow(
             key: const ValueKey<int>(1),
@@ -317,9 +328,10 @@
           ),
         ]
       )
-    );
-    await tester.pumpWidget(
-      new Table(
+    ));
+    await tester.pumpWidget(new Directionality(
+      textDirection: TextDirection.ltr,
+      child: new Table(
         children: <TableRow>[
           new TableRow(
             children: <Widget>[
@@ -339,14 +351,15 @@
           ),
         ]
       )
-    );
+    ));
     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: <TableRow>[
           new TableRow(
             key: const ValueKey<int>(1),
@@ -364,7 +377,7 @@
           ),
         ]
       )
-    );
+    ));
 
     final TestStatefulWidgetState state11 = tester.state(find.byKey(const ValueKey<int>(11)));
     final TestStatefulWidgetState state12 = tester.state(find.byKey(const ValueKey<int>(12)));
@@ -376,8 +389,9 @@
     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: <TableRow>[
           new TableRow(
             key: const ValueKey<int>(2),
@@ -388,7 +402,7 @@
           ),
         ]
       )
-    );
+    ));
 
     expect(state11.mounted, isFalse);
     expect(state12.mounted, isFalse);
@@ -499,27 +513,30 @@
 
   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: <TableRow>[
           new TableRow(
             children: <Widget>[
-              const Text('A'), const Text('B'), const Text('C')
-            ]
+              const Text('A'), const Text('B'), const Text('C'),
+            ],
           ),
           new TableRow(
             children: <Widget>[
-              const Text('D'), const Text('EEE'), const Text('F')
-            ]
+              const Text('D'), const Text('EEE'), const Text('F'),
+            ],
           ),
           new TableRow(
             children: <Widget>[
-              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 a58f79a..89c752a 100644
--- a/packages/flutter/test/widgets/text_test.dart
+++ b/packages/flutter/test/widgets/text_test.dart
@@ -10,7 +10,7 @@
     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 @@
     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 @@
     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 7ebc405..3ff890b 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 @@
         child: new PageView.builder(
           itemBuilder: (BuildContext context, int index) {
             return new ListView(
-                controller: controller,
-                children: new List<Widget>.generate(
-                  10,
-                  (int i) => new Container(
-                      height: listItemHeight,
-                      child: new Text("Page$index-Item$i")),
-                ).toList());
+              controller: controller,
+              children: new List<Widget>.generate(
+                10,
+                (int i) => new Container(
+                  height: listItemHeight,
+                  child: new Text('Page$index-Item$i'),
+                ),
+              ).toList(),
+            );
           },
         ),
       ),
@@ -37,8 +39,7 @@
     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 @@
     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 @@
     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 e1e410d..875d562 100644
--- a/packages/flutter/test/widgets/transitions_test.dart
+++ b/packages/flutter/test/widgets/transitions_test.dart
@@ -10,7 +10,7 @@
   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 @@
         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 @@
           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 e10deba..830772f 100644
--- a/packages/flutter/test/widgets/widget_inspector_test.dart
+++ b/packages/flutter/test/widgets/widget_inspector_test.dart
@@ -13,9 +13,9 @@
     await tester.pumpWidget(
       new Stack(
         children: <Widget>[
-          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 @@
         selectButtonBuilder: null,
         child: new Stack(
           children: <Widget>[
-            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 @@
             expect(didLongPress, isFalse);
             didLongPress = true;
           },
-          child: const Text('target'),
+          child: const Text('target', textDirection: TextDirection.ltr),
         ),
       ),
     );
@@ -202,9 +202,9 @@
             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 9440522..fb6ec9f 100644
--- a/packages/flutter/test/widgets/wrap_test.dart
+++ b/packages/flutter/test/widgets/wrap_test.dart
@@ -817,7 +817,7 @@
             child: new Wrap(
               textDirection: TextDirection.ltr,
               children: <Widget>[
-                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 f544df7..2f84ec7 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -269,20 +269,22 @@
 
   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 @@
       _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 e900de8..c9970d3 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<Widget> fooBarTexts = const <Text>[
+  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 @@
     });
 
     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 @@
     });
 
     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 @@
     });
 
     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 @@
 
   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 @@
 
   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 @@
       await tester.pumpWidget(new Row(
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+          new Column(children: fooBarTexts),
         ],
       ));
 
@@ -157,8 +162,8 @@
       await tester.pumpWidget(new Row(
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          new Column(children: <Text>[const Text('foo'), const Text('bar')]),
-          new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+          new Column(children: fooBarTexts),
+          new Column(children: fooBarTexts),
         ],
       ));
 
@@ -172,8 +177,8 @@
       await tester.pumpWidget(new Row(
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          new Column(children: <Text>[const Text('foo')]),
-          const Text('bar'),
+          new Column(children: <Text>[const Text('foo', textDirection: TextDirection.ltr)]),
+          const Text('bar', textDirection: TextDirection.ltr),
         ],
       ));
 
@@ -198,7 +203,7 @@
       await tester.pumpWidget(new Row(
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+          new Column(children: fooBarTexts),
         ],
       ));
 
@@ -212,7 +217,7 @@
       await tester.pumpWidget(new Row(
         textDirection: TextDirection.ltr,
         children: <Widget>[
-          new Column(children: <Text>[const Text('foo'), const Text('bar')]),
+          new Column(children: fooBarTexts),
         ],
       ));