From 14bce28f3cf5a12def3b7a9a65f857e48856c3d2 Mon Sep 17 00:00:00 2001 From: hangyu Date: Tue, 13 Feb 2024 14:35:43 -0800 Subject: [PATCH] [a11y] Add isEnabled semantics flag to text field (#143334) Add a semantics flag to text field to fix https://github.com/flutter/flutter/issues/143337 (in IOS the disabled text field is not read `dimmed`) internal: b/322345393 --- .../flutter/lib/src/material/text_field.dart | 1 + .../test/cupertino/text_field_test.dart | 7 +- .../input_date_picker_form_field_test.dart | 2 + .../flutter/test/material/search_test.dart | 4 + .../test/material/text_field_test.dart | 73 +++++++++++++++++-- .../test/material/time_picker_test.dart | 14 +++- .../flutter_test/test/controller_test.dart | 9 ++- 7 files changed, 94 insertions(+), 16 deletions(-) diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 7bc78fdb379d..99822ef0fac7 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -1574,6 +1574,7 @@ class _TextFieldState extends State with RestorationMixin implements animation: controller, // changes the _currentLength builder: (BuildContext context, Widget? child) { return Semantics( + enabled: _isEnabled, maxValueLength: semanticsMaxValueLength, currentValueLength: _currentLength, onTap: widget.readOnly ? null : () { diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 41b76b5587b7..bd072841624c 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -529,8 +529,11 @@ void main() { children: [ TestSemantics( id: 4, - flags: [SemanticsFlag.isTextField, - SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled,], + flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + ], actions: [SemanticsAction.tap, SemanticsAction.didGainAccessibilityFocus,], textDirection: TextDirection.ltr, diff --git a/packages/flutter/test/material/input_date_picker_form_field_test.dart b/packages/flutter/test/material/input_date_picker_form_field_test.dart index 0588bdfc03ed..14773781409f 100644 --- a/packages/flutter/test/material/input_date_picker_form_field_test.dart +++ b/packages/flutter/test/material/input_date_picker_form_field_test.dart @@ -279,6 +279,8 @@ void main() { expect(tester.getSemantics(find.byType(EditableText)), matchesSemantics( label: 'Enter Date', isTextField: true, + hasEnabledState: true, + isEnabled: true, isFocused: true, value: '01/15/2016', hasTapAction: true, diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 4b4359b76962..79e856c6cb74 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -670,6 +670,8 @@ void main() { id: 9, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, SemanticsFlag.isHeader, if (debugDefaultTargetPlatformOverride != TargetPlatform.iOS && @@ -818,6 +820,8 @@ void main() { id: 11, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, SemanticsFlag.isHeader, if (debugDefaultTargetPlatformOverride != TargetPlatform.iOS && diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index a906f581cbba..85f6e2d9459f 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -746,7 +746,7 @@ void main() { children: [ TestSemantics( id: 4, - flags: [SemanticsFlag.isTextField], + flags: [SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled], actions: [ SemanticsAction.tap, SemanticsAction.didGainAccessibilityFocus, @@ -1856,7 +1856,12 @@ void main() { children: [ TestSemantics( id: 1, - flags: [SemanticsFlag.isTextField, SemanticsFlag.isFocused], + flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocused, + ], actions: [ SemanticsAction.tap, SemanticsAction.moveCursorBackwardByCharacter, @@ -5189,6 +5194,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], ), TestSemantics.rootChild( @@ -6530,7 +6537,7 @@ void main() { ), ); - expect(semantics, includesNodeWith(flags: [SemanticsFlag.isTextField])); + expect(semantics, includesNodeWith(flags: [SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled])); semantics.dispose(); }); @@ -6944,7 +6951,7 @@ void main() { ); expect(semantics, includesNodeWith( - flags: [SemanticsFlag.isTextField], + flags: [SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled], maxValueLength: 10, currentValueLength: 0, )); @@ -6959,7 +6966,12 @@ void main() { await tester.pump(); expect(semantics, includesNodeWith( - flags: [SemanticsFlag.isTextField, SemanticsFlag.isFocused], + flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocused, + ], maxValueLength: 10, currentValueLength: 3, )); @@ -6985,7 +6997,12 @@ void main() { expect( semantics, - includesNodeWith(flags: [SemanticsFlag.isTextField, SemanticsFlag.isReadOnly]), + includesNodeWith(flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isReadOnly, + ]), ); semantics.dispose(); @@ -8050,6 +8067,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], ), ], @@ -8069,6 +8088,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], ), ], @@ -8094,6 +8115,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8122,6 +8145,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8149,6 +8174,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8174,6 +8201,8 @@ void main() { textDirection: TextDirection.ltr, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], value: 'Hello', ) @@ -8189,6 +8218,8 @@ void main() { textDirection: TextDirection.ltr, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isObscured, ], ) @@ -8204,6 +8235,8 @@ void main() { textDirection: TextDirection.ltr, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], value: 'Hello', ) @@ -8246,6 +8279,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8281,6 +8316,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], ), ], @@ -8307,6 +8344,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8337,6 +8376,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8386,6 +8427,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8434,6 +8477,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], ), @@ -8470,7 +8515,7 @@ void main() { children: [ TestSemantics( id: inputFieldId, - flags: [SemanticsFlag.isTextField], + flags: [SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled], actions: [SemanticsAction.tap], value: textInTextField, textDirection: TextDirection.ltr, @@ -8490,6 +8535,8 @@ void main() { id: inputFieldId, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], actions: [ @@ -8543,7 +8590,7 @@ void main() { children: [ TestSemantics( id: inputFieldId, - flags: [SemanticsFlag.isTextField], + flags: [SemanticsFlag.isTextField, SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled], actions: [SemanticsAction.tap], value: textInTextField, textDirection: TextDirection.ltr, @@ -8563,6 +8610,8 @@ void main() { id: inputFieldId, flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], actions: [ @@ -8743,6 +8792,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], children: [ TestSemantics( @@ -8778,6 +8829,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, SemanticsFlag.isFocused, ], children: [ @@ -8835,6 +8888,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], children: [ TestSemantics( @@ -8882,6 +8937,8 @@ void main() { ], flags: [ SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, ], children: [ TestSemantics( diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 32e85d865103..f81f9892059a 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -1343,7 +1343,12 @@ void main() { label: 'Hour', value: '07', actions: [SemanticsAction.tap], - flags: [SemanticsFlag.isTextField, SemanticsFlag.isMultiline], + flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isMultiline, + ], ), ); expect( @@ -1352,7 +1357,12 @@ void main() { label: 'Minute', value: '00', actions: [SemanticsAction.tap], - flags: [SemanticsFlag.isTextField, SemanticsFlag.isMultiline], + flags: [ + SemanticsFlag.isTextField, + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isMultiline, + ], ), ); diff --git a/packages/flutter_test/test/controller_test.dart b/packages/flutter_test/test/controller_test.dart index 00e1fc8736d2..40c4d1c26e6e 100644 --- a/packages/flutter_test/test/controller_test.dart +++ b/packages/flutter_test/test/controller_test.dart @@ -883,6 +883,7 @@ void main() { group('simulatedTraversal', () { final List fullTraversalMatchers = [ containsSemantics(isHeader: true, label: 'Semantics Test'), + containsSemantics(label: 'Text Field'), containsSemantics(isTextField: true), containsSemantics(label: 'Off Switch'), containsSemantics(hasToggledState: true), @@ -913,7 +914,7 @@ void main() { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); // We're expecting the traversal to start where the slider is. - final List expectedMatchers = [...fullTraversalMatchers]..removeRange(0, 8); + final List expectedMatchers = [...fullTraversalMatchers]..removeRange(0, 9); expect( tester.semantics.simulatedAccessibilityTraversal(start: find.byType(Slider)), @@ -935,7 +936,7 @@ void main() { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); // We're expecting the traversal to end where the slider is, inclusive. - final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(0, 9); + final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(0, 10); expect( tester.semantics.simulatedAccessibilityTraversal(end: find.byType(Slider)), @@ -957,7 +958,7 @@ void main() { await tester.pumpWidget(const MaterialApp(home: _SemanticsTestWidget())); // We're expecting the traversal to start at the text field and end at the slider. - final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(1, 9); + final Iterable expectedMatchers = [...fullTraversalMatchers].getRange(1, 10); expect( tester.semantics.simulatedAccessibilityTraversal( @@ -1661,7 +1662,7 @@ class _SemanticsTestWidget extends StatelessWidget { child: Column( children: [ const _SemanticsTestCard( - label: 'TextField', + label: 'Text Field', widget: TextField(), ), _SemanticsTestCard(