From 6338ab1575d3b929ec06a2ed4b7f9888fe969c13 Mon Sep 17 00:00:00 2001 From: Tirth Date: Fri, 7 Apr 2023 03:03:21 +0530 Subject: [PATCH] [DropdownMenu] add helperText & errorText to DropdownMenu Widget (#123775) [DropdownMenu] add helperText & errorText to DropdownMenu Widget --- .../lib/src/material/dropdown_menu.dart | 29 +++++ .../test/material/dropdown_menu_test.dart | 117 ++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index 350eb9b6c4..f60e3ea351 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -127,6 +127,8 @@ class DropdownMenu extends StatefulWidget { this.trailingIcon, this.label, this.hintText, + this.helperText, + this.errorText, this.selectedTrailingIcon, this.enableFilter = false, this.enableSearch = true, @@ -183,6 +185,31 @@ class DropdownMenu extends StatefulWidget { /// Defaults to null; final String? hintText; + /// Text that provides context about the [DropdownMenu]'s value, such + /// as how the value will be used. + /// + /// If non-null, the text is displayed below the input field, in + /// the same location as [errorText]. If a non-null [errorText] value is + /// specified then the helper text is not shown. + /// + /// Defaults to null; + /// + /// See also: + /// + /// * [InputDecoration.helperText], which is the text that provides context about the [InputDecorator.child]'s value. + final String? helperText; + + /// Text that appears below the input field and the border to show the error message. + /// + /// If non-null, the border's color animates to red and the [helperText] is not shown. + /// + /// Defaults to null; + /// + /// See also: + /// + /// * [InputDecoration.errorText], which is the text that appears below the [InputDecorator.child] and the border. + final String? errorText; + /// An optional icon at the end of the text field to indicate that the text /// field is pressed. /// @@ -579,6 +606,8 @@ class _DropdownMenuState extends State> { enabled: widget.enabled, label: widget.label, hintText: widget.hintText, + helperText: widget.helperText, + errorText: widget.errorText, prefixIcon: widget.leadingIcon != null ? Container( key: _leadingKey, child: widget.leadingIcon diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index 62da023541..cddb0cea21 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -1110,6 +1110,123 @@ void main() { expect(textInput1.width, 200); expect(menu1.width, 200); }); + + testWidgets('Semantics does not include hint when input is not empty', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(); + const String hintText = 'I am hintText'; + TestMenu? selectedValue; + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) => MaterialApp( + theme: themeData, + home: Scaffold( + body: Center( + child: DropdownMenu( + requestFocusOnTap: true, + dropdownMenuEntries: menuChildren, + hintText: hintText, + onSelected: (TestMenu? value) { + setState(() { + selectedValue = value; + }); + }, + controller: controller, + ), + ), + ), + ), + ), + ); + final SemanticsNode node = tester.getSemantics(find.text(hintText)); + + expect(selectedValue?.label, null); + expect(node.label, hintText); + expect(node.value, ''); + + await tester.tap(find.byType(DropdownMenu)); + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(MenuItemButton, 'Item 3').last); + await tester.pumpAndSettle(); + expect(selectedValue?.label, 'Item 3'); + expect(node.label, ''); + expect(node.value, 'Item 3'); + }); + + testWidgets('helperText is not visible when errorText is not null', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(); + const String helperText = 'I am helperText'; + const String errorText = 'I am errorText'; + + Widget buildFrame(bool hasError) { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Center( + child: DropdownMenu( + dropdownMenuEntries: menuChildren, + helperText: helperText, + errorText: hasError ? errorText : null, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(false)); + expect(find.text(helperText), findsOneWidget); + expect(find.text(errorText), findsNothing); + + await tester.pumpWidget(buildFrame(true)); + await tester.pumpAndSettle(); + expect(find.text(helperText), findsNothing); + expect(find.text(errorText), findsOneWidget); + }); + + testWidgets('DropdownMenu can respect helperText when helperText is not null', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(); + const String helperText = 'I am helperText'; + + Widget buildFrame() { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Center( + child: DropdownMenu( + dropdownMenuEntries: menuChildren, + helperText: helperText, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame()); + expect(find.text(helperText), findsOneWidget); + }); + + testWidgets('DropdownMenu can respect errorText when errorText is not null', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(); + const String errorText = 'I am errorText'; + + Widget buildFrame() { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Center( + child: DropdownMenu( + dropdownMenuEntries: menuChildren, + errorText: errorText, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame()); + expect(find.text(errorText), findsOneWidget); + }); } enum TestMenu {