diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index 9c11c5523e8..1ea2aa525d3 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -674,6 +674,7 @@ class _DropdownMenuState extends State> { TextDirection textDirection, { int? focusedIndex, bool enableScrollToHighlight = true, + bool excludeSemantics = false, }) { final List result = []; for (int i = 0; i < filteredEntries.length; i++) { @@ -743,28 +744,31 @@ class _DropdownMenuState extends State> { ); } - final Widget menuItemButton = MenuItemButton( - key: enableScrollToHighlight ? buttonItemKeys[i] : null, - style: effectiveStyle, - leadingIcon: entry.leadingIcon, - trailingIcon: entry.trailingIcon, - closeOnActivate: widget.closeBehavior == DropdownMenuCloseBehavior.all, - onPressed: entry.enabled && widget.enabled - ? () { - _localTextEditingController?.value = TextEditingValue( - text: entry.label, - selection: TextSelection.collapsed(offset: entry.label.length), - ); - currentHighlight = widget.enableSearch ? i : null; - widget.onSelected?.call(entry.value); - _enableFilter = false; - if (widget.closeBehavior == DropdownMenuCloseBehavior.self) { - _controller.close(); + final Widget menuItemButton = ExcludeSemantics( + excluding: excludeSemantics, + child: MenuItemButton( + key: enableScrollToHighlight ? buttonItemKeys[i] : null, + style: effectiveStyle, + leadingIcon: entry.leadingIcon, + trailingIcon: entry.trailingIcon, + closeOnActivate: widget.closeBehavior == DropdownMenuCloseBehavior.all, + onPressed: entry.enabled && widget.enabled + ? () { + _localTextEditingController?.value = TextEditingValue( + text: entry.label, + selection: TextSelection.collapsed(offset: entry.label.length), + ); + currentHighlight = widget.enableSearch ? i : null; + widget.onSelected?.call(entry.value); + _enableFilter = false; + if (widget.closeBehavior == DropdownMenuCloseBehavior.self) { + _controller.close(); + } } - } - : null, - requestFocusOnHover: false, - child: label, + : null, + requestFocusOnHover: false, + child: label, + ), ); result.add(menuItemButton); } @@ -828,7 +832,13 @@ class _DropdownMenuState extends State> { @override Widget build(BuildContext context) { final TextDirection textDirection = Directionality.of(context); - _initialMenu ??= _buildButtons(widget.dropdownMenuEntries, textDirection, enableScrollToHighlight: false); + _initialMenu ??= _buildButtons( + widget.dropdownMenuEntries, + textDirection, + enableScrollToHighlight: false, + // The _initialMenu is invisible, we should not add semantics nodes to it + excludeSemantics: true, + ); final DropdownMenuThemeData theme = DropdownMenuTheme.of(context); final DropdownMenuThemeData defaults = _DropdownMenuDefaultsM3(context); diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index bcc77b90f6b..17c069482c3 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -2286,7 +2286,6 @@ void main() { }); 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(); @@ -2295,7 +2294,6 @@ void main() { await tester.pumpWidget( StatefulBuilder( builder: (BuildContext context, StateSetter setState) => MaterialApp( - theme: themeData, home: Scaffold( body: Center( child: DropdownMenu( @@ -2329,6 +2327,41 @@ void main() { expect(node.value, 'Item 3'); }); + + testWidgets('Semantics does not include initial menu buttons', (WidgetTester tester) async { + final TextEditingController controller = TextEditingController(); + addTearDown(controller.dispose); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: DropdownMenu( + requestFocusOnTap: true, + dropdownMenuEntries: menuChildren, + onSelected: (TestMenu? value) {}, + controller: controller, + ), + ), + ), + ), + ); + // The menu buttons should not be visible and should not be in the semantics tree. + for (final String label in TestMenu.values.map((TestMenu menu) => menu.label)) { + expect(find.bySemanticsLabel(label), findsNothing); + } + + // Open the menu. + await tester.tap(find.widgetWithIcon(IconButton, Icons.arrow_drop_down).first); + await tester.pump(); + + // The menu buttons should be visible and in the semantics tree. + for (final String label in TestMenu.values.map((TestMenu menu) => menu.label)) { + expect(find.bySemanticsLabel(label), findsOneWidget); + } + + }); + testWidgets('helperText is not visible when errorText is not null', (WidgetTester tester) async { final ThemeData themeData = ThemeData(); const String helperText = 'I am helperText';