@@ -9,70 +9,6 @@ import 'package:flutter_test/flutter_test.dart';
9
9
10
10
import 'semantics_tester.dart' ;
11
11
12
- class TestFocus extends StatefulWidget {
13
- const TestFocus ({
14
- super .key,
15
- this .debugLabel,
16
- this .name = 'a' ,
17
- this .autofocus = false ,
18
- });
19
-
20
- final String ? debugLabel;
21
- final String name;
22
- final bool autofocus;
23
-
24
- @override
25
- TestFocusState createState () => TestFocusState ();
26
- }
27
-
28
- class TestFocusState extends State <TestFocus > {
29
- late FocusNode focusNode;
30
- late String _label;
31
- bool built = false ;
32
-
33
- @override
34
- void dispose () {
35
- focusNode.removeListener (_updateLabel);
36
- focusNode.dispose ();
37
- super .dispose ();
38
- }
39
-
40
- String get label => focusNode.hasFocus ? '${widget .name .toUpperCase ()} FOCUSED' : widget.name.toLowerCase ();
41
-
42
- @override
43
- void initState () {
44
- super .initState ();
45
- focusNode = FocusNode (debugLabel: widget.debugLabel);
46
- _label = label;
47
- focusNode.addListener (_updateLabel);
48
- }
49
-
50
- void _updateLabel () {
51
- setState (() {
52
- _label = label;
53
- });
54
- }
55
-
56
- @override
57
- Widget build (BuildContext context) {
58
- built = true ;
59
- return GestureDetector (
60
- onTap: () {
61
- FocusScope .of (context).requestFocus (focusNode);
62
- },
63
- child: Focus (
64
- autofocus: widget.autofocus,
65
- focusNode: focusNode,
66
- debugLabel: widget.debugLabel,
67
- child: Text (
68
- _label,
69
- textDirection: TextDirection .ltr,
70
- ),
71
- ),
72
- );
73
- }
74
- }
75
-
76
12
void main () {
77
13
group ('FocusScope' , () {
78
14
testWidgets ('Can focus' , (WidgetTester tester) async {
@@ -530,6 +466,72 @@ void main() {
530
466
expect (find.text ('A FOCUSED' ), findsOneWidget);
531
467
});
532
468
469
+ testWidgets ('Setting parentNode determines focus tree hierarchy.' , (WidgetTester tester) async {
470
+ final FocusNode topNode = FocusNode (debugLabel: 'Top' );
471
+ final FocusNode parentNode = FocusNode (debugLabel: 'Parent' );
472
+ final FocusNode childNode = FocusNode (debugLabel: 'Child' );
473
+ final FocusNode insertedNode = FocusNode (debugLabel: 'Inserted' );
474
+
475
+ await tester.pumpWidget (
476
+ FocusScope (
477
+ child: Focus .withExternalFocusNode (
478
+ focusNode: topNode,
479
+ child: Column (
480
+ children: < Widget > [
481
+ Focus .withExternalFocusNode (
482
+ focusNode: parentNode,
483
+ child: const SizedBox (),
484
+ ),
485
+ Focus .withExternalFocusNode (
486
+ focusNode: childNode,
487
+ parentNode: parentNode,
488
+ autofocus: true ,
489
+ child: const SizedBox (),
490
+ )
491
+ ],
492
+ ),
493
+ ),
494
+ ),
495
+ );
496
+ await tester.pump ();
497
+
498
+ expect (childNode.hasPrimaryFocus, isTrue);
499
+ expect (parentNode.hasFocus, isTrue);
500
+ expect (topNode.hasFocus, isTrue);
501
+
502
+ // Check that inserting a Focus in between doesn't reparent the child.
503
+ await tester.pumpWidget (
504
+ FocusScope (
505
+ child: Focus .withExternalFocusNode (
506
+ focusNode: topNode,
507
+ child: Column (
508
+ children: < Widget > [
509
+ Focus .withExternalFocusNode (
510
+ focusNode: parentNode,
511
+ child: const SizedBox (),
512
+ ),
513
+ Focus .withExternalFocusNode (
514
+ focusNode: insertedNode,
515
+ child: Focus .withExternalFocusNode (
516
+ focusNode: childNode,
517
+ parentNode: parentNode,
518
+ autofocus: true ,
519
+ child: const SizedBox (),
520
+ ),
521
+ )
522
+ ],
523
+ ),
524
+ ),
525
+ ),
526
+ );
527
+ await tester.pump ();
528
+
529
+ expect (childNode.hasPrimaryFocus, isTrue);
530
+ expect (parentNode.hasFocus, isTrue);
531
+ expect (topNode.hasFocus, isTrue);
532
+ expect (insertedNode.hasFocus, isFalse);
533
+ });
534
+
533
535
// Arguably, this isn't correct behavior, but it is what happens now.
534
536
testWidgets ("Removing focused widget doesn't move focus to next widget within FocusScope" , (WidgetTester tester) async {
535
537
final GlobalKey <TestFocusState > keyA = GlobalKey ();
@@ -2015,3 +2017,70 @@ void main() {
2015
2017
});
2016
2018
});
2017
2019
}
2020
+
2021
+ class TestFocus extends StatefulWidget {
2022
+ const TestFocus ({
2023
+ super .key,
2024
+ this .debugLabel,
2025
+ this .name = 'a' ,
2026
+ this .autofocus = false ,
2027
+ this .parentNode,
2028
+ });
2029
+
2030
+ final String ? debugLabel;
2031
+ final String name;
2032
+ final bool autofocus;
2033
+ final FocusNode ? parentNode;
2034
+
2035
+ @override
2036
+ TestFocusState createState () => TestFocusState ();
2037
+ }
2038
+
2039
+ class TestFocusState extends State <TestFocus > {
2040
+ late FocusNode focusNode;
2041
+ late String _label;
2042
+ bool built = false ;
2043
+
2044
+ @override
2045
+ void dispose () {
2046
+ focusNode.removeListener (_updateLabel);
2047
+ focusNode.dispose ();
2048
+ super .dispose ();
2049
+ }
2050
+
2051
+ String get label => focusNode.hasFocus ? '${widget .name .toUpperCase ()} FOCUSED' : widget.name.toLowerCase ();
2052
+
2053
+ @override
2054
+ void initState () {
2055
+ super .initState ();
2056
+ focusNode = FocusNode (debugLabel: widget.debugLabel);
2057
+ _label = label;
2058
+ focusNode.addListener (_updateLabel);
2059
+ }
2060
+
2061
+ void _updateLabel () {
2062
+ setState (() {
2063
+ _label = label;
2064
+ });
2065
+ }
2066
+
2067
+ @override
2068
+ Widget build (BuildContext context) {
2069
+ built = true ;
2070
+ return GestureDetector (
2071
+ onTap: () {
2072
+ FocusScope .of (context).requestFocus (focusNode);
2073
+ },
2074
+ child: Focus (
2075
+ autofocus: widget.autofocus,
2076
+ focusNode: focusNode,
2077
+ parentNode: widget.parentNode,
2078
+ debugLabel: widget.debugLabel,
2079
+ child: Text (
2080
+ _label,
2081
+ textDirection: TextDirection .ltr,
2082
+ ),
2083
+ ),
2084
+ );
2085
+ }
2086
+ }
0 commit comments