fix DropdownMenu keyboard navigation is broken when expandedInsets is set (#156803)

## Description

This PR fixes keyboard navigation when `DropdownMenu.expandedInsets` is used.
Before this PR the Shortcuts widget defining the navigation intents was only added when `DropdownMenu.expandedInsets` was null.
After this PR, the Shortcuts widget is always added.

## Related Issue

Fixes [DropdownMenu keyboard navigation is broken when expandedInsets is set](https://github.com/flutter/flutter/issues/156712).

## Tests

Adds 2 tests.
Replaces several unneeded 'pumpAndSettle'.
This commit is contained in:
Bruno Leroux 2024-10-16 09:30:34 +02:00 committed by GitHub
parent 13718df71d
commit 2430f00a01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 121 additions and 40 deletions

View File

@ -971,12 +971,20 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
).applyDefaults(effectiveInputDecorationTheme)
);
if (widget.expandedInsets != null) {
// If [expandedInsets] is not null, the width of the text field should depend
// on its parent width. So we don't need to use `_DropdownMenuBody` to
// calculate the children's width.
return textField;
}
// If [expandedInsets] is not null, the width of the text field should depend
// on its parent width. So we don't need to use `_DropdownMenuBody` to
// calculate the children's width.
final Widget body = widget.expandedInsets != null
? textField
: _DropdownMenuBody(
width: widget.width,
children: <Widget>[
textField,
..._initialMenu!.map((Widget item) => ExcludeFocus(excluding: !controller.isOpen, child: item)),
trailingButton,
leadingButton,
],
);
return Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
@ -985,15 +993,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
SingleActivator(LogicalKeyboardKey.arrowUp): _ArrowUpIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): _ArrowDownIntent(),
},
child: _DropdownMenuBody(
width: widget.width,
children: <Widget>[
textField,
..._initialMenu!.map((Widget item) => ExcludeFocus(excluding: !controller.isOpen, child: item)),
trailingButton,
leadingButton,
],
),
child: body,
);
},
);

View File

@ -1159,12 +1159,12 @@ void main() {
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Item 0'), true);
// Press down key one more time, the highlight should move to the next item.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Menu 1'), true);
// The previous item should not be highlighted.
@ -1187,12 +1187,12 @@ void main() {
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Item 5'), true);
// Press up key one more time, the highlight should move up to the item 4.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Item 4'), true);
// The previous item should not be highlighted.
@ -1224,20 +1224,103 @@ void main() {
await tester.pump();
await tester.enterText(find.byType(TextField).first, 'example');
await tester.pumpAndSettle();
await tester.pump();
expect(controller.text, 'example');
expect(controller.selection, const TextSelection.collapsed(offset: 7));
// Press left key, the caret should move left.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 6));
// Press Right key, the caret should move right.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 7));
}, variant: TargetPlatformVariant.desktop());
});
// Regression test for https://github.com/flutter/flutter/issues/156712.
testWidgets(
'Up and down keys can highlight the menu item when expandedInsets is set',
(WidgetTester tester) async {
final ThemeData themeData = ThemeData();
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: Scaffold(
body: DropdownMenu<TestMenu>(
expandedInsets: EdgeInsets.zero,
requestFocusOnTap: true,
dropdownMenuEntries: menuChildren,
),
),
));
await tester.tap(find.byType(DropdownMenu<TestMenu>));
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Item 5'), true);
// Press up key one more time, the highlight should move up to the item 4.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Item 4'), true);
// The previous item should not be highlighted.
expect(isItemHighlighted(tester, themeData, 'Item 5'), false);
// Press down key, the highlight should move back to the item 5.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'Item 5'), true);
},
);
// Regression test for https://github.com/flutter/flutter/issues/156712.
testWidgets(
'Left and right keys can move text field selection when expandedInsets is set',
(WidgetTester tester) async {
final TextEditingController controller = TextEditingController();
addTearDown(controller.dispose);
final ThemeData themeData = ThemeData();
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: Scaffold(
body: DropdownMenu<TestMenu>(
expandedInsets: EdgeInsets.zero,
requestFocusOnTap: true,
enableFilter: true,
filterCallback: (List<DropdownMenuEntry<TestMenu>> entries, String filter) {
return entries.where((DropdownMenuEntry<TestMenu> element) => element.label.contains(filter)).toList();
},
dropdownMenuEntries: menuChildren,
controller: controller,
),
),
));
// Open the menu.
await tester.tap(find.byType(DropdownMenu<TestMenu>));
await tester.pump();
await tester.enterText(find.byType(TextField).first, 'example');
await tester.pump();
expect(controller.text, 'example');
expect(controller.selection, const TextSelection.collapsed(offset: 7));
// Press left key, the caret should move left.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 6));
// Press Right key, the caret should move right.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 7));
},
);
// Regression test for https://github.com/flutter/flutter/issues/147253.
testWidgets('Down key and up key can navigate while focused when a label text '
@ -1271,24 +1354,24 @@ void main() {
// Press down key three times, the highlight should move to the next item each time.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'ABC'), true);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'AB'), true);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'ABCD'), true);
// Press up key two times, the highlight should up each time.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'AB'), true);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'ABC'), true);
});
@ -1309,9 +1392,9 @@ void main() {
await tester.tap(find.byType(DropdownMenu<TestMenu>));
await tester.pump();
await tester.enterText(find.byType(TextField).first, 'Me');
await tester.pumpAndSettle();
await tester.pump();
await tester.enterText(find.byType(TextField).first, 'Meu');
await tester.pumpAndSettle();
await tester.pump();
expect(tester.takeException(), isNull);
});
@ -1354,15 +1437,15 @@ void main() {
const String itemLabel = 'Item 2';
// Open the menu
await tester.tap(find.byType(DropdownMenu<TestMenu>));
await tester.pumpAndSettle();
await tester.pump();
// Highlight the third item by exact search.
await tester.enterText(find.byType(TextField).first, itemLabel);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, itemLabel), true);
// Search something that matches multiple items.
await tester.enterText(find.byType(TextField).first, 'Item');
await tester.pumpAndSettle();
await tester.pump();
// The third item should still be highlighted.
expect(isItemHighlighted(tester, themeData, itemLabel), true);
});
@ -1408,24 +1491,24 @@ void main() {
// Press down key three times, the highlight should move to the next item each time.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'ABC'), true);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'AB'), true);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'ABCD'), true);
// Press up key two times, the highlight should up each time.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'AB'), true);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
await tester.pump();
expect(isItemHighlighted(tester, themeData, 'ABC'), true);
});
@ -2327,7 +2410,6 @@ 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);
@ -2359,7 +2441,6 @@ void main() {
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 {