From e7c90057c785541a434d530ebf047eec8394f783 Mon Sep 17 00:00:00 2001 From: Tjong Anthony Date: Wed, 19 Feb 2020 13:31:05 -0800 Subject: [PATCH] [Form] Add is valid to FormState (#48948) --- packages/flutter/lib/src/widgets/form.dart | 15 ++++ packages/flutter/test/widgets/form_test.dart | 94 ++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 96b8bdf248..1204d8f683 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -349,6 +349,16 @@ class FormFieldState extends State> { /// True if this field has any validation errors. bool get hasError => _errorText != null; + /// True if the current value is valid. + /// + /// This will not set [errorText] or [hasError] and it will not update + /// error display. + /// + /// See also: + /// + /// * [validate], which may update [errorText] and [hasError]. + bool get isValid => widget.validator?.call(_value) == null; + /// Calls the [FormField]'s onSaved method with the current value. void save() { if (widget.onSaved != null) @@ -365,6 +375,11 @@ class FormFieldState extends State> { /// Calls [FormField.validator] to set the [errorText]. Returns true if there /// were no errors. + /// + /// See also: + /// + /// * [isValid], which passively gets the validity without setting + /// [errorText] or [hasError]. bool validate() { setState(() { _validate(); diff --git a/packages/flutter/test/widgets/form_test.dart b/packages/flutter/test/widgets/form_test.dart index 29bb7fee9a..e4d4ad9e13 100644 --- a/packages/flutter/test/widgets/form_test.dart +++ b/packages/flutter/test/widgets/form_test.dart @@ -137,6 +137,100 @@ void main() { await checkErrorText(''); }); + testWidgets('isValid returns true when a field is valid', (WidgetTester tester) async { + final GlobalKey> fieldKey1 = GlobalKey>(); + final GlobalKey> fieldKey2 = GlobalKey>(); + const String validString = 'Valid string'; + String validator(String s) => s == validString ? null : 'Error text'; + + Widget builder() { + return MaterialApp( + home: MediaQuery( + data: const MediaQueryData(devicePixelRatio: 1.0), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Material( + child: Form( + child: ListView( + children: [ + TextFormField( + key: fieldKey1, + initialValue: validString, + validator: validator, + autovalidate: true + ), + TextFormField( + key: fieldKey2, + initialValue: validString, + validator: validator, + autovalidate: true + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(builder()); + + expect(fieldKey1.currentState.isValid, isTrue); + expect(fieldKey2.currentState.isValid, isTrue); + }); + + testWidgets( + 'isValid returns false when the field is invalid and does not change error display', + (WidgetTester tester) async { + final GlobalKey> fieldKey1 = GlobalKey>(); + final GlobalKey> fieldKey2 = GlobalKey>(); + const String validString = 'Valid string'; + String validator(String s) => s == validString ? null : 'Error text'; + + Widget builder() { + return MaterialApp( + home: MediaQuery( + data: const MediaQueryData(devicePixelRatio: 1.0), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Material( + child: Form( + child: ListView( + children: [ + TextFormField( + key: fieldKey1, + initialValue: validString, + validator: validator, + autovalidate: false, + ), + TextFormField( + key: fieldKey2, + initialValue: '', + validator: validator, + autovalidate: false, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(builder()); + + expect(fieldKey1.currentState.isValid, isTrue); + expect(fieldKey2.currentState.isValid, isFalse); + expect(fieldKey2.currentState.hasError, isFalse); + }, + ); + testWidgets('Multiple TextFormFields communicate', (WidgetTester tester) async { final GlobalKey formKey = GlobalKey(); final GlobalKey> fieldKey = GlobalKey>();