Unverified Commit 5a69de82 authored by Pedro Massango's avatar Pedro Massango Committed by GitHub

FormField should autovalidate only if its content was changed (fixed) (#59766)

parent 9c4a5ef1
......@@ -1437,7 +1437,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
/// Creates a [DropdownButton] widget that is a [FormField], wrapped in an
/// [InputDecorator].
///
/// For a description of the `onSaved`, `validator`, or `autovalidate`
/// For a description of the `onSaved`, `validator`, or `autovalidateMode`
/// parameters, see [FormField]. For the rest (other than [decoration]), see
/// [DropdownButton].
///
......@@ -1469,6 +1469,7 @@ class DropdownButtonFormField<T> extends FormField<T> {
FormFieldSetter<T> onSaved,
FormFieldValidator<T> validator,
bool autovalidate = false,
AutovalidateMode autovalidateMode,
}) : assert(items == null || items.isEmpty || value == null ||
items.where((DropdownMenuItem<T> item) {
return item.value == value;
......@@ -1484,13 +1485,21 @@ class DropdownButtonFormField<T> extends FormField<T> {
assert(isExpanded != null),
assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
assert(autofocus != null),
assert(autovalidate != null),
assert(
autovalidate == false ||
autovalidate == true && autovalidateMode == null,
'autovalidate and autovalidateMode should not be used together.'
),
decoration = decoration ?? InputDecoration(focusColor: focusColor),
super(
key: key,
onSaved: onSaved,
initialValue: value,
validator: validator,
autovalidate: autovalidate,
autovalidateMode: autovalidate
? AutovalidateMode.always
: (autovalidateMode ?? AutovalidateMode.disabled),
builder: (FormFieldState<T> field) {
final _DropdownButtonFormFieldState<T> state = field as _DropdownButtonFormFieldState<T>;
final InputDecoration decorationArg = decoration ?? InputDecoration(focusColor: focusColor);
......
......@@ -180,6 +180,7 @@ class TextFormField extends FormField<String> {
InputCounterWidgetBuilder buildCounter,
ScrollPhysics scrollPhysics,
Iterable<String> autofillHints,
AutovalidateMode autovalidateMode,
}) : assert(initialValue == null || controller == null),
assert(textAlign != null),
assert(autofocus != null),
......@@ -189,6 +190,11 @@ class TextFormField extends FormField<String> {
assert(autocorrect != null),
assert(enableSuggestions != null),
assert(autovalidate != null),
assert(
autovalidate == false ||
autovalidate == true && autovalidateMode == null,
'autovalidate and autovalidateMode should not be used together.'
),
assert(maxLengthEnforced != null),
assert(scrollPadding != null),
assert(maxLines == null || maxLines > 0),
......@@ -206,67 +212,69 @@ class TextFormField extends FormField<String> {
assert(maxLength == null || maxLength > 0),
assert(enableInteractiveSelection != null),
super(
key: key,
initialValue: controller != null ? controller.text : (initialValue ?? ''),
onSaved: onSaved,
validator: validator,
autovalidate: autovalidate,
enabled: enabled ?? decoration?.enabled ?? true,
builder: (FormFieldState<String> field) {
final _TextFormFieldState state = field as _TextFormFieldState;
final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
if (onChanged != null) {
onChanged(value);
}
field.didChange(value);
}
return TextField(
controller: state._effectiveController,
focusNode: focusNode,
decoration: effectiveDecoration.copyWith(errorText: field.errorText),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
textDirection: textDirection,
textCapitalization: textCapitalization,
autofocus: autofocus,
toolbarOptions: toolbarOptions,
readOnly: readOnly,
showCursor: showCursor,
obscuringCharacter: obscuringCharacter,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
smartQuotesType: smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
enableSuggestions: enableSuggestions,
maxLengthEnforced: maxLengthEnforced,
maxLines: maxLines,
minLines: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: enabled ?? decoration?.enabled ?? true,
cursorWidth: cursorWidth,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: enableInteractiveSelection,
buildCounter: buildCounter,
autofillHints: autofillHints,
);
},
);
key: key,
initialValue: controller != null ? controller.text : (initialValue ?? ''),
onSaved: onSaved,
validator: validator,
enabled: enabled ?? decoration?.enabled ?? true,
autovalidateMode: autovalidate
? AutovalidateMode.always
: (autovalidateMode ?? AutovalidateMode.disabled),
builder: (FormFieldState<String> field) {
final _TextFormFieldState state = field as _TextFormFieldState;
final InputDecoration effectiveDecoration = (decoration ?? const InputDecoration())
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
if (onChanged != null) {
onChanged(value);
}
field.didChange(value);
}
return TextField(
controller: state._effectiveController,
focusNode: focusNode,
decoration: effectiveDecoration.copyWith(errorText: field.errorText),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
textDirection: textDirection,
textCapitalization: textCapitalization,
autofocus: autofocus,
toolbarOptions: toolbarOptions,
readOnly: readOnly,
showCursor: showCursor,
obscuringCharacter: obscuringCharacter,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
smartQuotesType: smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
enableSuggestions: enableSuggestions,
maxLengthEnforced: maxLengthEnforced,
maxLines: maxLines,
minLines: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: enabled ?? decoration?.enabled ?? true,
cursorWidth: cursorWidth,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: enableInteractiveSelection,
buildCounter: buildCounter,
autofillHints: autofillHints,
);
},
);
/// Controls the text being edited.
///
......
......@@ -81,7 +81,17 @@ class Form extends StatefulWidget {
this.autovalidate = false,
this.onWillPop,
this.onChanged,
AutovalidateMode autovalidateMode,
}) : assert(child != null),
assert(autovalidate != null),
assert(
autovalidate == false ||
autovalidate == true && autovalidateMode == null,
'autovalidate and autovalidateMode should not be used together.'
),
autovalidateMode = autovalidate
? AutovalidateMode.always
: (autovalidateMode ?? AutovalidateMode.disabled),
super(key: key);
/// Returns the closest [FormState] which encloses the given context.
......@@ -127,6 +137,12 @@ class Form extends StatefulWidget {
/// will rebuild.
final VoidCallback onChanged;
/// Used to enable/disable form fields auto validation and update their error
/// text.
///
/// {@macro flutter.widgets.form.autovalidateMode}
final AutovalidateMode autovalidateMode;
@override
FormState createState() => FormState();
}
......@@ -139,6 +155,7 @@ class Form extends StatefulWidget {
/// Typically obtained via [Form.of].
class FormState extends State<Form> {
int _generation = 0;
bool _hasInteractedByUser = false;
final Set<FormFieldState<dynamic>> _fields = <FormFieldState<dynamic>>{};
// Called when a form field has changed. This will cause all form fields
......@@ -146,6 +163,10 @@ class FormState extends State<Form> {
void _fieldDidChange() {
if (widget.onChanged != null)
widget.onChanged();
_hasInteractedByUser = _fields
.any((FormFieldState<dynamic> field) => field._hasInteractedByUser);
_forceRebuild();
}
......@@ -165,8 +186,19 @@ class FormState extends State<Form> {
@override
Widget build(BuildContext context) {
if (widget.autovalidate)
_validate();
switch (widget.autovalidateMode) {
case AutovalidateMode.always:
_validate();
break;
case AutovalidateMode.onUserInteraction:
if (_hasInteractedByUser) {
_validate();
}
break;
case AutovalidateMode.disabled:
break;
}
return WillPopScope(
onWillPop: widget.onWillPop,
child: _FormScope(
......@@ -188,11 +220,12 @@ class FormState extends State<Form> {
///
/// The [Form.onChanged] callback will be called.
///
/// If the form's [Form.autovalidate] property is true, the fields will all be
/// revalidated after being reset.
/// If the form's [Form.autovalidateMode] property is [AutovalidateMode.always],
/// the fields will all be revalidated after being reset.
void reset() {
for (final FormFieldState<dynamic> field in _fields)
field.reset();
_hasInteractedByUser = false;
_fieldDidChange();
}
......@@ -201,6 +234,7 @@ class FormState extends State<Form> {
///
/// The form will rebuild to report the results.
bool validate() {
_hasInteractedByUser = true;
_forceRebuild();
return _validate();
}
......@@ -287,7 +321,16 @@ class FormField<T> extends StatefulWidget {
this.initialValue,
this.autovalidate = false,
this.enabled = true,
AutovalidateMode autovalidateMode,
}) : assert(builder != null),
assert(
autovalidate == false ||
autovalidate == true && autovalidateMode == null,
'autovalidate and autovalidateMode should not be used together.'
),
autovalidateMode = autovalidate
? AutovalidateMode.always
: (autovalidateMode ?? AutovalidateMode.disabled),
super(key: key);
/// An optional method to call with the final value when the form is saved via
......@@ -325,11 +368,26 @@ class FormField<T> extends StatefulWidget {
/// Whether the form is able to receive user input.
///
/// Defaults to true. If [autovalidate] is true, the field will be validated.
/// Likewise, if this field is false, the widget will not be validated
/// regardless of [autovalidate].
/// Defaults to true. If [autovalidateMode] is not [AutovalidateMode.disabled],
/// the field will be auto validated. Likewise, if this field is false, the widget
/// will not be validated regardless of [autovalidateMode].
final bool enabled;
/// Used to enable/disable this form field auto validation and update its
/// error text.
///
/// {@template flutter.widgets.form.autovalidateMode}
/// If [AutovalidateMode.onUserInteraction] this form field will only
/// auto-validate after its content changes, if [AutovalidateMode.always] it
/// will auto validate even without user interaction and
/// if [AutovalidateMode.disabled] the auto validation will be disabled.
///
/// Defaults to [AutovalidateMode.disabled] if [autovalidate] is false which
/// means no auto validation will occur. If [autovalidate] is true then this
/// is set to [AutovalidateMode.always] for backward compatibility.
/// {@endtemplate}
final AutovalidateMode autovalidateMode;
@override
FormFieldState<T> createState() => FormFieldState<T>();
}
......@@ -339,6 +397,7 @@ class FormField<T> extends StatefulWidget {
class FormFieldState<T> extends State<FormField<T>> {
T _value;
String _errorText;
bool _hasInteractedByUser = false;
/// The current value of the form field.
T get value => _value;
......@@ -371,8 +430,10 @@ class FormFieldState<T> extends State<FormField<T>> {
void reset() {
setState(() {
_value = widget.initialValue;
_hasInteractedByUser = false;
_errorText = null;
});
Form.of(context)?._fieldDidChange();
}
/// Calls [FormField.validator] to set the [errorText]. Returns true if there
......@@ -397,11 +458,13 @@ class FormFieldState<T> extends State<FormField<T>> {
/// Updates this field's state to the new value. Useful for responding to
/// child widget changes, e.g. [Slider]'s [Slider.onChanged] argument.
///
/// Triggers the [Form.onChanged] callback and, if the [Form.autovalidate]
/// field is set, revalidates all the fields of the form.
/// Triggers the [Form.onChanged] callback and, if [Form.autovalidateMode] is
/// [AutovalidateMode.always] or [AutovalidateMode.onUserInteraction],
/// revalidates all the fields of the form.
void didChange(T value) {
setState(() {
_value = value;
_hasInteractedByUser = true;
});
Form.of(context)?._fieldDidChange();
}
......@@ -432,10 +495,34 @@ class FormFieldState<T> extends State<FormField<T>> {
@override
Widget build(BuildContext context) {
// Only autovalidate if the widget is also enabled
if (widget.autovalidate && widget.enabled)
_validate();
if (widget.enabled) {
switch (widget.autovalidateMode) {
case AutovalidateMode.always:
_validate();
break;
case AutovalidateMode.onUserInteraction:
if (_hasInteractedByUser) {
_validate();
}
break;
case AutovalidateMode.disabled:
break;
}
}
Form.of(context)?._register(this);
return widget.builder(this);
}
}
/// Used to configure the auto validation of [FormField] and [Form] widgets.
enum AutovalidateMode {
/// No auto validation will occur.
disabled,
/// Used to auto-validate [Form] and [FormField] even without user interaction.
always,
/// Used to auto-validate [Form] and [FormField] only after each user
/// interaction.
onUserInteraction,
}
......@@ -29,7 +29,7 @@ Finder _iconRichText(Key iconKey) {
Widget buildFormFrame({
Key buttonKey,
bool autovalidate = false,
AutovalidateMode autovalidateMode = AutovalidateMode.disabled,
int elevation = 8,
String value = 'two',
ValueChanged<String> onChanged,
......@@ -55,7 +55,7 @@ Widget buildFormFrame({
child: RepaintBoundary(
child: DropdownButtonFormField<String>(
key: buttonKey,
autovalidate: autovalidate,
autovalidateMode: autovalidateMode,
elevation: elevation,
value: value,
hint: hint,
......@@ -180,7 +180,7 @@ void main() {
_validateCalled++;
return currentValue == null ? 'Must select value' : null;
},
autovalidate: true,
autovalidateMode: AutovalidateMode.always,
),
),
);
......@@ -763,4 +763,57 @@ void main() {
expect(currentValue, equals('one'));
expect(find.text(currentValue), findsOneWidget);
});
testWidgets('autovalidateMode is passed to super', (WidgetTester tester) async {
int _validateCalled = 0;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: DropdownButtonFormField<String>(
autovalidateMode: AutovalidateMode.always,
items: menuItems.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: onChanged,
validator: (String value) {
_validateCalled++;
return null;
},
),
),
),
),
);
expect(_validateCalled, 1);
});
testWidgets('autovalidateMode and autovalidate should not be used at the same time', (WidgetTester tester) async {
Widget builder() {
return MaterialApp(
home: Material(
child: Center(
child: DropdownButtonFormField<String>(
autovalidate: true,
autovalidateMode: AutovalidateMode.always,
items: menuItems.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: onChanged,
),
),
),
);
}
expect(() => builder(), throwsAssertionError);
});
}
......@@ -2169,6 +2169,7 @@ void main() {
icon: Container(),
items: itemValues.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
......
......@@ -191,7 +191,7 @@ void main() {
expect(_value, 'Soup');
});
testWidgets('autovalidate is passed to super', (WidgetTester tester) async {
testWidgets('autovalidateMode is passed to super', (WidgetTester tester) async {
int _validateCalled = 0;
await tester.pumpWidget(
......@@ -199,7 +199,7 @@ void main() {
home: Material(
child: Center(
child: TextFormField(
autovalidate: true,
autovalidateMode: AutovalidateMode.always,
validator: (String value) {
_validateCalled++;
return null;
......@@ -225,7 +225,7 @@ void main() {
child: Center(
child: TextFormField(
enabled: true,
autovalidate: true,
autovalidateMode: AutovalidateMode.always,
validator: (String value) {
_validateCalled += 1;
return null;
......@@ -444,4 +444,46 @@ void main() {
final TextField widget = tester.widget(find.byType(TextField));
expect(widget.autofillHints, equals(const <String>[AutofillHints.countryName]));
});
testWidgets('autovalidateMode is passed to super', (WidgetTester tester) async {
int _validateCalled = 0;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Scaffold(
body: TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (String value) {
_validateCalled++;
return null;
},
),
),
),
),
);
expect(_validateCalled, 0);
await tester.enterText(find.byType(TextField), 'a');
await tester.pump();
expect(_validateCalled, 1);
});
testWidgets('autovalidateMode and autovalidate should not be used at the same time', (WidgetTester tester) async {
expect(() async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Scaffold(
body: TextFormField(
autovalidate: true,
autovalidateMode: AutovalidateMode.onUserInteraction,
),
),
),
),
);
}, throwsAssertionError);
});
}
......@@ -89,7 +89,7 @@ void main() {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
String errorText(String value) => value + '/error';
Widget builder(bool autovalidate) {
Widget builder(AutovalidateMode autovalidateMode) {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
......@@ -99,7 +99,7 @@ void main() {
child: Material(
child: Form(
key: formKey,
autovalidate: autovalidate,
autovalidateMode: autovalidateMode,
child: TextFormField(
validator: errorText,
),
......@@ -112,11 +112,11 @@ void main() {
}
// Start off not autovalidating.
await tester.pumpWidget(builder(false));
await tester.pumpWidget(builder(AutovalidateMode.disabled));
Future<void> checkErrorText(String testValue) async {
formKey.currentState.reset();
await tester.pumpWidget(builder(false));
await tester.pumpWidget(builder(AutovalidateMode.disabled));
await tester.enterText(find.byType(TextFormField), testValue);
await tester.pump();
......@@ -128,7 +128,7 @@ void main() {
// Try again with autovalidation. Should validate immediately.
formKey.currentState.reset();
await tester.pumpWidget(builder(true));
await tester.pumpWidget(builder(AutovalidateMode.always));
await tester.enterText(find.byType(TextFormField), testValue);
await tester.pump();
......@@ -160,13 +160,13 @@ void main() {
key: fieldKey1,
initialValue: validString,
validator: validator,
autovalidate: true
autovalidateMode: AutovalidateMode.always,
),
TextFormField(
key: fieldKey2,
initialValue: validString,
validator: validator,
autovalidate: true
autovalidateMode: AutovalidateMode.always,
),
],
),
......@@ -207,13 +207,13 @@ void main() {
key: fieldKey1,
initialValue: validString,
validator: validator,
autovalidate: false,
autovalidateMode: AutovalidateMode.disabled,
),
TextFormField(
key: fieldKey2,
initialValue: '',
validator: validator,
autovalidate: false,
autovalidateMode: AutovalidateMode.disabled,
),
],
),
......@@ -249,7 +249,7 @@ void main() {
child: Material(
child: Form(
key: formKey,
autovalidate: true,
autovalidateMode: AutovalidateMode.always,
child: ListView(
children: <Widget>[
TextFormField(
......@@ -580,4 +580,273 @@ void main() {
formKey.currentState.save();
expect(formKey.currentState.validate(), isTrue);
});
testWidgets('Does not auto-validate before value changes when autovalidateMode is set to onUserInteraction', (WidgetTester tester) async {
FormFieldState<String> formFieldState;
String errorText(String value) => '$value/error';
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: FormField<String>(
initialValue: 'foo',
autovalidateMode: AutovalidateMode.onUserInteraction,
builder: (FormFieldState<String> state) {
formFieldState = state;
return Container();
},
validator: errorText,
),
),
),
),
),
);
}
await tester.pumpWidget(builder());
// The form field has no error.
expect(formFieldState.hasError, isFalse);
// No error widget is visible.
expect(find.text(errorText('foo')), findsNothing);
});
testWidgets('auto-validate before value changes if autovalidateMode was set to always', (WidgetTester tester) async {
FormFieldState<String> formFieldState;
String errorText(String value) => '$value/error';
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: FormField<String>(
initialValue: 'foo',
autovalidateMode: AutovalidateMode.always,
builder: (FormFieldState<String> state) {
formFieldState = state;
return Container();
},
validator: errorText,
),
),
),
),
),
);
}
await tester.pumpWidget(builder());
expect(formFieldState.hasError, isTrue);
});
testWidgets('Form auto-validates form fields only after one of them changes if autovalidateMode is onUserInteraction', (WidgetTester tester) async {
const String initialValue = 'foo';
String errorText(String value) => 'error/$value';
Widget builder() {
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: <Widget>[
TextFormField(
initialValue: initialValue,
validator: errorText,
),
TextFormField(
initialValue: initialValue,
validator: errorText,
),
TextFormField(
initialValue: initialValue,
validator: errorText,
)
],
),
),
),
),
),
);
}
// Makes sure the Form widget won't autovalidate the form fields
// after rebuilds if there is not user interaction.
await tester.pumpWidget(builder());
await tester.pumpWidget(builder());
// We expect no validation error text being shown.
expect(find.text(errorText(initialValue)), findsNothing);
// Set a empty string into the first form field to
// trigger the fields validators.
await tester.enterText(find.byType(TextFormField).first, '');
await tester.pump();
// Now we expect the errors to be shown for the first Text Field and
// for the next two form fields that have their contents unchanged.
expect(find.text(errorText('')), findsOneWidget);
expect(find.text(errorText(initialValue)), findsNWidgets(2));
});
testWidgets('Form auto-validates form fields even before any have changed if autovalidateMode is set to always', (WidgetTester tester) async {
String errorText(String value) => 'error/$value';
Widget builder() {
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: Form(
autovalidateMode: AutovalidateMode.always,
child: TextFormField(
validator: errorText,
),
),
),
),
),
);
}
// The issue only happens on the second build so we
// need to rebuild the tree twice.
await tester.pumpWidget(builder());
await tester.pumpWidget(builder());
// We expect validation error text being shown.
expect(find.text(errorText('')), findsOneWidget);
});
testWidgets('autovalidate parameter is still used if true', (WidgetTester tester) async {
FormFieldState<String> formFieldState;
String errorText(String value) => '$value/error';
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Material(
child: FormField<String>(
initialValue: 'foo',
autovalidate: true,
builder: (FormFieldState<String> state) {
formFieldState = state;
return Container();
},
validator: errorText,
),
),
),
),
),
);
}
await tester.pumpWidget(builder());
expect(formFieldState.hasError, isTrue);
});
testWidgets('Form.reset() resets form fields, and auto validation will only happen on the next user interaction if autovalidateMode is onUserInteraction', (WidgetTester tester) async {
final GlobalKey<FormState> formState = GlobalKey<FormState>();
String errorText(String value) => '$value/error';
Widget builder() {
return MaterialApp(
theme: ThemeData(),
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Form(
key: formState,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Material(
child: TextFormField(
initialValue: 'foo',
validator: errorText,
),
),
),
),
),
),
);
}
await tester.pumpWidget(builder());
// No error text is visible yet.
expect(find.text(errorText('foo')), findsNothing);
await tester.enterText(find.byType(TextFormField), 'bar');
await tester.pumpAndSettle();
await tester.pump();
expect(find.text(errorText('bar')), findsOneWidget);
// Resetting the form state should remove the error text.
formState.currentState.reset();
await tester.pump();
expect(find.text(errorText('bar')), findsNothing);
});
testWidgets('Form.autovalidateMode and Form.autovalidate should not be used at the same time', (WidgetTester tester) async {
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: Form(
autovalidate: true,
autovalidateMode: AutovalidateMode.disabled,
child: Container(),
),
),
),
);
}
expect(() => builder(), throwsAssertionError);
});
testWidgets('FormField.autovalidateMode and FormField.autovalidate should not be used at the same time', (WidgetTester tester) async {
Widget builder() {
return MaterialApp(
home: MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: FormField<String>(
autovalidate: true,
autovalidateMode: AutovalidateMode.disabled,
builder: (_) {
return Container();
},
),
),
),
);
}
expect(() => builder(), throwsAssertionError);
});
}
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