Unverified Commit 0c40f21f authored by SharbelOkzan's avatar SharbelOkzan Committed by GitHub

Introduce new Form validation method (#135578)

Introduced `validateGranually` which, apart from announcing the errors to the UI, returns a `Map<Key, bool>` providing more granular validation details: The results of calling `validate` on each `FormField` and their corresponding widget keys.

* related issue: #135363
parent 536de5ed
......@@ -291,18 +291,45 @@ class FormState extends State<Form> {
/// returns true if there are no errors.
///
/// The form will rebuild to report the results.
///
/// See also:
/// * [validateGranularly], which also validates descendant [FormField]s,
/// but instead returns a [Set] of fields with errors.
bool validate() {
_hasInteractedByUser = true;
_forceRebuild();
return _validate();
}
bool _validate() {
/// Validates every [FormField] that is a descendant of this [Form], and
/// returns a [Set] of [FormFieldState] of the invalid field(s) only, if any.
///
/// This method can be useful to highlight field(s) with errors.
///
/// The form will rebuild to report the results.
///
/// See also:
/// * [validate], which also validates descendant [FormField]s,
/// and return true if there are no errors.
Set<FormFieldState<Object?>> validateGranularly() {
final Set<FormFieldState<Object?>> invalidFields = <FormFieldState<Object?>>{};
_hasInteractedByUser = true;
_forceRebuild();
_validate(invalidFields);
return invalidFields;
}
bool _validate([Set<FormFieldState<Object?>>? invalidFields]) {
bool hasError = false;
String errorMessage = '';
for (final FormFieldState<dynamic> field in _fields) {
hasError = !field.validate() || hasError;
final bool isFieldValid = field.validate();
hasError = !isFieldValid || hasError;
errorMessage += field.errorText ?? '';
if (invalidFields != null && !isFieldValid) {
invalidFields.add(field);
}
}
if (errorMessage.isNotEmpty) {
......
......@@ -272,6 +272,121 @@ void main() {
},
);
testWidgets(
'validateGranularly returns a set containing all, and only, invalid fields',
(WidgetTester tester) async {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final UniqueKey validFieldsKey = UniqueKey();
final UniqueKey invalidFieldsKey = UniqueKey();
const String validString = 'Valid string';
const String invalidString = 'Invalid string';
String? validator(String? s) => s == validString ? null : 'Error text';
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: Form(
key: formKey,
child: ListView(
children: <Widget>[
TextFormField(
key: validFieldsKey,
initialValue: validString,
validator: validator,
autovalidateMode: AutovalidateMode.disabled,
),
TextFormField(
key: invalidFieldsKey,
initialValue: invalidString,
validator: validator,
autovalidateMode: AutovalidateMode.disabled,
),
TextFormField(
key: invalidFieldsKey,
initialValue: invalidString,
validator: validator,
autovalidateMode: AutovalidateMode.disabled,
),
],
),
),
),
),
),
),
);
}
await tester.pumpWidget(builder());
final Set<FormFieldState<dynamic>> validationResult = formKey.currentState!.validateGranularly();
expect(validationResult.length, equals(2));
expect(validationResult.where((FormFieldState<dynamic> field) => field.widget.key == invalidFieldsKey).length, equals(2));
expect(validationResult.where((FormFieldState<dynamic> field) => field.widget.key == validFieldsKey).length, equals(0));
},
);
testWidgets(
'Should announce error text when validateGranularly is called',
(WidgetTester tester) async {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
const String validString = 'Valid string';
String? validator(String? s) => s == validString ? null : 'error';
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: Form(
key: formKey,
child: ListView(
children: <Widget>[
TextFormField(
initialValue: validString,
validator: validator,
autovalidateMode: AutovalidateMode.disabled,
),
TextFormField(
initialValue: '',
validator: validator,
autovalidateMode: AutovalidateMode.disabled,
),
],
),
),
),
),
),
),
);
}
await tester.pumpWidget(builder());
expect(find.text('error'), findsNothing);
formKey.currentState!.validateGranularly();
await tester.pump();
expect(find.text('error'), findsOneWidget);
final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements().single;
expect(announcement.message, 'error');
expect(announcement.textDirection, TextDirection.ltr);
expect(announcement.assertiveness, Assertiveness.assertive);
},
);
testWidgets('Multiple TextFormFields communicate', (WidgetTester tester) async {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final GlobalKey<FormFieldState<String>> fieldKey = GlobalKey<FormFieldState<String>>();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment