@@ -272,6 +272,121 @@ void main() {
272272 },
273273 );
274274
275+ testWidgets (
276+ 'validateGranularly returns a set containing all, and only, invalid fields' ,
277+ (WidgetTester tester) async {
278+ final GlobalKey <FormState > formKey = GlobalKey <FormState >();
279+ final UniqueKey validFieldsKey = UniqueKey ();
280+ final UniqueKey invalidFieldsKey = UniqueKey ();
281+
282+ const String validString = 'Valid string' ;
283+ const String invalidString = 'Invalid string' ;
284+ String ? validator (String ? s) => s == validString ? null : 'Error text' ;
285+
286+ Widget builder () {
287+ return MaterialApp (
288+ home: MediaQuery (
289+ data: const MediaQueryData (),
290+ child: Directionality (
291+ textDirection: TextDirection .ltr,
292+ child: Center (
293+ child: Material (
294+ child: Form (
295+ key: formKey,
296+ child: ListView (
297+ children: < Widget > [
298+ TextFormField (
299+ key: validFieldsKey,
300+ initialValue: validString,
301+ validator: validator,
302+ autovalidateMode: AutovalidateMode .disabled,
303+ ),
304+ TextFormField (
305+ key: invalidFieldsKey,
306+ initialValue: invalidString,
307+ validator: validator,
308+ autovalidateMode: AutovalidateMode .disabled,
309+ ),
310+ TextFormField (
311+ key: invalidFieldsKey,
312+ initialValue: invalidString,
313+ validator: validator,
314+ autovalidateMode: AutovalidateMode .disabled,
315+ ),
316+ ],
317+ ),
318+ ),
319+ ),
320+ ),
321+ ),
322+ ),
323+ );
324+ }
325+
326+ await tester.pumpWidget (builder ());
327+
328+ final Set <FormFieldState <dynamic >> validationResult = formKey.currentState! .validateGranularly ();
329+
330+ expect (validationResult.length, equals (2 ));
331+ expect (validationResult.where ((FormFieldState <dynamic > field) => field.widget.key == invalidFieldsKey).length, equals (2 ));
332+ expect (validationResult.where ((FormFieldState <dynamic > field) => field.widget.key == validFieldsKey).length, equals (0 ));
333+ },
334+ );
335+
336+ testWidgets (
337+ 'Should announce error text when validateGranularly is called' ,
338+ (WidgetTester tester) async {
339+ final GlobalKey <FormState > formKey = GlobalKey <FormState >();
340+ const String validString = 'Valid string' ;
341+ String ? validator (String ? s) => s == validString ? null : 'error' ;
342+
343+ Widget builder () {
344+ return MaterialApp (
345+ home: MediaQuery (
346+ data: const MediaQueryData (),
347+ child: Directionality (
348+ textDirection: TextDirection .ltr,
349+ child: Center (
350+ child: Material (
351+ child: Form (
352+ key: formKey,
353+ child: ListView (
354+ children: < Widget > [
355+ TextFormField (
356+ initialValue: validString,
357+ validator: validator,
358+ autovalidateMode: AutovalidateMode .disabled,
359+ ),
360+ TextFormField (
361+ initialValue: '' ,
362+ validator: validator,
363+ autovalidateMode: AutovalidateMode .disabled,
364+ ),
365+ ],
366+ ),
367+ ),
368+ ),
369+ ),
370+ ),
371+ ),
372+ );
373+ }
374+
375+ await tester.pumpWidget (builder ());
376+ expect (find.text ('error' ), findsNothing);
377+
378+ formKey.currentState! .validateGranularly ();
379+
380+ await tester.pump ();
381+ expect (find.text ('error' ), findsOneWidget);
382+
383+ final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements ().single;
384+ expect (announcement.message, 'error' );
385+ expect (announcement.textDirection, TextDirection .ltr);
386+ expect (announcement.assertiveness, Assertiveness .assertive);
387+ },
388+ );
389+
275390 testWidgets ('Multiple TextFormFields communicate' , (WidgetTester tester) async {
276391 final GlobalKey <FormState > formKey = GlobalKey <FormState >();
277392 final GlobalKey <FormFieldState <String >> fieldKey = GlobalKey <FormFieldState <String >>();
0 commit comments