4
4
5
5
import 'dart:ui' ;
6
6
7
- import 'package:flutter/foundation.dart' ;
8
7
import 'package:flutter/material.dart' ;
9
8
import 'package:flutter/rendering.dart' ;
10
9
import 'package:flutter/services.dart' ;
@@ -3493,14 +3492,19 @@ void main() {
3493
3492
// Regression test for https://github.com/flutter/flutter/issues/131120.
3494
3493
testWidgets ('Focus traversal ignores non visible entries' , (WidgetTester tester) async {
3495
3494
final FocusNode buttonFocusNode = FocusNode ();
3495
+ final FocusNode textFieldFocusNode = FocusNode ();
3496
3496
addTearDown (buttonFocusNode.dispose);
3497
+ addTearDown (textFieldFocusNode.dispose);
3497
3498
3498
3499
await tester.pumpWidget (
3499
3500
MaterialApp (
3500
3501
home: Scaffold (
3501
3502
body: Column (
3502
3503
children: < Widget > [
3503
- DropdownMenu <TestMenu >(dropdownMenuEntries: menuChildren),
3504
+ DropdownMenu <TestMenu >(
3505
+ dropdownMenuEntries: menuChildren,
3506
+ focusNode: textFieldFocusNode,
3507
+ ),
3504
3508
ElevatedButton (
3505
3509
focusNode: buttonFocusNode,
3506
3510
onPressed: () {},
@@ -3512,18 +3516,17 @@ void main() {
3512
3516
),
3513
3517
);
3514
3518
3515
- // Move the focus to the text field.
3516
- primaryFocus! .nextFocus ();
3517
- await tester.pump ();
3518
- final Element textField = tester.element (find.byType (TextField ));
3519
- expect (Focus .of (textField).hasFocus, isTrue);
3520
-
3521
3519
// Move the focus to the dropdown trailing icon.
3522
3520
primaryFocus! .nextFocus ();
3523
3521
await tester.pump ();
3524
3522
final Element iconButton = tester.firstElement (find.byIcon (Icons .arrow_drop_down));
3525
3523
expect (Focus .of (iconButton).hasFocus, isTrue);
3526
3524
3525
+ // Move the focus to the text field.
3526
+ primaryFocus! .nextFocus ();
3527
+ await tester.pump ();
3528
+ expect (textFieldFocusNode.hasFocus, isTrue);
3529
+
3527
3530
// Move the focus to the elevated button.
3528
3531
primaryFocus! .nextFocus ();
3529
3532
await tester.pump ();
@@ -4110,11 +4113,11 @@ void main() {
4110
4113
),
4111
4114
),
4112
4115
);
4113
- // Pressing the tab key 3 times moves the focus to the icon button.
4114
- for ( int i = 0 ; i < 3 ; i ++ ) {
4115
- await tester. sendKeyEvent ( LogicalKeyboardKey .tab);
4116
- await tester.pump ( );
4117
- }
4116
+
4117
+ // Adding FocusNode to IconButton causes the IconButton to receive focus.
4118
+ // Thus it does not matter if the TextField has a FocusNode or not.
4119
+ await tester.sendKeyEvent ( LogicalKeyboardKey .tab );
4120
+ await tester. pump ();
4118
4121
4119
4122
// Now the focus is on the icon button.
4120
4123
final Element iconButton = tester.firstElement (find.byIcon (Icons .arrow_drop_down));
@@ -4151,17 +4154,11 @@ void main() {
4151
4154
),
4152
4155
),
4153
4156
);
4154
- // If there is no `FocusNode`, by default, `TextField` can receive focus
4155
- // on desktop platforms, but not on mobile platforms. Therefore, on desktop
4156
- // platforms, it takes 3 tabs to reach the icon button.
4157
- final int tabCount = switch (defaultTargetPlatform) {
4158
- TargetPlatform .iOS || TargetPlatform .android || TargetPlatform .fuchsia => 2 ,
4159
- TargetPlatform .macOS || TargetPlatform .linux || TargetPlatform .windows => 3 ,
4160
- };
4161
- for (int i = 0 ; i < tabCount; i++ ) {
4162
- await tester.sendKeyEvent (LogicalKeyboardKey .tab);
4163
- await tester.pump ();
4164
- }
4157
+
4158
+ // Adding FocusNode to IconButton causes the IconButton to receive focus.
4159
+ // Thus it does not matter if the TextField has a FocusNode or not.
4160
+ await tester.sendKeyEvent (LogicalKeyboardKey .tab);
4161
+ await tester.pump ();
4165
4162
4166
4163
// Now the focus is on the icon button.
4167
4164
final Element iconButton = tester.firstElement (find.byIcon (Icons .arrow_drop_down));
@@ -4588,6 +4585,120 @@ void main() {
4588
4585
},
4589
4586
);
4590
4587
4588
+ testWidgets ('DropdownMenu trailingIconFocusNode is created when not provided' , (
4589
+ WidgetTester tester,
4590
+ ) async {
4591
+ final FocusNode textFieldFocusNode = FocusNode ();
4592
+ final FocusNode buttonFocusNode = FocusNode ();
4593
+ addTearDown (textFieldFocusNode.dispose);
4594
+ addTearDown (buttonFocusNode.dispose);
4595
+
4596
+ await tester.pumpWidget (
4597
+ MaterialApp (
4598
+ home: Scaffold (
4599
+ body: Column (
4600
+ children: < Widget > [
4601
+ DropdownMenu <TestMenu >(
4602
+ dropdownMenuEntries: menuChildren,
4603
+ focusNode: textFieldFocusNode,
4604
+ ),
4605
+ ElevatedButton (
4606
+ focusNode: buttonFocusNode,
4607
+ onPressed: () {},
4608
+ child: const Text ('Button' ),
4609
+ ),
4610
+ ],
4611
+ ),
4612
+ ),
4613
+ ),
4614
+ );
4615
+
4616
+ primaryFocus! .nextFocus ();
4617
+ await tester.pump ();
4618
+
4619
+ // Ensure the trailing icon does not have focus.
4620
+ // If FocusNode is not created then the TextField will have focus.
4621
+ final Element iconButton = tester.firstElement (find.byIcon (Icons .arrow_drop_down));
4622
+ expect (Focus .of (iconButton).hasFocus, isTrue);
4623
+
4624
+ // Ensure the TextField has focus.
4625
+ primaryFocus! .nextFocus ();
4626
+ await tester.pump ();
4627
+ expect (textFieldFocusNode.hasFocus, isTrue);
4628
+
4629
+ // Ensure the button has focus.
4630
+ primaryFocus! .nextFocus ();
4631
+ await tester.pump ();
4632
+ expect (buttonFocusNode.hasFocus, isTrue);
4633
+ });
4634
+
4635
+ testWidgets ('DropdownMenu trailingIconFocusNode is used when provided' , (
4636
+ WidgetTester tester,
4637
+ ) async {
4638
+ final FocusNode textFieldFocusNode = FocusNode ();
4639
+ final FocusNode trailingIconFocusNode = FocusNode ();
4640
+ final FocusNode buttonFocusNode = FocusNode ();
4641
+ addTearDown (textFieldFocusNode.dispose);
4642
+ addTearDown (trailingIconFocusNode.dispose);
4643
+ addTearDown (buttonFocusNode.dispose);
4644
+
4645
+ await tester.pumpWidget (
4646
+ MaterialApp (
4647
+ home: Scaffold (
4648
+ body: Column (
4649
+ children: < Widget > [
4650
+ DropdownMenu <TestMenu >(
4651
+ dropdownMenuEntries: menuChildren,
4652
+ focusNode: textFieldFocusNode,
4653
+ trailingIconFocusNode: trailingIconFocusNode,
4654
+ ),
4655
+ ElevatedButton (
4656
+ focusNode: buttonFocusNode,
4657
+ onPressed: () {},
4658
+ child: const Text ('Button' ),
4659
+ ),
4660
+ ],
4661
+ ),
4662
+ ),
4663
+ ),
4664
+ );
4665
+
4666
+ primaryFocus! .nextFocus ();
4667
+ await tester.pump ();
4668
+
4669
+ // Ensure the trailing icon has focus.
4670
+ expect (trailingIconFocusNode.hasFocus, isTrue);
4671
+
4672
+ // Ensure the TextField has focus.
4673
+ primaryFocus! .nextFocus ();
4674
+ await tester.pump ();
4675
+ expect (textFieldFocusNode.hasFocus, isTrue);
4676
+
4677
+ // Ensure the button has focus.
4678
+ primaryFocus! .nextFocus ();
4679
+ await tester.pump ();
4680
+ expect (buttonFocusNode.hasFocus, isTrue);
4681
+ });
4682
+
4683
+ testWidgets (
4684
+ 'Throw assertion error when showTrailingIcon is false and trailingIconFocusNode is provided' ,
4685
+ (WidgetTester tester) async {
4686
+ expect (() {
4687
+ final FocusNode focusNode = FocusNode ();
4688
+ addTearDown (focusNode.dispose);
4689
+ MaterialApp (
4690
+ home: Scaffold (
4691
+ body: DropdownMenu <TestMenu >(
4692
+ showTrailingIcon: false ,
4693
+ trailingIconFocusNode: focusNode,
4694
+ dropdownMenuEntries: menuChildren,
4695
+ ),
4696
+ ),
4697
+ );
4698
+ }, throwsAssertionError);
4699
+ },
4700
+ );
4701
+
4591
4702
testWidgets ('DropdownMenu can set cursorHeight' , (WidgetTester tester) async {
4592
4703
const double cursorHeight = 4.0 ;
4593
4704
await tester.pumpWidget (
0 commit comments