focus_scope_test.dart 58.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7
import 'dart:ui';

import 'package:flutter/semantics.dart';
8 9 10
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
11

12 13
import 'semantics_tester.dart';

14 15
class TestFocus extends StatefulWidget {
  const TestFocus({
16
    Key? key,
17
    this.debugLabel,
18 19 20 21
    this.name = 'a',
    this.autofocus = false,
  }) : super(key: key);

22
  final String? debugLabel;
23 24 25 26
  final String name;
  final bool autofocus;

  @override
27
  TestFocusState createState() => TestFocusState();
28 29
}

30
class TestFocusState extends State<TestFocus> {
31 32
  late FocusNode focusNode;
  late String _label;
33
  bool built = false;
34 35 36

  @override
  void dispose() {
37
    focusNode.removeListener(_updateLabel);
38
    focusNode.dispose();
39 40 41
    super.dispose();
  }

42 43
  String get label => focusNode.hasFocus ? '${widget.name.toUpperCase()} FOCUSED' : widget.name.toLowerCase();

44 45 46 47
  @override
  void initState() {
    super.initState();
    focusNode = FocusNode(debugLabel: widget.debugLabel);
48 49 50 51 52 53 54 55
    _label = label;
    focusNode.addListener(_updateLabel);
  }

  void _updateLabel() {
    setState(() {
      _label = label;
    });
56 57
  }

58 59
  @override
  Widget build(BuildContext context) {
60
    built = true;
61 62 63 64
    return GestureDetector(
      onTap: () {
        FocusScope.of(context).requestFocus(focusNode);
      },
65 66 67 68 69 70 71 72
      child: Focus(
        autofocus: widget.autofocus,
        focusNode: focusNode,
        debugLabel: widget.debugLabel,
        child: Text(
          _label,
          textDirection: TextDirection.ltr,
        ),
73 74 75 76 77 78
      ),
    );
  }
}

void main() {
79
  group('FocusScope', () {
80 81
    testWidgets('Can focus', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> key = GlobalKey();
82

83 84 85
      await tester.pumpWidget(
        TestFocus(key: key, name: 'a'),
      );
86

87
      expect(key.currentState!.focusNode.hasFocus, isFalse);
88

89
      FocusScope.of(key.currentContext!).requestFocus(key.currentState!.focusNode);
90
      await tester.pumpAndSettle();
91

92
      expect(key.currentState!.focusNode.hasFocus, isTrue);
93 94
      expect(find.text('A FOCUSED'), findsOneWidget);
    });
95

96 97 98 99 100
    testWidgets('Can unfocus', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
      await tester.pumpWidget(
        Column(
101
          children: <Widget>[
102 103
            TestFocus(key: keyA, name: 'a'),
            TestFocus(key: keyB, name: 'b'),
104
          ],
105
        ),
106
      );
107

108
      expect(keyA.currentState!.focusNode.hasFocus, isFalse);
109
      expect(find.text('a'), findsOneWidget);
110
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
111
      expect(find.text('b'), findsOneWidget);
112

113
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
114
      await tester.pumpAndSettle();
115

116
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
117
      expect(find.text('A FOCUSED'), findsOneWidget);
118
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
119
      expect(find.text('b'), findsOneWidget);
120

121
      // Set focus to the "B" node to unfocus the "A" node.
122
      FocusScope.of(keyB.currentContext!).requestFocus(keyB.currentState!.focusNode);
123
      await tester.pumpAndSettle();
124

125
      expect(keyA.currentState!.focusNode.hasFocus, isFalse);
126
      expect(find.text('a'), findsOneWidget);
127
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
128 129
      expect(find.text('B FOCUSED'), findsOneWidget);
    });
130

131 132 133 134 135 136 137 138 139 140 141 142 143 144
    testWidgets('Autofocus works', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
      await tester.pumpWidget(
        Column(
          children: <Widget>[
            TestFocus(key: keyA, name: 'a'),
            TestFocus(key: keyB, name: 'b', autofocus: true),
          ],
        ),
      );

      await tester.pump();

145
      expect(keyA.currentState!.focusNode.hasFocus, isFalse);
146
      expect(find.text('a'), findsOneWidget);
147
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
148 149 150
      expect(find.text('B FOCUSED'), findsOneWidget);
    });

151 152 153 154 155 156
    testWidgets('Can have multiple focused children and they update accordingly', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();

      await tester.pumpWidget(
        Column(
157
          children: <Widget>[
158 159 160 161 162 163 164 165
            TestFocus(
              key: keyA,
              name: 'a',
              autofocus: true,
            ),
            TestFocus(
              key: keyB,
              name: 'b',
166 167 168
            ),
          ],
        ),
169 170 171 172
      );

      // Autofocus is delayed one frame.
      await tester.pump();
173
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
174
      expect(find.text('A FOCUSED'), findsOneWidget);
175
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
176 177 178
      expect(find.text('b'), findsOneWidget);
      await tester.tap(find.text('A FOCUSED'));
      await tester.pump();
179
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
180
      expect(find.text('A FOCUSED'), findsOneWidget);
181
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
182 183 184
      expect(find.text('b'), findsOneWidget);
      await tester.tap(find.text('b'));
      await tester.pump();
185
      expect(keyA.currentState!.focusNode.hasFocus, isFalse);
186
      expect(find.text('a'), findsOneWidget);
187
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
188 189 190
      expect(find.text('B FOCUSED'), findsOneWidget);
      await tester.tap(find.text('a'));
      await tester.pump();
191
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
192
      expect(find.text('A FOCUSED'), findsOneWidget);
193
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
      expect(find.text('b'), findsOneWidget);
    });

    // This moves a focus node first into a focus scope that is added to its
    // parent, and then out of that focus scope again.
    testWidgets('Can move focus in and out of FocusScope', (WidgetTester tester) async {
      final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node');
      final FocusScopeNode childFocusScope = FocusScopeNode(debugLabel: 'Child Scope Node');
      final GlobalKey<TestFocusState> key = GlobalKey();

      // Initially create the focus inside of the parent FocusScope.
      await tester.pumpWidget(
        FocusScope(
          debugLabel: 'Parent Scope',
          node: parentFocusScope,
          autofocus: true,
          child: Column(
            children: <Widget>[
              TestFocus(
                key: key,
                name: 'a',
                debugLabel: 'Child',
              ),
            ],
          ),
        ),
      );

222
      expect(key.currentState!.focusNode.hasFocus, isFalse);
223
      expect(find.text('a'), findsOneWidget);
224
      FocusScope.of(key.currentContext!).requestFocus(key.currentState!.focusNode);
225 226
      await tester.pumpAndSettle();

227
      expect(key.currentState!.focusNode.hasFocus, isTrue);
228 229 230 231 232
      expect(find.text('A FOCUSED'), findsOneWidget);

      expect(parentFocusScope, hasAGoodToStringDeep);
      expect(
        parentFocusScope.toStringDeep(),
233
        equalsIgnoringHashCodes('FocusScopeNode#00000(Parent Scope Node [IN FOCUS PATH])\n'
234
            ' │ context: FocusScope\n'
235
            ' │ IN FOCUS PATH\n'
236
            ' │ focusedChildren: FocusNode#00000(Child [PRIMARY FOCUS])\n'
237
            ' │\n'
238
            ' └─Child 1: FocusNode#00000(Child [PRIMARY FOCUS])\n'
239
            '     context: Focus\n'
240
            '     PRIMARY FOCUS\n'),
241 242
      );

243
      expect(FocusManager.instance.rootScope, hasAGoodToStringDeep);
244
      expect(
245
        FocusManager.instance.rootScope.toStringDeep(minLevel: DiagnosticLevel.info),
246
        equalsIgnoringHashCodes('FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n'
247
            ' │ IN FOCUS PATH\n'
248 249
            ' │ focusedChildren: FocusScopeNode#00000(Parent Scope Node [IN FOCUS\n'
            ' │   PATH])\n'
250
            ' │\n'
251
            ' └─Child 1: FocusScopeNode#00000(Parent Scope Node [IN FOCUS PATH])\n'
252
            '   │ context: FocusScope\n'
253
            '   │ IN FOCUS PATH\n'
254
            '   │ focusedChildren: FocusNode#00000(Child [PRIMARY FOCUS])\n'
255
            '   │\n'
256
            '   └─Child 1: FocusNode#00000(Child [PRIMARY FOCUS])\n'
257
            '       context: Focus\n'
258
            '       PRIMARY FOCUS\n'),
259 260 261
      );

      // Add the child focus scope to the focus tree.
262
      final FocusAttachment childAttachment = childFocusScope.attach(key.currentContext!);
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
      parentFocusScope.setFirstFocus(childFocusScope);
      await tester.pumpAndSettle();
      expect(childFocusScope.isFirstFocus, isTrue);

      // Now add the child focus scope with no child focusable in it to the tree.
      await tester.pumpWidget(
        FocusScope(
          debugLabel: 'Parent Scope',
          node: parentFocusScope,
          child: Column(
            children: <Widget>[
              TestFocus(
                key: key,
                debugLabel: 'Child',
              ),
              FocusScope(
                debugLabel: 'Child Scope',
                node: childFocusScope,
                child: Container(),
              ),
            ],
          ),
        ),
      );

288
      expect(key.currentState!.focusNode.hasFocus, isFalse);
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
      expect(find.text('a'), findsOneWidget);

      // Now move the existing focus node into the child focus scope.
      await tester.pumpWidget(
        FocusScope(
          debugLabel: 'Parent Scope',
          node: parentFocusScope,
          child: Column(
            children: <Widget>[
              FocusScope(
                debugLabel: 'Child Scope',
                node: childFocusScope,
                child: TestFocus(
                  key: key,
                  debugLabel: 'Child',
                ),
              ),
            ],
          ),
        ),
      );

      await tester.pumpAndSettle();

313
      expect(key.currentState!.focusNode.hasFocus, isFalse);
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
      expect(find.text('a'), findsOneWidget);

      // Now remove the child focus scope.
      await tester.pumpWidget(
        FocusScope(
          debugLabel: 'Parent Scope',
          node: parentFocusScope,
          child: Column(
            children: <Widget>[
              TestFocus(
                key: key,
                debugLabel: 'Child',
              ),
            ],
          ),
        ),
      );
331

332
      await tester.pumpAndSettle();
333
      expect(key.currentState!.focusNode.hasFocus, isFalse);
334
      expect(find.text('a'), findsOneWidget);
335

336 337 338 339 340
      // Must detach the child because we had to attach it in order to call
      // setFirstFocus before adding to the widget.
      childAttachment.detach();
    });

341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
    testWidgets('Setting first focus requests focus for the scope properly.', (WidgetTester tester) async {
      final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node');
      final FocusScopeNode childFocusScope1 = FocusScopeNode(debugLabel: 'Child Scope Node 1');
      final FocusScopeNode childFocusScope2 = FocusScopeNode(debugLabel: 'Child Scope Node 2');
      final GlobalKey<TestFocusState> keyA = GlobalKey(debugLabel: 'Key A');
      final GlobalKey<TestFocusState> keyB = GlobalKey(debugLabel: 'Key B');
      final GlobalKey<TestFocusState> keyC = GlobalKey(debugLabel: 'Key C');

      await tester.pumpWidget(
        FocusScope(
          debugLabel: 'Parent Scope',
          node: parentFocusScope,
          child: Column(
            children: <Widget>[
              FocusScope(
                debugLabel: 'Child Scope 1',
                node: childFocusScope1,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      key: keyA,
                      name: 'a',
                      autofocus: true,
                      debugLabel: 'Child A',
                    ),
                    TestFocus(
                      key: keyB,
                      name: 'b',
                      debugLabel: 'Child B',
                    ),
                  ],
                ),
              ),
              FocusScope(
                debugLabel: 'Child Scope 2',
                node: childFocusScope2,
                child: TestFocus(
                  key: keyC,
                  name: 'c',
                  debugLabel: 'Child C',
                ),
              ),
            ],
          ),
        ),
      );

      await tester.pumpAndSettle();

390
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
391 392 393 394 395
      expect(find.text('A FOCUSED'), findsOneWidget);

      parentFocusScope.setFirstFocus(childFocusScope2);
      await tester.pumpAndSettle();

396
      expect(keyA.currentState!.focusNode.hasFocus, isFalse);
397 398 399 400 401
      expect(find.text('a'), findsOneWidget);

      parentFocusScope.setFirstFocus(childFocusScope1);
      await tester.pumpAndSettle();

402
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
403 404
      expect(find.text('A FOCUSED'), findsOneWidget);

405
      keyB.currentState!.focusNode.requestFocus();
406 407
      await tester.pumpAndSettle();

408
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
409 410 411 412 413 414 415
      expect(find.text('B FOCUSED'), findsOneWidget);
      expect(parentFocusScope.isFirstFocus, isTrue);
      expect(childFocusScope1.isFirstFocus, isTrue);

      parentFocusScope.setFirstFocus(childFocusScope2);
      await tester.pumpAndSettle();

416
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
417 418 419 420 421
      expect(find.text('b'), findsOneWidget);
      expect(parentFocusScope.isFirstFocus, isTrue);
      expect(childFocusScope1.isFirstFocus, isFalse);
      expect(childFocusScope2.isFirstFocus, isTrue);

422
      keyC.currentState!.focusNode.requestFocus();
423 424
      await tester.pumpAndSettle();

425
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
426
      expect(find.text('b'), findsOneWidget);
427
      expect(keyC.currentState!.focusNode.hasFocus, isTrue);
428 429 430 431 432 433 434
      expect(find.text('C FOCUSED'), findsOneWidget);
      expect(parentFocusScope.isFirstFocus, isTrue);
      expect(childFocusScope1.isFirstFocus, isFalse);
      expect(childFocusScope2.isFirstFocus, isTrue);

      childFocusScope1.requestFocus();
      await tester.pumpAndSettle();
435
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
436
      expect(find.text('B FOCUSED'), findsOneWidget);
437
      expect(keyC.currentState!.focusNode.hasFocus, isFalse);
438 439 440 441 442 443 444
      expect(find.text('c'), findsOneWidget);
      expect(parentFocusScope.isFirstFocus, isTrue);
      expect(childFocusScope1.isFirstFocus, isTrue);
      expect(childFocusScope2.isFirstFocus, isFalse);
    });

    testWidgets('Removing focused widget moves focus to next widget', (WidgetTester tester) async {
445 446 447 448 449
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();

      await tester.pumpWidget(
        Column(
450
          children: <Widget>[
451 452 453 454 455 456 457
            TestFocus(
              key: keyA,
              name: 'a',
            ),
            TestFocus(
              key: keyB,
              name: 'b',
458 459 460
            ),
          ],
        ),
461
      );
462

463
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
464

465
      await tester.pumpAndSettle();
466

467
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
468
      expect(find.text('A FOCUSED'), findsOneWidget);
469
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
470 471 472 473
      expect(find.text('b'), findsOneWidget);

      await tester.pumpWidget(
        Column(
474
          children: <Widget>[
475 476 477 478
            TestFocus(
              key: keyB,
              name: 'b',
            ),
479 480
          ],
        ),
481
      );
482

483
      await tester.pump();
484

485
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
486 487
      expect(find.text('b'), findsOneWidget);
    });
488

489 490 491 492
    testWidgets('Adding a new FocusScope attaches the child it to its parent.', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node');
      final FocusScopeNode childFocusScope = FocusScopeNode(debugLabel: 'Child Scope Node');
493

494 495 496 497 498
      await tester.pumpWidget(
        FocusScope(
          node: childFocusScope,
          child: TestFocus(
            debugLabel: 'Child',
499
            key: keyA,
500
          ),
501 502
        ),
      );
503

504 505 506 507
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      expect(FocusScope.of(keyA.currentContext!), equals(childFocusScope));
      expect(Focus.of(keyA.currentContext!, scopeOk: true), equals(childFocusScope));
      FocusManager.instance.rootScope.setFirstFocus(FocusScope.of(keyA.currentContext!));
508

509
      await tester.pumpAndSettle();
510

511
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
512 513
      expect(find.text('A FOCUSED'), findsOneWidget);
      expect(childFocusScope.isFirstFocus, isTrue);
514

515 516 517 518 519 520 521 522 523
      await tester.pumpWidget(
        FocusScope(
          node: parentFocusScope,
          child: FocusScope(
            node: childFocusScope,
            child: TestFocus(
              debugLabel: 'Child',
              key: keyA,
            ),
524
          ),
525 526 527 528 529 530
        ),
      );

      await tester.pump();
      expect(childFocusScope.isFirstFocus, isTrue);
      // Node keeps it's focus when moved to the new scope.
531
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
      expect(find.text('A FOCUSED'), findsOneWidget);
    });

    // Arguably, this isn't correct behavior, but it is what happens now.
    testWidgets("Removing focused widget doesn't move focus to next widget within FocusScope", (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
      final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope');

      await tester.pumpWidget(
        FocusScope(
          debugLabel: 'Parent Scope',
          node: parentFocusScope,
          autofocus: true,
          child: Column(
            children: <Widget>[
              TestFocus(
                debugLabel: 'Widget A',
                key: keyA,
                name: 'a',
              ),
              TestFocus(
                debugLabel: 'Widget B',
                key: keyB,
                name: 'b',
              ),
            ],
          ),
        ),
      );

563 564
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      final FocusScopeNode scope = FocusScope.of(keyA.currentContext!);
565
      FocusManager.instance.rootScope.setFirstFocus(scope);
566 567 568

      await tester.pumpAndSettle();

569
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
570
      expect(find.text('A FOCUSED'), findsOneWidget);
571
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
      expect(find.text('b'), findsOneWidget);

      await tester.pumpWidget(
        FocusScope(
          node: parentFocusScope,
          child: Column(
            children: <Widget>[
              TestFocus(
                key: keyB,
                name: 'b',
              ),
            ],
          ),
        ),
      );
587

588
      await tester.pump();
589

590
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
591 592
      expect(find.text('b'), findsOneWidget);
    });
593

594 595 596 597 598 599
    testWidgets('Removing a FocusScope removes its node from the tree', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
      final GlobalKey<TestFocusState> scopeKeyA = GlobalKey();
      final GlobalKey<TestFocusState> scopeKeyB = GlobalKey();
      final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope');
600

601 602 603
      // This checks both FocusScopes that have their own nodes, as well as those
      // that use external nodes.
      await tester.pumpWidget(
604
        FocusTraversalGroup(
605 606 607 608 609 610 611 612 613 614 615 616 617 618
          child: Column(
            children: <Widget>[
              FocusScope(
                key: scopeKeyA,
                node: parentFocusScope,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      debugLabel: 'Child A',
                      key: keyA,
                      name: 'a',
                    ),
                  ],
                ),
619
              ),
620 621 622 623 624 625 626 627 628 629 630
              FocusScope(
                key: scopeKeyB,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      debugLabel: 'Child B',
                      key: keyB,
                      name: 'b',
                    ),
                  ],
                ),
631
              ),
632 633
            ],
          ),
634
        ),
635
      );
636

637 638 639 640
      FocusScope.of(keyB.currentContext!).requestFocus(keyB.currentState!.focusNode);
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      final FocusScopeNode aScope = FocusScope.of(keyA.currentContext!);
      final FocusScopeNode bScope = FocusScope.of(keyB.currentContext!);
641 642
      FocusManager.instance.rootScope.setFirstFocus(bScope);
      FocusManager.instance.rootScope.setFirstFocus(aScope);
643

644
      await tester.pumpAndSettle();
645

646 647
      expect(FocusScope.of(keyA.currentContext!).isFirstFocus, isTrue);
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
648
      expect(find.text('A FOCUSED'), findsOneWidget);
649
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
650
      expect(find.text('b'), findsOneWidget);
651

652
      await tester.pumpWidget(Container());
653

654
      expect(FocusManager.instance.rootScope.children, isEmpty);
655
    });
656

657 658 659 660 661 662 663 664 665 666
    // By "pinned", it means kept in the tree by a GlobalKey.
    testWidgets("Removing pinned focused scope doesn't move focus to focused widget within next FocusScope", (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
      final GlobalKey<TestFocusState> scopeKeyA = GlobalKey();
      final GlobalKey<TestFocusState> scopeKeyB = GlobalKey();
      final FocusScopeNode parentFocusScope1 = FocusScopeNode(debugLabel: 'Parent Scope 1');
      final FocusScopeNode parentFocusScope2 = FocusScopeNode(debugLabel: 'Parent Scope 2');

      await tester.pumpWidget(
667
        FocusTraversalGroup(
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700
          child: Column(
            children: <Widget>[
              FocusScope(
                key: scopeKeyA,
                node: parentFocusScope1,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      debugLabel: 'Child A',
                      key: keyA,
                      name: 'a',
                    ),
                  ],
                ),
              ),
              FocusScope(
                key: scopeKeyB,
                node: parentFocusScope2,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      debugLabel: 'Child B',
                      key: keyB,
                      name: 'b',
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );

701 702 703 704
      FocusScope.of(keyB.currentContext!).requestFocus(keyB.currentState!.focusNode);
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      final FocusScopeNode bScope = FocusScope.of(keyB.currentContext!);
      final FocusScopeNode aScope = FocusScope.of(keyA.currentContext!);
705 706
      FocusManager.instance.rootScope.setFirstFocus(bScope);
      FocusManager.instance.rootScope.setFirstFocus(aScope);
707 708 709

      await tester.pumpAndSettle();

710 711
      expect(FocusScope.of(keyA.currentContext!).isFirstFocus, isTrue);
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
712
      expect(find.text('A FOCUSED'), findsOneWidget);
713
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
714 715 716
      expect(find.text('b'), findsOneWidget);

      await tester.pumpWidget(
717
        FocusTraversalGroup(
718 719 720 721 722 723 724 725
          child: Column(
            children: <Widget>[
              FocusScope(
                key: scopeKeyB,
                node: parentFocusScope2,
                child: Column(
                  children: <Widget>[
                    TestFocus(
726
                      debugLabel: 'Child B',
727 728 729 730 731 732 733 734 735 736 737 738 739 740
                      key: keyB,
                      name: 'b',
                      autofocus: true,
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      );

      await tester.pump();

741
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
742
      expect(find.text('B FOCUSED'), findsOneWidget);
743 744
    });

745 746 747 748 749
    testWidgets("Removing unpinned focused scope doesn't move focus to focused widget within next FocusScope", (WidgetTester tester) async {
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
      final FocusScopeNode parentFocusScope1 = FocusScopeNode(debugLabel: 'Parent Scope 1');
      final FocusScopeNode parentFocusScope2 = FocusScopeNode(debugLabel: 'Parent Scope 2');
750

751
      await tester.pumpWidget(
752
        FocusTraversalGroup(
753 754 755 756 757 758 759 760 761 762 763 764 765
          child: Column(
            children: <Widget>[
              FocusScope(
                node: parentFocusScope1,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      debugLabel: 'Child A',
                      key: keyA,
                      name: 'a',
                    ),
                  ],
                ),
766
              ),
767 768 769 770 771 772 773 774 775 776 777
              FocusScope(
                node: parentFocusScope2,
                child: Column(
                  children: <Widget>[
                    TestFocus(
                      debugLabel: 'Child B',
                      key: keyB,
                      name: 'b',
                    ),
                  ],
                ),
778
              ),
779 780
            ],
          ),
781
        ),
782 783
      );

784 785 786 787
      FocusScope.of(keyB.currentContext!).requestFocus(keyB.currentState!.focusNode);
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      final FocusScopeNode bScope = FocusScope.of(keyB.currentContext!);
      final FocusScopeNode aScope = FocusScope.of(keyA.currentContext!);
788 789
      FocusManager.instance.rootScope.setFirstFocus(bScope);
      FocusManager.instance.rootScope.setFirstFocus(aScope);
790 791 792

      await tester.pumpAndSettle();

793 794
      expect(FocusScope.of(keyA.currentContext!).isFirstFocus, isTrue);
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
795
      expect(find.text('A FOCUSED'), findsOneWidget);
796
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
797 798 799
      expect(find.text('b'), findsOneWidget);

      await tester.pumpWidget(
800
        FocusTraversalGroup(
801 802 803 804 805 806 807
          child: Column(
            children: <Widget>[
              FocusScope(
                node: parentFocusScope2,
                child: Column(
                  children: <Widget>[
                    TestFocus(
808
                      debugLabel: 'Child B',
809 810 811 812 813 814
                      key: keyB,
                      name: 'b',
                      autofocus: true,
                    ),
                  ],
                ),
815
              ),
816 817
            ],
          ),
818
        ),
819 820
      );
      await tester.pump();
821

822
      expect(keyB.currentState!.focusNode.hasFocus, isTrue);
823
      expect(find.text('B FOCUSED'), findsOneWidget);
824
    });
825

826 827 828 829 830
    testWidgets('Moving widget from one scope to another retains focus', (WidgetTester tester) async {
      final FocusScopeNode parentFocusScope1 = FocusScopeNode();
      final FocusScopeNode parentFocusScope2 = FocusScopeNode();
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
831

832 833 834 835 836 837 838 839 840 841 842 843 844
      await tester.pumpWidget(
        Column(
          children: <Widget>[
            FocusScope(
              node: parentFocusScope1,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    key: keyA,
                    name: 'a',
                  ),
                ],
              ),
845
            ),
846 847 848 849 850 851 852 853 854 855
            FocusScope(
              node: parentFocusScope2,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    key: keyB,
                    name: 'b',
                  ),
                ],
              ),
856
            ),
857 858 859
          ],
        ),
      );
860

861 862
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      final FocusScopeNode aScope = FocusScope.of(keyA.currentContext!);
863
      FocusManager.instance.rootScope.setFirstFocus(aScope);
864

865
      await tester.pumpAndSettle();
866

867
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
868
      expect(find.text('A FOCUSED'), findsOneWidget);
869
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
870
      expect(find.text('b'), findsOneWidget);
871

872 873 874 875 876 877 878 879 880 881 882 883 884
      await tester.pumpWidget(
        Column(
          children: <Widget>[
            FocusScope(
              node: parentFocusScope1,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    key: keyB,
                    name: 'b',
                  ),
                ],
              ),
885
            ),
886 887 888 889 890 891 892 893 894 895 896 897 898 899
            FocusScope(
              node: parentFocusScope2,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    key: keyA,
                    name: 'a',
                  ),
                ],
              ),
            ),
          ],
        ),
      );
900

901
      await tester.pump();
902

903
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
904
      expect(find.text('A FOCUSED'), findsOneWidget);
905
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
906 907
      expect(find.text('b'), findsOneWidget);
    });
908

909 910 911 912 913
    testWidgets('Moving FocusScopeNodes retains focus', (WidgetTester tester) async {
      final FocusScopeNode parentFocusScope1 = FocusScopeNode(debugLabel: 'Scope 1');
      final FocusScopeNode parentFocusScope2 = FocusScopeNode(debugLabel: 'Scope 2');
      final GlobalKey<TestFocusState> keyA = GlobalKey();
      final GlobalKey<TestFocusState> keyB = GlobalKey();
914

915 916 917 918 919 920 921 922 923 924 925 926 927 928
      await tester.pumpWidget(
        Column(
          children: <Widget>[
            FocusScope(
              node: parentFocusScope1,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    debugLabel: 'Child A',
                    key: keyA,
                    name: 'a',
                  ),
                ],
              ),
929
            ),
930 931 932 933 934 935 936 937 938 939 940
            FocusScope(
              node: parentFocusScope2,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    debugLabel: 'Child B',
                    key: keyB,
                    name: 'b',
                  ),
                ],
              ),
941
            ),
942 943 944
          ],
        ),
      );
945

946 947
      FocusScope.of(keyA.currentContext!).requestFocus(keyA.currentState!.focusNode);
      final FocusScopeNode aScope = FocusScope.of(keyA.currentContext!);
948
      FocusManager.instance.rootScope.setFirstFocus(aScope);
949

950
      await tester.pumpAndSettle();
951

952
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
953
      expect(find.text('A FOCUSED'), findsOneWidget);
954
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
955
      expect(find.text('b'), findsOneWidget);
956

957 958 959 960 961 962 963 964 965 966 967 968 969 970 971
      // This just swaps the FocusScopeNodes that the FocusScopes have in them.
      await tester.pumpWidget(
        Column(
          children: <Widget>[
            FocusScope(
              node: parentFocusScope2,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    debugLabel: 'Child A',
                    key: keyA,
                    name: 'a',
                  ),
                ],
              ),
972
            ),
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987
            FocusScope(
              node: parentFocusScope1,
              child: Column(
                children: <Widget>[
                  TestFocus(
                    debugLabel: 'Child B',
                    key: keyB,
                    name: 'b',
                  ),
                ],
              ),
            ),
          ],
        ),
      );
988

989
      await tester.pump();
990

991
      expect(keyA.currentState!.focusNode.hasFocus, isTrue);
992
      expect(find.text('A FOCUSED'), findsOneWidget);
993
      expect(keyB.currentState!.focusNode.hasFocus, isFalse);
994 995
      expect(find.text('b'), findsOneWidget);
    });
996

997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012
    testWidgets('Can focus root node.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      await tester.pumpWidget(
        Focus(
          key: key1,
          child: Container(),
        ),
      );

      final Element firstElement = tester.element(find.byKey(key1));
      final FocusScopeNode rootNode = FocusScope.of(firstElement);
      rootNode.requestFocus();

      await tester.pump();

      expect(rootNode.hasFocus, isTrue);
1013
      expect(rootNode, equals(firstElement.owner!.focusManager.rootScope));
1014
    });
1015

1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
    testWidgets('Can autofocus a node.', (WidgetTester tester) async {
      final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
      await tester.pumpWidget(
        Focus(
          focusNode: focusNode,
          child: Container(),
        ),
      );

      await tester.pump();
      expect(focusNode.hasPrimaryFocus, isFalse);

      await tester.pumpWidget(
        Focus(
          autofocus: true,
          focusNode: focusNode,
          child: Container(),
        ),
      );

      await tester.pump();
      expect(focusNode.hasPrimaryFocus, isTrue);
    });
1039

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
    testWidgets("Won't autofocus a node if one is already focused.", (WidgetTester tester) async {
      final FocusNode focusNodeA = FocusNode(debugLabel: 'Test Node A');
      final FocusNode focusNodeB = FocusNode(debugLabel: 'Test Node B');
      await tester.pumpWidget(
        Column(
          children: <Widget>[
            Focus(
              focusNode: focusNodeA,
              autofocus: true,
              child: Container(),
            ),
          ],
        ),
      );

      await tester.pump();
      expect(focusNodeA.hasPrimaryFocus, isTrue);

      await tester.pumpWidget(
        Column(
          children: <Widget>[
            Focus(
              focusNode: focusNodeA,
              child: Container(),
            ),
            Focus(
              focusNode: focusNodeB,
              autofocus: true,
              child: Container(),
            ),
          ],
        ),
      );

      await tester.pump();
      expect(focusNodeB.hasPrimaryFocus, isFalse);
      expect(focusNodeA.hasPrimaryFocus, isTrue);
    });
1078
  });
1079

1080
  group('Focus', () {
1081
    testWidgets('Focus.of stops at the nearest Focus widget.', (WidgetTester tester) async {
1082 1083 1084 1085 1086 1087
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      final GlobalKey key2 = GlobalKey(debugLabel: '2');
      final GlobalKey key3 = GlobalKey(debugLabel: '3');
      final GlobalKey key4 = GlobalKey(debugLabel: '4');
      final GlobalKey key5 = GlobalKey(debugLabel: '5');
      final GlobalKey key6 = GlobalKey(debugLabel: '6');
1088
      final FocusScopeNode scopeNode = FocusScopeNode();
1089
      await tester.pumpWidget(
1090
        FocusScope(
1091
          key: key1,
1092
          node: scopeNode,
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
          debugLabel: 'Key 1',
          child: Container(
            key: key2,
            child: Focus(
              debugLabel: 'Key 3',
              key: key3,
              child: Container(
                key: key4,
                child: Focus(
                  debugLabel: 'Key 5',
                  key: key5,
                  child: Container(
                    key: key6,
                  ),
1107
                ),
1108
              ),
1109 1110
            ),
          ),
1111 1112 1113 1114 1115 1116 1117 1118
        ),
      );
      final Element element1 = tester.element(find.byKey(key1));
      final Element element2 = tester.element(find.byKey(key2));
      final Element element3 = tester.element(find.byKey(key3));
      final Element element4 = tester.element(find.byKey(key4));
      final Element element5 = tester.element(find.byKey(key5));
      final Element element6 = tester.element(find.byKey(key6));
1119
      final FocusNode root = element1.owner!.focusManager.rootScope;
1120

1121 1122 1123 1124 1125 1126
      expect(Focus.maybeOf(element1), isNull);
      expect(Focus.maybeOf(element2), isNull);
      expect(Focus.maybeOf(element3), isNull);
      expect(Focus.of(element4).parent!.parent, equals(root));
      expect(Focus.of(element5).parent!.parent, equals(root));
      expect(Focus.of(element6).parent!.parent!.parent, equals(root));
1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
    });
    testWidgets('Can traverse Focus children.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      final GlobalKey key2 = GlobalKey(debugLabel: '2');
      final GlobalKey key3 = GlobalKey(debugLabel: '3');
      final GlobalKey key4 = GlobalKey(debugLabel: '4');
      final GlobalKey key5 = GlobalKey(debugLabel: '5');
      final GlobalKey key6 = GlobalKey(debugLabel: '6');
      final GlobalKey key7 = GlobalKey(debugLabel: '7');
      final GlobalKey key8 = GlobalKey(debugLabel: '8');
      await tester.pumpWidget(
        Focus(
          child: Column(
            key: key1,
            children: <Widget>[
              Focus(
                key: key2,
                child: Container(
                  child: Focus(
                    key: key3,
                    child: Container(),
                  ),
1149
                ),
1150 1151 1152 1153 1154 1155 1156 1157
              ),
              Focus(
                key: key4,
                child: Container(
                  child: Focus(
                    key: key5,
                    child: Container(),
                  ),
1158
                ),
1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
              ),
              Focus(
                key: key6,
                child: Column(
                  children: <Widget>[
                    Focus(
                      key: key7,
                      child: Container(),
                    ),
                    Focus(
                      key: key8,
                      child: Container(),
                    ),
                  ],
1173
                ),
1174 1175
              ),
            ],
1176
          ),
1177 1178 1179 1180 1181 1182 1183 1184
        ),
      );

      final Element firstScope = tester.element(find.byKey(key1));
      final List<FocusNode> nodes = <FocusNode>[];
      final List<Key> keys = <Key>[];
      bool visitor(FocusNode node) {
        nodes.add(node);
1185
        keys.add(node.context!.widget.key!);
1186 1187 1188 1189 1190
        return true;
      }

      await tester.pump();

1191
      Focus.of(firstScope).descendants.forEach(visitor);
1192 1193 1194 1195 1196 1197 1198 1199 1200
      expect(nodes.length, equals(7));
      expect(keys.length, equals(7));
      // Depth first.
      expect(keys, equals(<Key>[key3, key2, key5, key4, key7, key8, key6]));

      // Just traverses a sub-tree.
      final Element secondScope = tester.element(find.byKey(key7));
      nodes.clear();
      keys.clear();
1201
      Focus.of(secondScope).descendants.forEach(visitor);
1202 1203 1204
      expect(nodes.length, equals(2));
      expect(keys, equals(<Key>[key7, key8]));
    });
1205

1206 1207
    testWidgets('Can set focus.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
1208
      late bool gotFocus;
1209 1210 1211 1212 1213 1214 1215 1216
      await tester.pumpWidget(
        Focus(
          onFocusChange: (bool focused) => gotFocus = focused,
          child: Container(key: key1),
        ),
      );

      final Element firstNode = tester.element(find.byKey(key1));
1217
      final FocusNode node = Focus.of(firstNode);
1218 1219 1220 1221 1222 1223 1224
      node.requestFocus();

      await tester.pump();

      expect(gotFocus, isTrue);
      expect(node.hasFocus, isTrue);
    });
1225

1226 1227
    testWidgets('Focus is ignored when set to not focusable.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
1228
      bool? gotFocus;
1229 1230 1231 1232 1233 1234 1235 1236 1237
      await tester.pumpWidget(
        Focus(
          canRequestFocus: false,
          onFocusChange: (bool focused) => gotFocus = focused,
          child: Container(key: key1),
        ),
      );

      final Element firstNode = tester.element(find.byKey(key1));
1238
      final FocusNode node = Focus.of(firstNode);
1239 1240 1241 1242 1243 1244 1245
      node.requestFocus();

      await tester.pump();

      expect(gotFocus, isNull);
      expect(node.hasFocus, isFalse);
    });
1246

1247 1248
    testWidgets('Focus is lost when set to not focusable.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
1249
      bool? gotFocus;
1250 1251 1252 1253 1254 1255 1256 1257 1258 1259
      await tester.pumpWidget(
        Focus(
          autofocus: true,
          canRequestFocus: true,
          onFocusChange: (bool focused) => gotFocus = focused,
          child: Container(key: key1),
        ),
      );

      Element firstNode = tester.element(find.byKey(key1));
1260
      FocusNode node = Focus.of(firstNode);
1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277
      node.requestFocus();

      await tester.pump();

      expect(gotFocus, isTrue);
      expect(node.hasFocus, isTrue);

      gotFocus = null;
      await tester.pumpWidget(
        Focus(
          canRequestFocus: false,
          onFocusChange: (bool focused) => gotFocus = focused,
          child: Container(key: key1),
        ),
      );

      firstNode = tester.element(find.byKey(key1));
1278
      node = Focus.of(firstNode);
1279 1280 1281 1282 1283 1284 1285
      node.requestFocus();

      await tester.pump();

      expect(gotFocus, false);
      expect(node.hasFocus, isFalse);
    });
1286

1287 1288 1289 1290
    testWidgets('Child of unfocusable Focus can get focus.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      final GlobalKey key2 = GlobalKey(debugLabel: '2');
      final FocusNode focusNode = FocusNode();
1291
      bool? gotFocus;
1292 1293 1294 1295 1296 1297 1298 1299 1300
      await tester.pumpWidget(
        Focus(
          canRequestFocus: false,
          onFocusChange: (bool focused) => gotFocus = focused,
          child: Focus(key: key1, focusNode: focusNode, child: Container(key: key2)),
        ),
      );

      final Element childWidget = tester.element(find.byKey(key1));
1301
      final FocusNode unfocusableNode = Focus.of(childWidget);
1302 1303 1304 1305 1306 1307 1308 1309
      unfocusableNode.requestFocus();

      await tester.pump();

      expect(gotFocus, isNull);
      expect(unfocusableNode.hasFocus, isFalse);

      final Element containerWidget = tester.element(find.byKey(key2));
1310
      final FocusNode focusableNode = Focus.of(containerWidget);
1311 1312 1313 1314 1315 1316 1317
      focusableNode.requestFocus();

      await tester.pump();

      expect(gotFocus, isTrue);
      expect(unfocusableNode.hasFocus, isTrue);
    });
1318

1319 1320
    testWidgets('Nodes are removed when all Focuses are removed.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
1321
      late bool gotFocus;
1322 1323 1324 1325 1326 1327 1328 1329
      await tester.pumpWidget(
        FocusScope(
          child: Focus(
            onFocusChange: (bool focused) => gotFocus = focused,
            child: Container(key: key1),
          ),
        ),
      );
1330

1331
      final Element firstNode = tester.element(find.byKey(key1));
1332
      final FocusNode node = Focus.of(firstNode);
1333
      node.requestFocus();
1334

1335
      await tester.pump();
1336

1337 1338
      expect(gotFocus, isTrue);
      expect(node.hasFocus, isTrue);
1339

1340
      await tester.pumpWidget(Container());
1341

1342 1343
      expect(FocusManager.instance.rootScope.descendants, isEmpty);
    });
1344

1345 1346
    testWidgets('Focus widgets set Semantics information about focus', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> key = GlobalKey();
1347

1348 1349 1350
      await tester.pumpWidget(
        TestFocus(key: key, name: 'a'),
      );
1351

1352
      final SemanticsNode semantics = tester.getSemantics(find.byKey(key));
1353

1354
      expect(key.currentState!.focusNode.hasFocus, isFalse);
1355 1356
      expect(semantics.hasFlag(SemanticsFlag.isFocused), isFalse);
      expect(semantics.hasFlag(SemanticsFlag.isFocusable), isTrue);
1357

1358
      FocusScope.of(key.currentContext!).requestFocus(key.currentState!.focusNode);
1359
      await tester.pumpAndSettle();
1360

1361
      expect(key.currentState!.focusNode.hasFocus, isTrue);
1362 1363
      expect(semantics.hasFlag(SemanticsFlag.isFocused), isTrue);
      expect(semantics.hasFlag(SemanticsFlag.isFocusable), isTrue);
1364

1365
      key.currentState!.focusNode.canRequestFocus = false;
1366
      await tester.pumpAndSettle();
1367

1368 1369
      expect(key.currentState!.focusNode.hasFocus, isFalse);
      expect(key.currentState!.focusNode.canRequestFocus, isFalse);
1370 1371 1372
      expect(semantics.hasFlag(SemanticsFlag.isFocused), isFalse);
      expect(semantics.hasFlag(SemanticsFlag.isFocusable), isFalse);
    });
1373

1374 1375
    testWidgets('Setting canRequestFocus on focus node causes update.', (WidgetTester tester) async {
      final GlobalKey<TestFocusState> key = GlobalKey();
1376

1377
      final TestFocus testFocus = TestFocus(key: key, name: 'a');
1378
      await tester.pumpWidget(
1379 1380 1381 1382
        testFocus,
      );

      await tester.pumpAndSettle();
1383 1384
      key.currentState!.built = false;
      key.currentState!.focusNode.canRequestFocus = false;
1385
      await tester.pumpAndSettle();
1386
      key.currentState!.built = true;
1387

1388
      expect(key.currentState!.focusNode.canRequestFocus, isFalse);
1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409
    });

    testWidgets('canRequestFocus causes descendants of scope to be skipped.', (WidgetTester tester) async {
      final GlobalKey scope1 = GlobalKey(debugLabel: 'scope1');
      final GlobalKey scope2 = GlobalKey(debugLabel: 'scope2');
      final GlobalKey focus1 = GlobalKey(debugLabel: 'focus1');
      final GlobalKey focus2 = GlobalKey(debugLabel: 'focus2');
      final GlobalKey container1 = GlobalKey(debugLabel: 'container');
      Future<void> pumpTest({
        bool allowScope1 = true,
        bool allowScope2 = true,
        bool allowFocus1 = true,
        bool allowFocus2 = true,
      }) async {
        await tester.pumpWidget(
          FocusScope(
            key: scope1,
            canRequestFocus: allowScope1,
            child: FocusScope(
              key: scope2,
              canRequestFocus: allowScope2,
1410
              child: Focus(
1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428
                key: focus1,
                canRequestFocus: allowFocus1,
                child: Focus(
                  key: focus2,
                  canRequestFocus: allowFocus2,
                  child: Container(
                    key: container1,
                  ),
                ),
              ),
            ),
          ),
        );
        await tester.pump();
      }

      // Check childless node (focus2).
      await pumpTest();
1429
      Focus.of(container1.currentContext!).requestFocus();
1430
      await tester.pump();
1431
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
1432
      await pumpTest(allowFocus2: false);
1433 1434
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
      Focus.of(container1.currentContext!).requestFocus();
1435
      await tester.pump();
1436
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
1437
      await pumpTest();
1438
      Focus.of(container1.currentContext!).requestFocus();
1439
      await tester.pump();
1440
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
1441 1442 1443

      // Check FocusNode with child (focus1). Shouldn't affect children.
      await pumpTest(allowFocus1: false);
1444 1445
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue); // focus2 has focus.
      Focus.of(focus2.currentContext!).requestFocus(); // Try to focus focus1
1446
      await tester.pump();
1447 1448
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue); // focus2 still has focus.
      Focus.of(container1.currentContext!).requestFocus(); // Now try to focus focus2
1449
      await tester.pump();
1450
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
1451 1452
      await pumpTest();
      // Try again, now that we've set focus1's canRequestFocus to true again.
1453
      Focus.of(container1.currentContext!).unfocus();
1454
      await tester.pump();
1455 1456
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
      Focus.of(container1.currentContext!).requestFocus();
1457
      await tester.pump();
1458
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
1459 1460 1461

      // Check FocusScopeNode with only FocusNode children (scope2). Should affect children.
      await pumpTest(allowScope2: false);
1462
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
1463
      FocusScope.of(focus1.currentContext!).requestFocus(); // Try to focus scope2
1464
      await tester.pump();
1465 1466
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
      Focus.of(focus2.currentContext!).requestFocus(); // Try to focus focus1
1467
      await tester.pump();
1468 1469
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
      Focus.of(container1.currentContext!).requestFocus(); // Try to focus focus2
1470
      await tester.pump();
1471
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
1472 1473
      await pumpTest();
      // Try again, now that we've set scope2's canRequestFocus to true again.
1474
      Focus.of(container1.currentContext!).requestFocus();
1475
      await tester.pump();
1476
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
1477 1478 1479

      // Check FocusScopeNode with both FocusNode children and FocusScope children (scope1). Should affect children.
      await pumpTest(allowScope1: false);
1480
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
1481
      FocusScope.of(scope2.currentContext!).requestFocus(); // Try to focus scope1
1482
      await tester.pump();
1483
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
1484
      FocusScope.of(focus1.currentContext!).requestFocus(); // Try to focus scope2
1485
      await tester.pump();
1486 1487
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
      Focus.of(focus2.currentContext!).requestFocus(); // Try to focus focus1
1488
      await tester.pump();
1489 1490
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
      Focus.of(container1.currentContext!).requestFocus(); // Try to focus focus2
1491
      await tester.pump();
1492
      expect(Focus.of(container1.currentContext!).hasFocus, isFalse);
1493 1494
      await pumpTest();
      // Try again, now that we've set scope1's canRequestFocus to true again.
1495
      Focus.of(container1.currentContext!).requestFocus();
1496
      await tester.pump();
1497
      expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
    });

    testWidgets('skipTraversal works as expected.', (WidgetTester tester) async {
      final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1');
      final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
      final FocusNode focus1 = FocusNode(debugLabel: 'focus1');
      final FocusNode focus2 = FocusNode(debugLabel: 'focus2');

      Future<void> pumpTest({
        bool traverseScope1 = false,
        bool traverseScope2 = false,
        bool traverseFocus1 = false,
        bool traverseFocus2 = false,
      }) async {
        await tester.pumpWidget(
          FocusScope(
            node: scope1,
            skipTraversal: traverseScope1,
            child: FocusScope(
              node: scope2,
              skipTraversal: traverseScope2,
              child: Focus(
                focusNode: focus1,
                skipTraversal: traverseFocus1,
                child: Focus(
                  focusNode: focus2,
                  skipTraversal: traverseFocus2,
                  child: Container(),
1526 1527 1528 1529
                ),
              ),
            ),
          ),
1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556
        );
        await tester.pump();
      }

      await pumpTest();
      expect(scope1.traversalDescendants, equals(<FocusNode>[focus2, focus1, scope2]));

      // Check childless node (focus2).
      await pumpTest(traverseFocus2: true);
      expect(scope1.traversalDescendants, equals(<FocusNode>[focus1, scope2]));

      // Check FocusNode with child (focus1). Shouldn't affect children.
      await pumpTest(traverseFocus1: true);
      expect(scope1.traversalDescendants, equals(<FocusNode>[focus2, scope2]));

      // Check FocusScopeNode with only FocusNode children (scope2). Should affect children.
      await pumpTest(traverseScope2: true);
      expect(scope1.traversalDescendants, equals(<FocusNode>[focus2, focus1]));

      // Check FocusScopeNode with both FocusNode children and FocusScope children (scope1). Should affect children.
      await pumpTest(traverseScope1: true);
      expect(scope1.traversalDescendants, equals(<FocusNode>[focus2, focus1, scope2]));
    });
    testWidgets('descendantsAreFocusable works as expected.', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      final GlobalKey key2 = GlobalKey(debugLabel: '2');
      final FocusNode focusNode = FocusNode();
1557
      bool? gotFocus;
1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568
      await tester.pumpWidget(
        Focus(
          descendantsAreFocusable: false,
          child: Focus(
            onFocusChange: (bool focused) => gotFocus = focused,
            child: Focus(
              key: key1,
              focusNode: focusNode,
              child: Container(key: key2),
            ),
          ),
1569 1570
        ),
      );
1571 1572

      final Element childWidget = tester.element(find.byKey(key1));
1573
      final FocusNode unfocusableNode = Focus.of(childWidget);
1574
      final Element containerWidget = tester.element(find.byKey(key2));
1575
      final FocusNode containerNode = Focus.of(containerWidget);
1576 1577 1578 1579 1580 1581 1582 1583 1584

      unfocusableNode.requestFocus();
      await tester.pump();

      expect(gotFocus, isNull);
      expect(containerNode.hasFocus, isFalse);
      expect(unfocusableNode.hasFocus, isFalse);

      containerNode.requestFocus();
1585 1586
      await tester.pump();

1587 1588 1589 1590
      expect(gotFocus, isNull);
      expect(containerNode.hasFocus, isFalse);
      expect(unfocusableNode.hasFocus, isFalse);
    });
1591 1592 1593 1594 1595 1596
    testWidgets("Focus doesn't introduce a Semantics node when includeSemantics is false", (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      await tester.pumpWidget(Focus(includeSemantics: false, child: Container()));
      final TestSemantics expectedSemantics = TestSemantics.root();
      expect(semantics, hasSemantics(expectedSemantics));
    });
1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627
    testWidgets('Focus updates the onKey handler when the widget updates', (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      final FocusNode focusNode = FocusNode();
      bool? keyEventHandled;
      await tester.pumpWidget(
        Focus(
          onKey: (_, __) => true, // This one does nothing.
          focusNode: focusNode,
          child: Container(key: key1),
        ),
      );

      Focus.of(key1.currentContext!).requestFocus();
      await tester.pump();
      await tester.sendKeyEvent(LogicalKeyboardKey.enter);
      expect(keyEventHandled, isNull);

      await tester.pumpWidget(
        Focus(
          onKey: (FocusNode node, RawKeyEvent event) { // The updated handler handles the key.
            keyEventHandled = true;
            return true;
          },
          focusNode: focusNode,
          child: Container(key: key1),
        ),
      );

      await tester.sendKeyEvent(LogicalKeyboardKey.enter);
      expect(keyEventHandled, isTrue);
    });
1628
  });
1629
  group('ExcludeFocus', () {
1630 1631 1632 1633
    testWidgets("Descendants of ExcludeFocus aren't focusable.", (WidgetTester tester) async {
      final GlobalKey key1 = GlobalKey(debugLabel: '1');
      final GlobalKey key2 = GlobalKey(debugLabel: '2');
      final FocusNode focusNode = FocusNode();
1634
      bool? gotFocus;
1635
      await tester.pumpWidget(
1636 1637 1638 1639
        ExcludeFocus(
          excluding: true,
          child: Focus(
            onFocusChange: (bool focused) => gotFocus = focused,
1640
            child: Focus(
1641 1642 1643
              key: key1,
              focusNode: focusNode,
              child: Container(key: key2),
1644 1645 1646 1647 1648
            ),
          ),
        ),
      );

1649
      final Element childWidget = tester.element(find.byKey(key1));
1650
      final FocusNode unfocusableNode = Focus.of(childWidget);
1651
      final Element containerWidget = tester.element(find.byKey(key2));
1652
      final FocusNode containerNode = Focus.of(containerWidget);
1653

1654 1655
      unfocusableNode.requestFocus();
      await tester.pump();
1656

1657 1658 1659
      expect(gotFocus, isNull);
      expect(containerNode.hasFocus, isFalse);
      expect(unfocusableNode.hasFocus, isFalse);
1660

1661 1662
      containerNode.requestFocus();
      await tester.pump();
1663

1664 1665 1666 1667
      expect(gotFocus, isNull);
      expect(containerNode.hasFocus, isFalse);
      expect(unfocusableNode.hasFocus, isFalse);
    });
1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735
    // Regression test for https://github.com/flutter/flutter/issues/61700
    testWidgets("ExcludeFocus doesn't transfer focus to another descendant.", (WidgetTester tester) async {
      final FocusNode parentFocusNode = FocusNode(debugLabel: 'group');
      final FocusNode focusNode1 = FocusNode(debugLabel: 'node 1');
      final FocusNode focusNode2 = FocusNode(debugLabel: 'node 2');
      await tester.pumpWidget(
        ExcludeFocus(
          excluding: false,
          child: Focus(
            focusNode: parentFocusNode,
            child: Column(
              children: <Widget>[
                Focus(
                  autofocus: true,
                  focusNode: focusNode1,
                  child: Container(),
                ),
                Focus(
                  focusNode: focusNode2,
                  child: Container(),
                ),
              ],
            ),
          ),
        ),
      );

      await tester.pump();

      expect(parentFocusNode.hasFocus, isTrue);
      expect(focusNode1.hasPrimaryFocus, isTrue);
      expect(focusNode2.hasFocus, isFalse);

      // Move focus to the second node to create some focus history for the scope.
      focusNode2.requestFocus();
      await tester.pump();

      expect(parentFocusNode.hasFocus, isTrue);
      expect(focusNode1.hasFocus, isFalse);
      expect(focusNode2.hasPrimaryFocus, isTrue);

      // Now turn off the focus for the subtree.
      await tester.pumpWidget(
        ExcludeFocus(
          excluding: true,
          child: Focus(
            focusNode: parentFocusNode,
            child: Column(
              children: <Widget>[
                Focus(
                  autofocus: true,
                  focusNode: focusNode1,
                  child: Container(),
                ),
                Focus(
                  focusNode: focusNode2,
                  child: Container(),
                ),
              ],
            ),
          ),
        ),
      );
      await tester.pump();

      expect(focusNode1.hasFocus, isFalse);
      expect(focusNode2.hasFocus, isFalse);
      expect(parentFocusNode.hasFocus, isFalse);
1736
      expect(parentFocusNode.enclosingScope!.hasPrimaryFocus, isTrue);
1737
    });
1738 1739 1740 1741 1742 1743
    testWidgets("ExcludeFocus doesn't introduce a Semantics node", (WidgetTester tester) async {
      final SemanticsTester semantics = SemanticsTester(tester);
      await tester.pumpWidget(ExcludeFocus(child: Container()));
      final TestSemantics expectedSemantics = TestSemantics.root();
      expect(semantics, hasSemantics(expectedSemantics));
    });
1744
  });
1745
}