// 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 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void sendFakeKeyEvent(Map<String, dynamic> data) { defaultBinaryMessenger.handlePlatformMessage( SystemChannels.keyEvent.name, SystemChannels.keyEvent.codec.encodeMessage(data), (ByteData data) {}, ); } void main() { final GlobalKey widgetKey = GlobalKey(); Future<BuildContext> setupWidget(WidgetTester tester) async { await tester.pumpWidget(Container(key: widgetKey)); return widgetKey.currentContext; } group(FocusNode, () { testWidgets('Can add children.', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusNode parent = FocusNode(); final FocusAttachment parentAttachment = parent.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); parentAttachment.reparent(parent: tester.binding.focusManager.rootScope); child1Attachment.reparent(parent: parent); expect(child1.parent, equals(parent)); expect(parent.children.first, equals(child1)); expect(parent.children.last, equals(child1)); child2Attachment.reparent(parent: parent); expect(child1.parent, equals(parent)); expect(child2.parent, equals(parent)); expect(parent.children.first, equals(child1)); expect(parent.children.last, equals(child2)); }); testWidgets('Can remove children.', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusNode parent = FocusNode(); final FocusAttachment parentAttachment = parent.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); parentAttachment.reparent(parent: tester.binding.focusManager.rootScope); child1Attachment.reparent(parent: parent); child2Attachment.reparent(parent: parent); expect(child1.parent, equals(parent)); expect(child2.parent, equals(parent)); expect(parent.children.first, equals(child1)); expect(parent.children.last, equals(child2)); child1Attachment.detach(); expect(child1.parent, isNull); expect(child2.parent, equals(parent)); expect(parent.children.first, equals(child2)); expect(parent.children.last, equals(child2)); child2Attachment.detach(); expect(child1.parent, isNull); expect(child2.parent, isNull); expect(parent.children, isEmpty); }); testWidgets('implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); FocusNode( debugLabel: 'Label', ).debugFillProperties(builder); final List<String> description = builder.properties.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)).map((DiagnosticsNode n) => n.toString()).toList(); expect(description, <String>[ 'debugLabel: "Label"', ]); }); }); group(FocusScopeNode, () { testWidgets('Can setFirstFocus on a scope with no manager.', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope'); scope.attach(context); final FocusScopeNode parent = FocusScopeNode(debugLabel: 'Parent'); parent.attach(context); final FocusScopeNode child1 = FocusScopeNode(debugLabel: 'Child 1'); final FocusAttachment child1Attachment = child1.attach(context); final FocusScopeNode child2 = FocusScopeNode(debugLabel: 'Child 2'); child2.attach(context); scope.setFirstFocus(parent); parent.setFirstFocus(child1); parent.setFirstFocus(child2); child1.requestFocus(); await tester.pump(); expect(scope.hasFocus, isFalse); expect(child1.hasFocus, isFalse); expect(child1.hasPrimaryFocus, isFalse); expect(scope.focusedChild, equals(parent)); expect(parent.focusedChild, equals(child1)); child1Attachment.detach(); expect(scope.hasFocus, isFalse); expect(scope.focusedChild, equals(parent)); }); testWidgets('Removing a node removes it from scope.', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope = FocusScopeNode(); final FocusAttachment scopeAttachment = scope.attach(context); final FocusNode parent = FocusNode(); final FocusAttachment parentAttachment = parent.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); parentAttachment.reparent(parent: scope); child1Attachment.reparent(parent: parent); child2Attachment.reparent(parent: parent); child1.requestFocus(); await tester.pump(); expect(scope.hasFocus, isTrue); expect(child1.hasFocus, isTrue); expect(child1.hasPrimaryFocus, isTrue); expect(scope.focusedChild, equals(child1)); child1Attachment.detach(); expect(scope.hasFocus, isFalse); expect(scope.focusedChild, isNull); }); testWidgets('Can add children to scope and focus', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope = FocusScopeNode(); final FocusAttachment scopeAttachment = scope.attach(context); final FocusNode parent = FocusNode(); final FocusAttachment parentAttachment = parent.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); parentAttachment.reparent(parent: scope); child1Attachment.reparent(parent: parent); child2Attachment.reparent(parent: parent); expect(scope.children.first, equals(parent)); expect(parent.parent, equals(scope)); expect(child1.parent, equals(parent)); expect(child2.parent, equals(parent)); expect(parent.children.first, equals(child1)); expect(parent.children.last, equals(child2)); child1.requestFocus(); await tester.pump(); expect(scope.focusedChild, equals(child1)); expect(parent.hasFocus, isTrue); expect(parent.hasPrimaryFocus, isFalse); expect(child1.hasFocus, isTrue); expect(child1.hasPrimaryFocus, isTrue); expect(child2.hasFocus, isFalse); expect(child2.hasPrimaryFocus, isFalse); child2.requestFocus(); await tester.pump(); expect(scope.focusedChild, equals(child2)); expect(parent.hasFocus, isTrue); expect(parent.hasPrimaryFocus, isFalse); expect(child1.hasFocus, isFalse); expect(child1.hasPrimaryFocus, isFalse); expect(child2.hasFocus, isTrue); expect(child2.hasPrimaryFocus, isTrue); }); testWidgets('Autofocus works.', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope'); final FocusAttachment scopeAttachment = scope.attach(context); final FocusNode parent = FocusNode(debugLabel: 'Parent'); final FocusAttachment parentAttachment = parent.attach(context); final FocusNode child1 = FocusNode(debugLabel: 'Child 1'); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(debugLabel: 'Child 2'); final FocusAttachment child2Attachment = child2.attach(context); scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); parentAttachment.reparent(parent: scope); child1Attachment.reparent(parent: parent); child2Attachment.reparent(parent: parent); scope.autofocus(child2); await tester.pump(); expect(scope.focusedChild, equals(child2)); expect(parent.hasFocus, isTrue); expect(child1.hasFocus, isFalse); expect(child1.hasPrimaryFocus, isFalse); expect(child2.hasFocus, isTrue); expect(child2.hasPrimaryFocus, isTrue); child1.requestFocus(); scope.autofocus(child2); await tester.pump(); expect(scope.focusedChild, equals(child1)); expect(parent.hasFocus, isTrue); expect(child1.hasFocus, isTrue); expect(child1.hasPrimaryFocus, isTrue); expect(child2.hasFocus, isFalse); expect(child2.hasPrimaryFocus, isFalse); }); testWidgets('Adding a focusedChild to a scope sets scope as focusedChild in parent scope', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope1 = FocusScopeNode(); final FocusAttachment scope1Attachment = scope1.attach(context); final FocusScopeNode scope2 = FocusScopeNode(); final FocusAttachment scope2Attachment = scope2.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope); scope2Attachment.reparent(parent: scope1); child1Attachment.reparent(parent: scope1); child2Attachment.reparent(parent: scope2); child2.requestFocus(); await tester.pump(); expect(scope2.focusedChild, equals(child2)); expect(scope1.focusedChild, equals(scope2)); expect(child1.hasFocus, isFalse); expect(child1.hasPrimaryFocus, isFalse); expect(child2.hasFocus, isTrue); expect(child2.hasPrimaryFocus, isTrue); child1.requestFocus(); await tester.pump(); expect(scope2.focusedChild, equals(child2)); expect(scope1.focusedChild, equals(child1)); expect(child1.hasFocus, isTrue); expect(child1.hasPrimaryFocus, isTrue); expect(child2.hasFocus, isFalse); expect(child2.hasPrimaryFocus, isFalse); }); testWidgets('Can move node with focus without losing focus', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope'); final FocusAttachment scopeAttachment = scope.attach(context); final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1'); final FocusAttachment parent1Attachment = parent1.attach(context); final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2'); final FocusAttachment parent2Attachment = parent2.attach(context); final FocusNode child1 = FocusNode(debugLabel: 'Child 1'); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(debugLabel: 'Child 2'); final FocusAttachment child2Attachment = child2.attach(context); scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); parent1Attachment.reparent(parent: scope); parent2Attachment.reparent(parent: scope); child1Attachment.reparent(parent: parent1); child2Attachment.reparent(parent: parent1); expect(scope.children.first, equals(parent1)); expect(scope.children.last, equals(parent2)); expect(parent1.parent, equals(scope)); expect(parent2.parent, equals(scope)); expect(child1.parent, equals(parent1)); expect(child2.parent, equals(parent1)); expect(parent1.children.first, equals(child1)); expect(parent1.children.last, equals(child2)); child1.requestFocus(); await tester.pump(); child1Attachment.reparent(parent: parent2); await tester.pump(); expect(scope.focusedChild, equals(child1)); expect(child1.parent, equals(parent2)); expect(child2.parent, equals(parent1)); expect(parent1.children.first, equals(child2)); expect(parent2.children.first, equals(child1)); }); testWidgets('Can move node between scopes and lose scope focus', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1')..attach(context); final FocusAttachment scope1Attachment = scope1.attach(context); final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2'); final FocusAttachment scope2Attachment = scope2.attach(context); final FocusNode parent1 = FocusNode(debugLabel: 'parent1'); final FocusAttachment parent1Attachment = parent1.attach(context); final FocusNode parent2 = FocusNode(debugLabel: 'parent2'); final FocusAttachment parent2Attachment = parent2.attach(context); final FocusNode child1 = FocusNode(debugLabel: 'child1'); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(debugLabel: 'child2'); final FocusAttachment child2Attachment = child2.attach(context); final FocusNode child3 = FocusNode(debugLabel: 'child3'); final FocusAttachment child3Attachment = child3.attach(context); final FocusNode child4 = FocusNode(debugLabel: 'child4'); final FocusAttachment child4Attachment = child4.attach(context); scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope); scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope); parent1Attachment.reparent(parent: scope1); parent2Attachment.reparent(parent: scope2); child1Attachment.reparent(parent: parent1); child2Attachment.reparent(parent: parent1); child3Attachment.reparent(parent: parent2); child4Attachment.reparent(parent: parent2); child1.requestFocus(); await tester.pump(); expect(scope1.focusedChild, equals(child1)); expect(parent2.children.contains(child1), isFalse); child1Attachment.reparent(parent: parent2); await tester.pump(); expect(scope1.focusedChild, isNull); expect(parent2.children.contains(child1), isTrue); }); testWidgets('Can move focus between scopes and keep focus', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope1 = FocusScopeNode(); final FocusAttachment scope1Attachment = scope1.attach(context); final FocusScopeNode scope2 = FocusScopeNode(); final FocusAttachment scope2Attachment = scope2.attach(context); final FocusNode parent1 = FocusNode(); final FocusAttachment parent1Attachment = parent1.attach(context); final FocusNode parent2 = FocusNode(); final FocusAttachment parent2Attachment = parent2.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); final FocusNode child3 = FocusNode(); final FocusAttachment child3Attachment = child3.attach(context); final FocusNode child4 = FocusNode(); final FocusAttachment child4Attachment = child4.attach(context); scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope); scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope); parent1Attachment.reparent(parent: scope1); parent2Attachment.reparent(parent: scope2); child1Attachment.reparent(parent: parent1); child2Attachment.reparent(parent: parent1); child3Attachment.reparent(parent: parent2); child4Attachment.reparent(parent: parent2); child4.requestFocus(); await tester.pump(); child1.requestFocus(); await tester.pump(); expect(child4.hasFocus, isFalse); expect(child4.hasPrimaryFocus, isFalse); expect(child1.hasFocus, isTrue); expect(child1.hasPrimaryFocus, isTrue); expect(scope1.hasFocus, isTrue); expect(scope1.hasPrimaryFocus, isFalse); expect(scope2.hasFocus, isFalse); expect(scope2.hasPrimaryFocus, isFalse); expect(parent1.hasFocus, isTrue); expect(parent2.hasFocus, isFalse); expect(scope1.focusedChild, equals(child1)); expect(scope2.focusedChild, equals(child4)); scope2.requestFocus(); await tester.pump(); expect(child4.hasFocus, isTrue); expect(child4.hasPrimaryFocus, isTrue); expect(child1.hasFocus, isFalse); expect(child1.hasPrimaryFocus, isFalse); expect(scope1.hasFocus, isFalse); expect(scope1.hasPrimaryFocus, isFalse); expect(scope2.hasFocus, isTrue); expect(scope2.hasPrimaryFocus, isFalse); expect(parent1.hasFocus, isFalse); expect(parent2.hasFocus, isTrue); expect(scope1.focusedChild, equals(child1)); expect(scope2.focusedChild, equals(child4)); }); testWidgets('Unfocus works properly', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope1 = FocusScopeNode()..attach(context); final FocusAttachment scope1Attachment = scope1.attach(context); final FocusScopeNode scope2 = FocusScopeNode(); final FocusAttachment scope2Attachment = scope2.attach(context); final FocusNode parent1 = FocusNode(); final FocusAttachment parent1Attachment = parent1.attach(context); final FocusNode parent2 = FocusNode(); final FocusAttachment parent2Attachment = parent2.attach(context); final FocusNode child1 = FocusNode(); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); final FocusAttachment child2Attachment = child2.attach(context); final FocusNode child3 = FocusNode(); final FocusAttachment child3Attachment = child3.attach(context); final FocusNode child4 = FocusNode(); final FocusAttachment child4Attachment = child4.attach(context); scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope); scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope); parent1Attachment.reparent(parent: scope1); parent2Attachment.reparent(parent: scope2); child1Attachment.reparent(parent: parent1); child2Attachment.reparent(parent: parent1); child3Attachment.reparent(parent: parent2); child4Attachment.reparent(parent: parent2); child1.requestFocus(); await tester.pump(); expect(scope1.focusedChild, equals(child1)); expect(parent2.children.contains(child1), isFalse); child1.unfocus(); await tester.pump(); expect(scope1.focusedChild, isNull); expect(child1.hasPrimaryFocus, isFalse); expect(scope1.hasFocus, isFalse); child1.requestFocus(); await tester.pump(); expect(scope1.focusedChild, equals(child1)); expect(parent2.children.contains(child1), isFalse); scope1.unfocus(); await tester.pump(); expect(scope1.focusedChild, isNull); expect(child1.hasPrimaryFocus, isFalse); expect(scope1.hasFocus, isFalse); }); testWidgets('Key handling bubbles up and terminates when handled.', (WidgetTester tester) async { final Set<FocusNode> receivedAnEvent = <FocusNode>{}; final Set<FocusNode> shouldHandle = <FocusNode>{}; bool handleEvent(FocusNode node, RawKeyEvent event) { if (shouldHandle.contains(node)) { receivedAnEvent.add(node); return true; } return false; } void sendEvent() { receivedAnEvent.clear(); sendFakeKeyEvent(<String, dynamic>{ 'type': 'keydown', 'keymap': 'fuchsia', 'hidUsage': 0x04, 'codePoint': 0x64, 'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta, }); } final BuildContext context = await setupWidget(tester); final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'Scope 1'); final FocusAttachment scope1Attachment = scope1.attach(context, onKey: handleEvent); final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'Scope 2'); final FocusAttachment scope2Attachment = scope2.attach(context, onKey: handleEvent); final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1', onKey: handleEvent); final FocusAttachment parent1Attachment = parent1.attach(context); final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2', onKey: handleEvent); final FocusAttachment parent2Attachment = parent2.attach(context); final FocusNode child1 = FocusNode(debugLabel: 'Child 1'); final FocusAttachment child1Attachment = child1.attach(context, onKey: handleEvent); final FocusNode child2 = FocusNode(debugLabel: 'Child 2'); final FocusAttachment child2Attachment = child2.attach(context, onKey: handleEvent); final FocusNode child3 = FocusNode(debugLabel: 'Child 3'); final FocusAttachment child3Attachment = child3.attach(context, onKey: handleEvent); final FocusNode child4 = FocusNode(debugLabel: 'Child 4'); final FocusAttachment child4Attachment = child4.attach(context, onKey: handleEvent); scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope); scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope); parent1Attachment.reparent(parent: scope1); parent2Attachment.reparent(parent: scope2); child1Attachment.reparent(parent: parent1); child2Attachment.reparent(parent: parent1); child3Attachment.reparent(parent: parent2); child4Attachment.reparent(parent: parent2); child4.requestFocus(); await tester.pump(); shouldHandle.addAll(<FocusNode>{scope2, parent2, child2, child4}); sendEvent(); expect(receivedAnEvent, equals(<FocusNode>{child4})); shouldHandle.remove(child4); sendEvent(); expect(receivedAnEvent, equals(<FocusNode>{parent2})); shouldHandle.remove(parent2); sendEvent(); expect(receivedAnEvent, equals(<FocusNode>{scope2})); shouldHandle.clear(); sendEvent(); expect(receivedAnEvent, isEmpty); child1.requestFocus(); await tester.pump(); shouldHandle.addAll(<FocusNode>{scope2, parent2, child2, child4}); sendEvent(); // Since none of the focused nodes handle this event, nothing should // receive it. expect(receivedAnEvent, isEmpty); }); testWidgets('implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); FocusScopeNode( debugLabel: 'Scope Label', ).debugFillProperties(builder); final List<String> description = builder.properties.where((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info)).map((DiagnosticsNode n) => n.toString()).toList(); expect(description, <String>[ 'debugLabel: "Scope Label"', ]); }); testWidgets('debugDescribeFocusTree produces correct output', (WidgetTester tester) async { final BuildContext context = await setupWidget(tester); final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'Scope 1'); final FocusAttachment scope1Attachment = scope1.attach(context); final FocusScopeNode scope2 = FocusScopeNode(); // No label, Just to test that it works. final FocusAttachment scope2Attachment = scope2.attach(context); final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1'); final FocusAttachment parent1Attachment = parent1.attach(context); final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2'); final FocusAttachment parent2Attachment = parent2.attach(context); final FocusNode child1 = FocusNode(debugLabel: 'Child 1'); final FocusAttachment child1Attachment = child1.attach(context); final FocusNode child2 = FocusNode(); // No label, Just to test that it works. final FocusAttachment child2Attachment = child2.attach(context); final FocusNode child3 = FocusNode(debugLabel: 'Child 3'); final FocusAttachment child3Attachment = child3.attach(context); final FocusNode child4 = FocusNode(debugLabel: 'Child 4'); final FocusAttachment child4Attachment = child4.attach(context); scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope); scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope); parent1Attachment.reparent(parent: scope1); parent2Attachment.reparent(parent: scope2); child1Attachment.reparent(parent: parent1); child2Attachment.reparent(parent: parent1); child3Attachment.reparent(parent: parent2); child4Attachment.reparent(parent: parent2); child4.requestFocus(); await tester.pump(); final String description = debugDescribeFocusTree(); expect( description, equalsIgnoringHashCodes( 'FocusManager#00000\n' ' │ currentFocus: FocusNode#00000\n' ' │\n' ' └─rootScope: FocusScopeNode#00000\n' ' │ FOCUSED\n' ' │ debugLabel: "Root Focus Scope"\n' ' │ focusedChild: FocusScopeNode#00000\n' ' │\n' ' ├─Child 1: FocusScopeNode#00000\n' ' │ │ context: Container-[GlobalKey#00000]\n' ' │ │ debugLabel: "Scope 1"\n' ' │ │\n' ' │ └─Child 1: FocusNode#00000\n' ' │ │ context: Container-[GlobalKey#00000]\n' ' │ │ debugLabel: "Parent 1"\n' ' │ │\n' ' │ ├─Child 1: FocusNode#00000\n' ' │ │ context: Container-[GlobalKey#00000]\n' ' │ │ debugLabel: "Child 1"\n' ' │ │\n' ' │ └─Child 2: FocusNode#00000\n' ' │ context: Container-[GlobalKey#00000]\n' ' │\n' ' └─Child 2: FocusScopeNode#00000\n' ' │ context: Container-[GlobalKey#00000]\n' ' │ FOCUSED\n' ' │ focusedChild: FocusNode#00000\n' ' │\n' ' └─Child 1: FocusNode#00000\n' ' │ context: Container-[GlobalKey#00000]\n' ' │ FOCUSED\n' ' │ debugLabel: "Parent 2"\n' ' │\n' ' ├─Child 1: FocusNode#00000\n' ' │ context: Container-[GlobalKey#00000]\n' ' │ debugLabel: "Child 3"\n' ' │\n' ' └─Child 2: FocusNode#00000\n' ' context: Container-[GlobalKey#00000]\n' ' FOCUSED\n' ' debugLabel: "Child 4"\n' )); }); }); }