Add Text.rich constructor to Text widget (#15317)

* add fromSpan constructor to Text widget and simple smoke test:

* change fromSpan to rich, clean up comments

* 'directly from' to 'with'

* make sure text styles are applied to either data or textspan. add diagnostic properties for span

* add expectation for text style to test case

* some work on diagnostics and docs
diff --git a/packages/flutter/lib/src/widgets/text.dart b/packages/flutter/lib/src/widgets/text.dart
index b163638..d933f0e 100644
--- a/packages/flutter/lib/src/widgets/text.dart
+++ b/packages/flutter/lib/src/widgets/text.dart
@@ -164,8 +164,9 @@
 /// for example, to make the text bold while using the default font family and
 /// size.
 ///
-/// To display text that uses multiple styles (e.g., a paragraph with some bold
-/// words), use [RichText].
+/// Using the [new TextSpan.rich] constructor, the [Text] widget can also be
+/// created with a [TextSpan] to display text that use multiple styles
+/// (e.g., a paragraph with some bold words).
 ///
 /// ## Sample code
 ///
@@ -210,11 +211,33 @@
     this.textScaleFactor,
     this.maxLines,
   }) : assert(data != null),
+       textSpan = null,
        super(key: key);
 
+  /// Creates a text widget with a [TextSpan].
+  const Text.rich(this.textSpan, {
+    Key key,
+    this.style,
+    this.textAlign,
+    this.textDirection,
+    this.softWrap,
+    this.overflow,
+    this.textScaleFactor,
+    this.maxLines,
+  }): assert(textSpan != null),
+      data = null,
+      super(key: key);
+
   /// The text to display.
+  ///
+  /// This will be null if a [textSpan] is provided instead.
   final String data;
 
+  /// The text to display as a [TextSpan].
+  ///
+  /// This will be null if [data] is provided instead.
+  final TextSpan textSpan;
+
   /// If non-null, the style to use for this text.
   ///
   /// If the style's "inherit" property is true, the style will be merged with
@@ -287,7 +310,8 @@
       text: new TextSpan(
         style: effectiveTextStyle,
         text: data,
-      )
+        children: textSpan != null ? <TextSpan>[textSpan] : null,
+      ),
     );
   }
 
@@ -295,6 +319,9 @@
   void debugFillProperties(DiagnosticPropertiesBuilder description) {
     super.debugFillProperties(description);
     description.add(new StringProperty('data', data, showName: false));
+    if (textSpan != null) {
+      description.add(textSpan.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
+    }
     style?.debugFillProperties(description);
     description.add(new EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
     description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
diff --git a/packages/flutter/test/widgets/text_test.dart b/packages/flutter/test/widgets/text_test.dart
index b1a2f9c..0d4c97f 100644
--- a/packages/flutter/test/widgets/text_test.dart
+++ b/packages/flutter/test/widgets/text_test.dart
@@ -85,4 +85,28 @@
     expect(message, contains('Directionality'));
     expect(message, contains(' Text '));
   });
+
+  testWidgets('Text can be created from TextSpans and uses defaultTextStyle', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      const DefaultTextStyle(
+        style: const TextStyle(
+          fontSize: 20.0,
+        ),
+        child: const Text.rich(
+          const TextSpan(
+            text: 'Hello',
+            children: const <TextSpan>[
+              const TextSpan(text: ' beautiful ', style: const TextStyle(fontStyle: FontStyle.italic)),
+              const TextSpan(text: 'world', style: const TextStyle(fontWeight: FontWeight.bold)),
+            ],
+          ),
+          textDirection: TextDirection.ltr,
+        ),
+      ),
+    );
+
+    final RichText text = tester.firstWidget(find.byType(RichText));
+    expect(text, isNotNull);
+    expect(text.text.style.fontSize, 20.0);
+  });
 }