// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; class TestFocus extends StatefulWidget { const TestFocus({ Key key, this.debugLabel, this.name = 'a', this.autofocus = false, }) : super(key: key); final String debugLabel; final String name; final bool autofocus; @override TestFocusState createState() => TestFocusState(); } class TestFocusState extends State<TestFocus> { FocusNode focusNode; String _label; @override void dispose() { focusNode.removeListener(_updateLabel); focusNode?.dispose(); super.dispose(); } String get label => focusNode.hasFocus ? '${widget.name.toUpperCase()} FOCUSED' : widget.name.toLowerCase(); @override void initState() { super.initState(); focusNode = FocusNode(debugLabel: widget.debugLabel); _label = label; focusNode.addListener(_updateLabel); } void _updateLabel() { setState(() { _label = label; }); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () { FocusScope.of(context).requestFocus(focusNode); }, child: Focus( autofocus: widget.autofocus, focusNode: focusNode, debugLabel: widget.debugLabel, child: Text( _label, textDirection: TextDirection.ltr, ), ), ); } } void main() { group(FocusScope, () { testWidgets('Can focus', (WidgetTester tester) async { final GlobalKey<TestFocusState> key = GlobalKey(); await tester.pumpWidget( TestFocus(key: key, name: 'a'), ); expect(key.currentState.focusNode.hasFocus, isFalse); FocusScope.of(key.currentContext).requestFocus(key.currentState.focusNode); await tester.pumpAndSettle(); expect(key.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); }); testWidgets('Can unfocus', (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'), ], ), ); expect(keyA.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); FocusScope.of(keyA.currentContext).requestFocus(keyA.currentState.focusNode); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); // Set focus to the "B" node to unfocus the "A" node. FocusScope.of(keyB.currentContext).requestFocus(keyB.currentState.focusNode); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isTrue); expect(find.text('B FOCUSED'), findsOneWidget); }); 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(); expect(keyA.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isTrue); expect(find.text('B FOCUSED'), findsOneWidget); }); 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( children: <Widget>[ TestFocus( key: keyA, name: 'a', autofocus: true, ), TestFocus( key: keyB, name: 'b', ), ], ), ); // Autofocus is delayed one frame. await tester.pump(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.tap(find.text('A FOCUSED')); await tester.pump(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.tap(find.text('b')); await tester.pump(); expect(keyA.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isTrue); expect(find.text('B FOCUSED'), findsOneWidget); await tester.tap(find.text('a')); await tester.pump(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); 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', ), ], ), ), ); expect(key.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); FocusScope.of(key.currentContext).requestFocus(key.currentState.focusNode); await tester.pumpAndSettle(); expect(key.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(parentFocusScope, hasAGoodToStringDeep); expect( parentFocusScope.toStringDeep(), equalsIgnoringHashCodes('FocusScopeNode#00000\n' ' │ context: FocusScope\n' ' │ FOCUSED\n' ' │ debugLabel: "Parent Scope Node"\n' ' │ focusedChild: FocusNode#00000\n' ' │\n' ' └─Child 1: FocusNode#00000\n' ' context: Focus\n' ' FOCUSED\n' ' debugLabel: "Child"\n'), ); expect(WidgetsBinding.instance.focusManager.rootScope, hasAGoodToStringDeep); expect( WidgetsBinding.instance.focusManager.rootScope.toStringDeep(minLevel: DiagnosticLevel.info), equalsIgnoringHashCodes('FocusScopeNode#00000\n' ' │ FOCUSED\n' ' │ debugLabel: "Root Focus Scope"\n' ' │ focusedChild: FocusScopeNode#00000\n' ' │\n' ' └─Child 1: FocusScopeNode#00000\n' ' │ context: FocusScope\n' ' │ FOCUSED\n' ' │ debugLabel: "Parent Scope Node"\n' ' │ focusedChild: FocusNode#00000\n' ' │\n' ' └─Child 1: FocusNode#00000\n' ' context: Focus\n' ' FOCUSED\n' ' debugLabel: "Child"\n'), ); // Add the child focus scope to the focus tree. final FocusAttachment childAttachment = childFocusScope.attach(key.currentContext); 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(), ), ], ), ), ); expect(key.currentState.focusNode.hasFocus, isFalse); 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(); expect(key.currentState.focusNode.hasFocus, isFalse); 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', ), ], ), ), ); await tester.pumpAndSettle(); expect(key.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); // Must detach the child because we had to attach it in order to call // setFirstFocus before adding to the widget. childAttachment.detach(); }); 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(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); parentFocusScope.setFirstFocus(childFocusScope2); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isFalse); expect(find.text('a'), findsOneWidget); parentFocusScope.setFirstFocus(childFocusScope1); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); keyB.currentState.focusNode.requestFocus(); await tester.pumpAndSettle(); expect(keyB.currentState.focusNode.hasFocus, isTrue); expect(find.text('B FOCUSED'), findsOneWidget); expect(parentFocusScope.isFirstFocus, isTrue); expect(childFocusScope1.isFirstFocus, isTrue); parentFocusScope.setFirstFocus(childFocusScope2); await tester.pumpAndSettle(); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); expect(parentFocusScope.isFirstFocus, isTrue); expect(childFocusScope1.isFirstFocus, isFalse); expect(childFocusScope2.isFirstFocus, isTrue); keyC.currentState.focusNode.requestFocus(); await tester.pumpAndSettle(); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); expect(keyC.currentState.focusNode.hasFocus, isTrue); expect(find.text('C FOCUSED'), findsOneWidget); expect(parentFocusScope.isFirstFocus, isTrue); expect(childFocusScope1.isFirstFocus, isFalse); expect(childFocusScope2.isFirstFocus, isTrue); childFocusScope1.requestFocus(); await tester.pumpAndSettle(); expect(keyB.currentState.focusNode.hasFocus, isTrue); expect(find.text('B FOCUSED'), findsOneWidget); expect(keyC.currentState.focusNode.hasFocus, isFalse); 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 { 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', ), ], ), ); FocusScope.of(keyA.currentContext).requestFocus(keyA.currentState.focusNode); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.pumpWidget( Column( children: <Widget>[ TestFocus( key: keyB, name: 'b', ), ], ), ); await tester.pump(); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); }); 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'); await tester.pumpWidget( FocusScope( node: childFocusScope, child: TestFocus( debugLabel: 'Child', key: keyA, ), ), ); FocusScope.of(keyA.currentContext).requestFocus(keyA.currentState.focusNode); expect(FocusScope.of(keyA.currentContext), equals(childFocusScope)); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(FocusScope.of(keyA.currentContext)); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(childFocusScope.isFirstFocus, isTrue); await tester.pumpWidget( FocusScope( node: parentFocusScope, child: FocusScope( node: childFocusScope, child: TestFocus( debugLabel: 'Child', key: keyA, ), ), ), ); await tester.pump(); expect(childFocusScope.isFirstFocus, isTrue); // Node keeps it's focus when moved to the new scope. expect(keyA.currentState.focusNode.hasFocus, isTrue); 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', ), ], ), ), ); FocusScope.of(keyA.currentContext).requestFocus(keyA.currentState.focusNode); final FocusScopeNode scope = FocusScope.of(keyA.currentContext); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(scope); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.pumpWidget( FocusScope( node: parentFocusScope, child: Column( children: <Widget>[ TestFocus( key: keyB, name: 'b', ), ], ), ), ); await tester.pump(); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); }); 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'); // This checks both FocusScopes that have their own nodes, as well as those // that use external nodes. await tester.pumpWidget( DefaultFocusTraversal( child: Column( children: <Widget>[ FocusScope( key: scopeKeyA, node: parentFocusScope, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child A', key: keyA, name: 'a', ), ], ), ), FocusScope( key: scopeKeyB, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child B', key: keyB, name: 'b', ), ], ), ), ], ), ), ); 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); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(bScope); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(aScope); await tester.pumpAndSettle(); expect(FocusScope.of(keyA.currentContext).isFirstFocus, isTrue); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.pumpWidget(Container()); expect(WidgetsBinding.instance.focusManager.rootScope.children, isEmpty); }); // 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( DefaultFocusTraversal( 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', ), ], ), ), ], ), ), ); 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); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(bScope); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(aScope); await tester.pumpAndSettle(); expect(FocusScope.of(keyA.currentContext).isFirstFocus, isTrue); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.pumpWidget( DefaultFocusTraversal( child: Column( children: <Widget>[ FocusScope( key: scopeKeyB, node: parentFocusScope2, child: Column( children: <Widget>[ TestFocus( key: keyB, name: 'b', autofocus: true, ), ], ), ), ], ), ), ); await tester.pump(); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); }); 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'); await tester.pumpWidget( DefaultFocusTraversal( child: Column( children: <Widget>[ FocusScope( node: parentFocusScope1, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child A', key: keyA, name: 'a', ), ], ), ), FocusScope( node: parentFocusScope2, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child B', key: keyB, name: 'b', ), ], ), ), ], ), ), ); 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); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(bScope); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(aScope); await tester.pumpAndSettle(); expect(FocusScope.of(keyA.currentContext).isFirstFocus, isTrue); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.pumpWidget( DefaultFocusTraversal( child: Column( children: <Widget>[ FocusScope( node: parentFocusScope2, child: Column( children: <Widget>[ TestFocus( key: keyB, name: 'b', autofocus: true, ), ], ), ), ], ), ), ); await tester.pump(); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); }); 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(); await tester.pumpWidget( Column( children: <Widget>[ FocusScope( node: parentFocusScope1, child: Column( children: <Widget>[ TestFocus( key: keyA, name: 'a', ), ], ), ), FocusScope( node: parentFocusScope2, child: Column( children: <Widget>[ TestFocus( key: keyB, name: 'b', ), ], ), ), ], ), ); FocusScope.of(keyA.currentContext).requestFocus(keyA.currentState.focusNode); final FocusScopeNode aScope = FocusScope.of(keyA.currentContext); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(aScope); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); await tester.pumpWidget( Column( children: <Widget>[ FocusScope( node: parentFocusScope1, child: Column( children: <Widget>[ TestFocus( key: keyB, name: 'b', ), ], ), ), FocusScope( node: parentFocusScope2, child: Column( children: <Widget>[ TestFocus( key: keyA, name: 'a', ), ], ), ), ], ), ); await tester.pump(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); }); 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(); await tester.pumpWidget( Column( children: <Widget>[ FocusScope( node: parentFocusScope1, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child A', key: keyA, name: 'a', ), ], ), ), FocusScope( node: parentFocusScope2, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child B', key: keyB, name: 'b', ), ], ), ), ], ), ); FocusScope.of(keyA.currentContext).requestFocus(keyA.currentState.focusNode); final FocusScopeNode aScope = FocusScope.of(keyA.currentContext); WidgetsBinding.instance.focusManager.rootScope.setFirstFocus(aScope); await tester.pumpAndSettle(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); // 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', ), ], ), ), FocusScope( node: parentFocusScope1, child: Column( children: <Widget>[ TestFocus( debugLabel: 'Child B', key: keyB, name: 'b', ), ], ), ), ], ), ); await tester.pump(); expect(keyA.currentState.focusNode.hasFocus, isTrue); expect(find.text('A FOCUSED'), findsOneWidget); expect(keyB.currentState.focusNode.hasFocus, isFalse); expect(find.text('b'), findsOneWidget); }); 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); expect(rootNode, equals(firstElement.owner.focusManager.rootScope)); }); }); group(Focus, () { testWidgets('Focus.of stops at the nearest Focus widget.', (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 FocusScopeNode scopeNode = FocusScopeNode(); await tester.pumpWidget( FocusScope( key: key1, node: scopeNode, 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, ), ), ), ), ), ), ); 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)); final FocusNode root = element1.owner.focusManager.rootScope; expect(Focus.of(element1, nullOk: true), isNull); expect(Focus.of(element2, nullOk: true), isNull); expect(Focus.of(element3, nullOk: true), 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)); }); 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(), ), ), ), Focus( key: key4, child: Container( child: Focus( key: key5, child: Container(), ), ), ), Focus( key: key6, child: Column( children: <Widget>[ Focus( key: key7, child: Container(), ), Focus( key: key8, child: Container(), ), ], ), ), ], ), ), ); 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); keys.add(node.context.widget.key); return true; } await tester.pump(); Focus.of(firstScope).descendants.forEach(visitor); 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(); Focus.of(secondScope).descendants.forEach(visitor); expect(nodes.length, equals(2)); expect(keys, equals(<Key>[key7, key8])); }); testWidgets('Can set focus.', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(debugLabel: '1'); bool gotFocus; await tester.pumpWidget( Focus( onFocusChange: (bool focused) => gotFocus = focused, child: Container(key: key1), ), ); final Element firstNode = tester.element(find.byKey(key1)); final FocusNode node = Focus.of(firstNode); node.requestFocus(); await tester.pump(); expect(gotFocus, isTrue); expect(node.hasFocus, isTrue); }); }); testWidgets('Nodes are removed when all Focuses are removed.', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(debugLabel: '1'); bool gotFocus; await tester.pumpWidget( FocusScope( child: Focus( onFocusChange: (bool focused) => gotFocus = focused, child: Container(key: key1), ), ), ); final Element firstNode = tester.element(find.byKey(key1)); final FocusNode node = Focus.of(firstNode); node.requestFocus(); await tester.pump(); expect(gotFocus, isTrue); expect(node.hasFocus, isTrue); await tester.pumpWidget(Container()); expect(WidgetsBinding.instance.focusManager.rootScope.descendants, isEmpty); }); }