@@ -9,70 +9,6 @@ import 'package:flutter_test/flutter_test.dart';
99
1010import 'semantics_tester.dart' ;
1111
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-
7612void main () {
7713 group ('FocusScope' , () {
7814 testWidgets ('Can focus' , (WidgetTester tester) async {
@@ -530,6 +466,72 @@ void main() {
530466 expect (find.text ('A FOCUSED' ), findsOneWidget);
531467 });
532468
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+
533535 // Arguably, this isn't correct behavior, but it is what happens now.
534536 testWidgets ("Removing focused widget doesn't move focus to next widget within FocusScope" , (WidgetTester tester) async {
535537 final GlobalKey <TestFocusState > keyA = GlobalKey ();
@@ -2015,3 +2017,70 @@ void main() {
20152017 });
20162018 });
20172019}
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