From bf7c27095cc4dc07c03d8a6ad2e9e0e28e26227e Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 18 Sep 2018 13:39:21 -0700 Subject: [PATCH] Make helper and error text separate widgets, make error and counter live region (#21752) --- .../lib/src/material/input_decorator.dart | 48 +++++++------ .../test/material/text_field_test.dart | 71 +++++++++++++++++-- 2 files changed, 95 insertions(+), 24 deletions(-) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 44ae7e39d79..a923f782acb 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -320,32 +320,39 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta Widget _buildHelper() { assert(widget.helperText != null); - return Opacity( - opacity: 1.0 - _controller.value, - child: Text( - widget.helperText, - style: widget.helperStyle, - textAlign: widget.textAlign, - overflow: TextOverflow.ellipsis, + return Semantics( + container: true, + child: Opacity( + opacity: 1.0 - _controller.value, + child: Text( + widget.helperText, + style: widget.helperStyle, + textAlign: widget.textAlign, + overflow: TextOverflow.ellipsis, + ), ), ); } Widget _buildError() { assert(widget.errorText != null); - return Opacity( - opacity: _controller.value, - child: FractionalTranslation( - translation: Tween( - begin: const Offset(0.0, -0.25), - end: const Offset(0.0, 0.0), - ).evaluate(_controller.view), - child: Text( - widget.errorText, - style: widget.errorStyle, - textAlign: widget.textAlign, - overflow: TextOverflow.ellipsis, - maxLines: widget.errorMaxLines, + return Semantics( + container: true, + liveRegion: true, + child: Opacity( + opacity: _controller.value, + child: FractionalTranslation( + translation: Tween( + begin: const Offset(0.0, -0.25), + end: const Offset(0.0, 0.0), + ).evaluate(_controller.view), + child: Text( + widget.errorText, + style: widget.errorStyle, + textAlign: widget.textAlign, + overflow: TextOverflow.ellipsis, + maxLines: widget.errorMaxLines, + ), ), ), ); @@ -1815,6 +1822,7 @@ class _InputDecoratorState extends State with TickerProviderStat final Widget counter = decoration.counterText == null ? null : Semantics( container: true, + liveRegion: isFocused, child: Text( decoration.counterText, style: _getHelperStyle(themeData).merge(decoration.counterStyle), diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 813055e085b..5a1d8d6d3a2 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -2843,7 +2843,7 @@ void main() { expect(semantics, hasSemantics(TestSemantics.root( children: [ TestSemantics.rootChild( - label: 'label\nhelper', + label: 'label', id: 1, textDirection: TextDirection.ltr, actions: [ @@ -2855,6 +2855,11 @@ void main() { children: [ TestSemantics( id: 2, + label: 'helper', + textDirection: TextDirection.ltr, + ), + TestSemantics( + id: 3, label: '10 characters remaining', textDirection: TextDirection.ltr, ), @@ -2869,7 +2874,7 @@ void main() { expect(semantics, hasSemantics(TestSemantics.root( children: [ TestSemantics.rootChild( - label: 'hint\nhelper', + label: 'hint', id: 1, textDirection: TextDirection.ltr, textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), @@ -2885,7 +2890,15 @@ void main() { children: [ TestSemantics( id: 2, + label: 'helper', + textDirection: TextDirection.ltr, + ), + TestSemantics( + id: 3, label: '10 characters remaining', + flags: [ + SemanticsFlag.isLiveRegion, + ], textDirection: TextDirection.ltr, ), ], @@ -2922,8 +2935,7 @@ void main() { expect(semantics, hasSemantics(TestSemantics.root( children: [ TestSemantics.rootChild( - label: 'label\nhelper', - id: 1, + label: 'label', textDirection: TextDirection.ltr, actions: [ SemanticsAction.tap, @@ -2932,6 +2944,10 @@ void main() { SemanticsFlag.isTextField, ], children: [ + TestSemantics( + label: 'helper', + textDirection: TextDirection.ltr, + ), TestSemantics( label: '0 out of 10', textDirection: TextDirection.ltr, @@ -2944,6 +2960,52 @@ void main() { semantics.dispose(); }); + testWidgets('InputDecoration errorText semantics', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + final TextEditingController controller = TextEditingController(); + final Key key = UniqueKey(); + + await tester.pumpWidget( + overlay( + child: TextField( + key: key, + controller: controller, + decoration: const InputDecoration( + labelText: 'label', + hintText: 'hint', + errorText: 'oh no!', + ), + ), + ), + ); + + expect(semantics, hasSemantics(TestSemantics.root( + children: [ + TestSemantics.rootChild( + label: 'label', + textDirection: TextDirection.ltr, + actions: [ + SemanticsAction.tap, + ], + flags: [ + SemanticsFlag.isTextField, + ], + children: [ + TestSemantics( + label: 'oh no!', + flags: [ + SemanticsFlag.isLiveRegion, + ], + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), ignoreTransform: true, ignoreRect: true, ignoreId: true)); + + semantics.dispose(); + }); + testWidgets('floating label does not overlap with value at large textScaleFactors', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(text: 'Just some text'); await tester.pumpWidget( @@ -2964,6 +3026,7 @@ void main() { ), ), ); + await tester.tap(find.byType(TextField)); final Rect labelRect = tester.getRect(find.text('Label')); final Rect fieldRect = tester.getRect(find.text('Just some text'));