From d541354936be9ddc999db480cd35ecb373ecda4b Mon Sep 17 00:00:00 2001 From: Aziz Chebbi <60013060+azizChebbi@users.noreply.github.com> Date: Thu, 28 Nov 2024 02:04:08 +0100 Subject: [PATCH] Fix: Announce only the first error message for better accessibility (#156399) ## Issue This pull request addresses an accessibility issue where all form error messages were concatenated and announced simultaneously, overwhelming users relying on screen readers like VoiceOver and TalkBack. This is the issue link: https://github.com/flutter/flutter/issues/156340 ## Update The change ensures that only the first error message is announced, providing a more manageable and user-friendly experience. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --------- Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com> --- packages/flutter/lib/src/widgets/form.dart | 5 +- packages/flutter/test/widgets/form_test.dart | 68 +++++++++++--------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 8fe5cf786b..dac0bf9c8d 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -357,7 +357,10 @@ class FormState extends State
{ if (!validateOnFocusChange || !field._focusNode.hasFocus) { final bool isFieldValid = field.validate(); hasError = !isFieldValid || hasError; - errorMessage += field.errorText ?? ''; + // Ensure that only the first error message gets announced to the user. + if (errorMessage.isEmpty) { + errorMessage = field.errorText ?? ''; + } if (invalidFields != null && !isFieldValid) { invalidFields.add(field); } diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart index 5a3117d47d..1fc97db56f 100644 --- a/packages/flutter/test/widgets/form_test.dart +++ b/packages/flutter/test/widgets/form_test.dart @@ -139,44 +139,52 @@ void main() { await checkErrorText(''); }); - testWidgets('Should announce error text when validate returns error', (WidgetTester tester) async { - final GlobalKey formKey = GlobalKey(); - await tester.pumpWidget( - MaterialApp( - home: MediaQuery( - data: const MediaQueryData(), - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: Material( - child: Form( - key: formKey, - child: TextFormField( - validator: (_)=> 'error', - ), + testWidgets('Should announce only the first error message when validate returns errors', (WidgetTester tester) async { + final GlobalKey formKey = GlobalKey(); + await tester.pumpWidget( + MaterialApp( + home: MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Material( + child: Form( + key: formKey, + child: Column( + children: [ + TextFormField( + validator: (_) => 'First error message', + ), + TextFormField( + validator: (_) => 'Second error message', + ), + ], ), ), ), ), ), ), - ); - formKey.currentState!.reset(); - await tester.enterText(find.byType(TextFormField), ''); - await tester.pump(); + ), + ); + formKey.currentState!.reset(); + await tester.enterText(find.byType(TextFormField).first, ''); + await tester.pump(); - // Manually validate. - expect(find.text('error'), findsNothing); - formKey.currentState!.validate(); - await tester.pump(); - expect(find.text('error'), findsOneWidget); + // Manually validate. + expect(find.text('First error message'), findsNothing); + expect(find.text('Second error message'), findsNothing); + formKey.currentState!.validate(); + await tester.pump(); + expect(find.text('First error message'), findsOneWidget); + expect(find.text('Second error message'), findsOneWidget); - final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements().single; - expect(announcement.message, 'error'); - expect(announcement.textDirection, TextDirection.ltr); - expect(announcement.assertiveness, Assertiveness.assertive); - - }); + final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements().single; + expect(announcement.message, 'First error message'); + expect(announcement.textDirection, TextDirection.ltr); + expect(announcement.assertiveness, Assertiveness.assertive); +}); testWidgets('isValid returns true when a field is valid', (WidgetTester tester) async { final GlobalKey> fieldKey1 = GlobalKey>();