diff --git a/README.md b/README.md index dc023aa..c73da12 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ customize to your needs. | [onSaved](https://api.flutter.dev/flutter/widgets/FormField/onSaved.html) | Called with the current selected item when the form is saved | FormFieldSetter | No | | [validator](https://api.flutter.dev/flutter/widgets/FormField/validator.html) | Called to validates if the input is invalid and display error text | FormFieldValidator | No | | [errorBuilder](https://api.flutter.dev/flutter/widgets/FormField/errorBuilder.html) | Called to display a custom error widget instead of the default error text | Widget Function(BuildContext, String) | No | +| [forceErrorText](https://api.flutter.dev/flutter/widgets/FormField/forceErrorText.html) | Forces the FormFieldState into an error state and displays the given text | String | No | | [autovalidateMode](https://api.flutter.dev/flutter/widgets/AutovalidateMode.html) | Used to enable/disable auto validation | AutovalidateMode | No | ## Installation diff --git a/packages/dropdown_button2/CHANGELOG.md b/packages/dropdown_button2/CHANGELOG.md index 8519247..51d8d3e 100644 --- a/packages/dropdown_button2/CHANGELOG.md +++ b/packages/dropdown_button2/CHANGELOG.md @@ -2,6 +2,7 @@ - Upgrade minimum required Flutter SDK version to 3.32.0. - Add `errorBuilder` support for DropdownButtonFormField2 [Flutter core]. +- Add `forceErrorText` support for DropdownButtonFormField2 [Flutter core]. ## 3.0.0 diff --git a/packages/dropdown_button2/lib/src/dropdown_button2.dart b/packages/dropdown_button2/lib/src/dropdown_button2.dart index fabbfb3..59cf71d 100644 --- a/packages/dropdown_button2/lib/src/dropdown_button2.dart +++ b/packages/dropdown_button2/lib/src/dropdown_button2.dart @@ -1068,6 +1068,7 @@ class DropdownButtonFormField2 extends FormField { super.onSaved, super.validator, super.errorBuilder, + super.forceErrorText, AutovalidateMode? autovalidateMode, bool? enableFeedback, AlignmentGeometry alignment = AlignmentDirectional.centerStart, diff --git a/packages/dropdown_button2/test/dropdown_button2_test.dart b/packages/dropdown_button2/test/dropdown_button2_test.dart index 9033acd..d6350a8 100644 --- a/packages/dropdown_button2/test/dropdown_button2_test.dart +++ b/packages/dropdown_button2/test/dropdown_button2_test.dart @@ -173,7 +173,7 @@ void main() { return tester.widget(findInputDecorator.first); } - testWidgets('validator error text is displayed', (WidgetTester tester) async { + testWidgets('validator error text should be displayed when validation fails', (WidgetTester tester) async { final GlobalKey formKey = GlobalKey(); const errorMessage = 'Please select a value'; @@ -201,7 +201,7 @@ void main() { expect(find.text(errorMessage), findsOneWidget); }); - testWidgets('validator returning null shows no error', (WidgetTester tester) async { + testWidgets('validator should show no error when returning null', (WidgetTester tester) async { final GlobalKey formKey = GlobalKey(); await tester.pumpWidget( @@ -230,7 +230,7 @@ void main() { expect(inputDecorator.decoration.error, isNull); }); - testWidgets('autovalidateMode.always validates on first build', ( + testWidgets('autovalidateMode.always should validate on first build', ( WidgetTester tester, ) async { int validateCalled = 0; @@ -258,7 +258,7 @@ void main() { expect(find.text('Error'), findsOneWidget); }); - testWidgets('decoration errorStyle is applied to error text', ( + testWidgets('decoration errorStyle should be applied to error text', ( WidgetTester tester, ) async { const errorStyle = TextStyle(color: Colors.orange, fontSize: 20); @@ -287,7 +287,7 @@ void main() { expect(find.text('Styled error'), findsOneWidget); }); - testWidgets('decoration errorMaxLines is respected', (WidgetTester tester) async { + testWidgets('decoration errorMaxLines should be respected', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -311,7 +311,7 @@ void main() { expect(inputDecorator.decoration.errorMaxLines, 2); }); - testWidgets('widget returned by errorBuilder is shown', (WidgetTester tester) async { + testWidgets('errorBuilder should replace default error text when provided', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -337,7 +337,7 @@ void main() { expect(find.text('Required'), findsNothing); }); - testWidgets('errorBuilder widget is passed to InputDecorator as error', ( + testWidgets('errorBuilder widget should be passed to InputDecorator as error', ( WidgetTester tester, ) async { final GlobalKey formKey = GlobalKey(); @@ -378,7 +378,7 @@ void main() { expect(find.byIcon(Icons.error), findsOneWidget); }); - testWidgets('errorBuilder is not called when there is no error', ( + testWidgets('errorBuilder should not be called when there is no error', ( WidgetTester tester, ) async { bool errorBuilderCalled = false; @@ -407,6 +407,77 @@ void main() { expect(errorBuilderCalled, isFalse); }); + + testWidgets('forceErrorText should force field to display error', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: DropdownButtonFormField2( + valueListenable: valueListenable, + items: buildItems(), + onChanged: (_) {}, + forceErrorText: 'Forced error', + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('Forced error'), findsOneWidget); + final inputDecorator = getFormFieldInputDecorator(tester); + expect(inputDecorator.decoration.errorText, 'Forced error'); + }); + + testWidgets('forceErrorText should make isValid return false', (WidgetTester tester) async { + final GlobalKey> fieldKey = GlobalKey>(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: DropdownButtonFormField2( + key: fieldKey, + valueListenable: valueListenable, + items: buildItems(), + onChanged: (_) {}, + forceErrorText: 'Forced error', + ), + ), + ), + ), + ); + + expect(fieldKey.currentState!.isValid, isFalse); + expect(fieldKey.currentState!.hasError, isTrue); + }); + + testWidgets('forceErrorText should override InputDecoration.errorText when both are provided', ( + WidgetTester tester, + ) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: DropdownButtonFormField2( + valueListenable: valueListenable, + items: buildItems(), + onChanged: (_) {}, + decoration: const InputDecoration(errorText: 'Decoration error'), + forceErrorText: 'Forced error', + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect(find.text('Forced error'), findsOneWidget); + expect(find.text('Decoration error'), findsNothing); + }); }, ); }