| // Copyright 2014 The Flutter 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:math' as math; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| 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('Geometry is transformed properly.', (WidgetTester tester) async { |
| final FocusNode focusNode1 = FocusNode(debugLabel: 'Test Node 1'); |
| final FocusNode focusNode2 = FocusNode(debugLabel: 'Test Node 2'); |
| await tester.pumpWidget( |
| Padding( |
| padding: const EdgeInsets.all(8.0), |
| child: Column( |
| children: <Widget>[ |
| Focus( |
| focusNode: focusNode1, |
| child: const SizedBox(width: 200, height: 100), |
| ), |
| Transform.translate( |
| offset: const Offset(10, 20), |
| child: Transform.scale( |
| scale: 0.33, |
| child: Transform.rotate( |
| angle: math.pi, |
| child: Focus(focusNode: focusNode2, child: const SizedBox(width: 200, height: 100)), |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| focusNode2.requestFocus(); |
| await tester.pump(); |
| |
| expect(focusNode1.rect, equals(const Rect.fromLTRB(300.0, 8.0, 500.0, 108.0))); |
| expect(focusNode2.rect, equals(const Rect.fromLTRB(443.0, 194.5, 377.0, 161.5))); |
| expect(focusNode1.size, equals(const Size(200.0, 100.0))); |
| expect(focusNode2.size, equals(const Size(-66.0, -33.0))); |
| expect(focusNode1.offset, equals(const Offset(300.0, 8.0))); |
| expect(focusNode2.offset, equals(const Offset(443.0, 194.5))); |
| }); |
| |
| testWidgets('descendantsAreFocusable disables focus for descendants.', (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: parent2); |
| child1.requestFocus(); |
| await tester.pump(); |
| |
| expect(tester.binding.focusManager.primaryFocus, equals(child1)); |
| expect(scope.focusedChild, equals(child1)); |
| expect(scope.traversalDescendants.contains(child1), isTrue); |
| expect(scope.traversalDescendants.contains(child2), isTrue); |
| |
| parent2.descendantsAreFocusable = false; |
| // Node should still be focusable, even if descendants are not. |
| parent2.requestFocus(); |
| await tester.pump(); |
| expect(parent2.hasPrimaryFocus, isTrue); |
| |
| child2.requestFocus(); |
| await tester.pump(); |
| expect(tester.binding.focusManager.primaryFocus, isNot(equals(child2))); |
| expect(tester.binding.focusManager.primaryFocus, equals(parent2)); |
| expect(scope.focusedChild, equals(parent2)); |
| expect(scope.traversalDescendants.contains(child1), isTrue); |
| expect(scope.traversalDescendants.contains(child2), isFalse); |
| |
| parent1.descendantsAreFocusable = false; |
| await tester.pump(); |
| expect(tester.binding.focusManager.primaryFocus, isNot(equals(child2))); |
| expect(tester.binding.focusManager.primaryFocus, isNot(equals(child1))); |
| expect(scope.focusedChild, equals(parent2)); |
| expect(scope.traversalDescendants.contains(child1), isFalse); |
| expect(scope.traversalDescendants.contains(child2), isFalse); |
| }); |
| |
| testWidgets('implements debugFillProperties', (WidgetTester tester) async { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| FocusNode( |
| debugLabel: 'Label', |
| ).debugFillProperties(builder); |
| final List<String> description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList(); |
| expect(description, <String>[ |
| 'context: null', |
| 'descendantsAreFocusable: true', |
| 'canRequestFocus: true', |
| 'hasFocus: false', |
| 'hasPrimaryFocus: false', |
| ]); |
| }); |
| |
| testWidgets('onKeyEvent and onKey correctly cooperate', (WidgetTester tester) async { |
| final FocusNode focusNode = FocusNode(debugLabel: 'Test Node 3'); |
| List<List<KeyEventResult>> results = <List<KeyEventResult>>[ |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| ]; |
| final List<int> logs = <int>[]; |
| |
| await tester.pumpWidget( |
| Focus( |
| focusNode: FocusNode(debugLabel: 'Test Node 1'), |
| onKeyEvent: (_, KeyEvent event) { |
| logs.add(0); |
| return results[0][0]; |
| }, |
| onKey: (_, RawKeyEvent event) { |
| logs.add(1); |
| return results[0][1]; |
| }, |
| child: Focus( |
| focusNode: FocusNode(debugLabel: 'Test Node 2'), |
| onKeyEvent: (_, KeyEvent event) { |
| logs.add(10); |
| return results[1][0]; |
| }, |
| onKey: (_, RawKeyEvent event) { |
| logs.add(11); |
| return results[1][1]; |
| }, |
| child: Focus( |
| focusNode: focusNode, |
| onKeyEvent: (_, KeyEvent event) { |
| logs.add(20); |
| return results[2][0]; |
| }, |
| onKey: (_, RawKeyEvent event) { |
| logs.add(21); |
| return results[2][1]; |
| }, |
| child: const SizedBox(width: 200, height: 100), |
| ), |
| ), |
| ), |
| ); |
| focusNode.requestFocus(); |
| await tester.pump(); |
| |
| // All ignored. |
| results = <List<KeyEventResult>>[ |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| ]; |
| expect(await simulateKeyDownEvent(LogicalKeyboardKey.digit1), |
| false); |
| expect(logs, <int>[20, 21, 10, 11, 0, 1]); |
| logs.clear(); |
| |
| // The onKeyEvent should be able to stop propagation. |
| results = <List<KeyEventResult>>[ |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.handled, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| ]; |
| expect(await simulateKeyUpEvent(LogicalKeyboardKey.digit1), |
| true); |
| expect(logs, <int>[20, 21, 10, 11]); |
| logs.clear(); |
| |
| // The onKey should be able to stop propagation. |
| results = <List<KeyEventResult>>[ |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.handled], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| ]; |
| expect(await simulateKeyDownEvent(LogicalKeyboardKey.digit1), |
| true); |
| expect(logs, <int>[20, 21, 10, 11]); |
| logs.clear(); |
| |
| // KeyEventResult.skipRemainingHandlers works. |
| results = <List<KeyEventResult>>[ |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.skipRemainingHandlers, KeyEventResult.ignored], |
| <KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored], |
| ]; |
| expect(await simulateKeyUpEvent(LogicalKeyboardKey.digit1), |
| false); |
| expect(logs, <int>[20, 21, 10, 11]); |
| logs.clear(); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| }); |
| |
| 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('Requesting focus before adding to tree results in a request after adding', (WidgetTester tester) async { |
| final BuildContext context = await setupWidget(tester); |
| final FocusScopeNode scope = FocusScopeNode(); |
| final FocusAttachment scopeAttachment = scope.attach(context); |
| final FocusNode child = FocusNode(); |
| child.requestFocus(); |
| expect(child.hasPrimaryFocus, isFalse); // not attached yet. |
| |
| scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); |
| await tester.pump(); |
| expect(scope.focusedChild, isNull); |
| expect(child.hasPrimaryFocus, isFalse); // not attached yet. |
| |
| final FocusAttachment childAttachment = child.attach(context); |
| expect(child.hasPrimaryFocus, isFalse); // not parented yet. |
| childAttachment.reparent(parent: scope); |
| await tester.pump(); |
| expect(child.hasPrimaryFocus, isTrue); // now attached and parented, so focus finally happened. |
| }); |
| |
| 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('canRequestFocus affects children.', (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); |
| child1.requestFocus(); |
| await tester.pump(); |
| |
| expect(tester.binding.focusManager.primaryFocus, equals(child1)); |
| expect(scope.focusedChild, equals(child1)); |
| expect(scope.traversalDescendants.contains(child1), isTrue); |
| expect(scope.traversalDescendants.contains(child2), isTrue); |
| |
| scope.canRequestFocus = false; |
| await tester.pump(); |
| child2.requestFocus(); |
| await tester.pump(); |
| expect(tester.binding.focusManager.primaryFocus, isNot(equals(child2))); |
| expect(tester.binding.focusManager.primaryFocus, isNot(equals(child1))); |
| expect(scope.focusedChild, equals(child1)); |
| expect(scope.traversalDescendants.contains(child1), isFalse); |
| expect(scope.traversalDescendants.contains(child2), isFalse); |
| }); |
| |
| testWidgets("skipTraversal doesn't affect children.", (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); |
| child1.requestFocus(); |
| await tester.pump(); |
| |
| expect(tester.binding.focusManager.primaryFocus, equals(child1)); |
| expect(scope.focusedChild, equals(child1)); |
| expect(tester.binding.focusManager.rootScope.traversalDescendants.contains(scope), isTrue); |
| expect(scope.traversalDescendants.contains(child1), isTrue); |
| expect(scope.traversalDescendants.contains(child2), isTrue); |
| |
| scope.skipTraversal = true; |
| await tester.pump(); |
| expect(tester.binding.focusManager.primaryFocus, equals(child1)); |
| expect(scope.focusedChild, equals(child1)); |
| expect(tester.binding.focusManager.rootScope.traversalDescendants.contains(scope), isFalse); |
| expect(scope.traversalDescendants.contains(child1), isTrue); |
| expect(scope.traversalDescendants.contains(child2), isTrue); |
| }); |
| |
| 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('ancestors and descendants are computed and recomputed properly', (WidgetTester tester) async { |
| final BuildContext context = await setupWidget(tester); |
| final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1'); |
| 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); |
| child4.requestFocus(); |
| await tester.pump(); |
| expect(child4.ancestors, equals(<FocusNode>[parent2, scope2, tester.binding.focusManager.rootScope])); |
| expect(tester.binding.focusManager.rootScope.descendants, equals(<FocusNode>[child1, child2, parent1, scope1, child3, child4, parent2, scope2])); |
| scope2Attachment.reparent(parent: child2); |
| await tester.pump(); |
| expect(child4.ancestors, equals(<FocusNode>[parent2, scope2, child2, parent1, scope1, tester.binding.focusManager.rootScope])); |
| expect(tester.binding.focusManager.rootScope.descendants, equals(<FocusNode>[child1, child3, child4, parent2, scope2, child2, parent1, scope1])); |
| }); |
| |
| 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 with disposition previouslyFocusedChild works properly', (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); |
| |
| // Build up a history. |
| child4.requestFocus(); |
| await tester.pump(); |
| child2.requestFocus(); |
| await tester.pump(); |
| child3.requestFocus(); |
| await tester.pump(); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| |
| child1.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child2)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasFocus, isTrue); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child2.hasPrimaryFocus, isTrue); |
| |
| // Can re-focus child. |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasFocus, isTrue); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isTrue); |
| expect(child3.hasPrimaryFocus, isFalse); |
| |
| // The same thing happens when unfocusing a second time. |
| child1.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child2)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasFocus, isTrue); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child2.hasPrimaryFocus, isTrue); |
| |
| // When the scope gets unfocused, then the sibling scope gets focus. |
| child1.requestFocus(); |
| await tester.pump(); |
| scope1.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasFocus, isFalse); |
| expect(scope2.hasFocus, isTrue); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child3.hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets('Unfocus with disposition scope works properly', (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); |
| |
| // Build up a history. |
| child4.requestFocus(); |
| await tester.pump(); |
| child2.requestFocus(); |
| await tester.pump(); |
| child3.requestFocus(); |
| await tester.pump(); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| |
| child1.unfocus(); |
| await tester.pump(); |
| // Focused child doesn't change. |
| expect(scope1.focusedChild, isNull); |
| expect(scope2.focusedChild, equals(child3)); |
| // Focus does change. |
| expect(scope1.hasPrimaryFocus, isTrue); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child2.hasPrimaryFocus, isFalse); |
| |
| // Can re-focus child. |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasFocus, isTrue); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isTrue); |
| expect(child3.hasPrimaryFocus, isFalse); |
| |
| // The same thing happens when unfocusing a second time. |
| child1.unfocus(); |
| await tester.pump(); |
| expect(scope1.focusedChild, isNull); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasPrimaryFocus, isTrue); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child2.hasPrimaryFocus, isFalse); |
| |
| // When the scope gets unfocused, then its parent scope (the root scope) |
| // gets focus, but it doesn't mess with the focused children. |
| child1.requestFocus(); |
| await tester.pump(); |
| scope1.unfocus(); |
| await tester.pump(); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasFocus, isFalse); |
| expect(scope2.hasFocus, isFalse); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child3.hasPrimaryFocus, isFalse); |
| expect(FocusManager.instance.rootScope.hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets('Unfocus works properly when some nodes are unfocusable', (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); |
| |
| // Build up a history. |
| child4.requestFocus(); |
| await tester.pump(); |
| child2.requestFocus(); |
| await tester.pump(); |
| child3.requestFocus(); |
| await tester.pump(); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(child1.hasPrimaryFocus, isTrue); |
| |
| scope1.canRequestFocus = false; |
| await tester.pump(); |
| |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(child3.hasPrimaryFocus, isTrue); |
| |
| child1.unfocus(); |
| await tester.pump(); |
| expect(child3.hasPrimaryFocus, isTrue); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasPrimaryFocus, isFalse); |
| expect(scope2.hasFocus, isTrue); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child2.hasPrimaryFocus, isFalse); |
| |
| child1.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); |
| await tester.pump(); |
| expect(child3.hasPrimaryFocus, isTrue); |
| expect(scope1.focusedChild, equals(child1)); |
| expect(scope2.focusedChild, equals(child3)); |
| expect(scope1.hasPrimaryFocus, isFalse); |
| expect(scope2.hasFocus, isTrue); |
| expect(child1.hasPrimaryFocus, isFalse); |
| expect(child2.hasPrimaryFocus, isFalse); |
| }); |
| |
| testWidgets('Requesting focus on a scope works properly when some focusedChild nodes are unfocusable', (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); |
| |
| // Build up a history. |
| child4.requestFocus(); |
| await tester.pump(); |
| child2.requestFocus(); |
| await tester.pump(); |
| child3.requestFocus(); |
| await tester.pump(); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(child1.hasPrimaryFocus, isTrue); |
| |
| child1.canRequestFocus = false; |
| child3.canRequestFocus = false; |
| await tester.pump(); |
| scope1.requestFocus(); |
| await tester.pump(); |
| |
| expect(scope1.focusedChild, equals(child2)); |
| expect(child2.hasPrimaryFocus, isTrue); |
| |
| scope2.requestFocus(); |
| await tester.pump(); |
| |
| expect(scope2.focusedChild, equals(child4)); |
| expect(child4.hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets('Key handling bubbles up and terminates when handled.', (WidgetTester tester) async { |
| final Set<FocusNode> receivedAnEvent = <FocusNode>{}; |
| final Set<FocusNode> shouldHandle = <FocusNode>{}; |
| KeyEventResult handleEvent(FocusNode node, RawKeyEvent event) { |
| if (shouldHandle.contains(node)) { |
| receivedAnEvent.add(node); |
| return KeyEventResult.handled; |
| } |
| return KeyEventResult.ignored; |
| } |
| |
| Future<void> sendEvent() async { |
| receivedAnEvent.clear(); |
| await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); |
| } |
| |
| 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}); |
| await sendEvent(); |
| expect(receivedAnEvent, equals(<FocusNode>{child4})); |
| shouldHandle.remove(child4); |
| await sendEvent(); |
| expect(receivedAnEvent, equals(<FocusNode>{parent2})); |
| shouldHandle.remove(parent2); |
| await sendEvent(); |
| expect(receivedAnEvent, equals(<FocusNode>{scope2})); |
| shouldHandle.clear(); |
| await sendEvent(); |
| expect(receivedAnEvent, isEmpty); |
| child1.requestFocus(); |
| await tester.pump(); |
| shouldHandle.addAll(<FocusNode>{scope2, parent2, child2, child4}); |
| await sendEvent(); |
| // Since none of the focused nodes handle this event, nothing should |
| // receive it. |
| expect(receivedAnEvent, isEmpty); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Initial highlight mode guesses correctly.', (WidgetTester tester) async { |
| FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic; |
| switch (defaultTargetPlatform) { |
| case TargetPlatform.fuchsia: |
| case TargetPlatform.android: |
| case TargetPlatform.iOS: |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch)); |
| break; |
| case TargetPlatform.linux: |
| case TargetPlatform.macOS: |
| case TargetPlatform.windows: |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| break; |
| } |
| }, variant: TargetPlatformVariant.all()); |
| |
| testWidgets('Mouse events change initial focus highlight mode on mobile.', (WidgetTester tester) async { |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch)); |
| RendererBinding.instance!.initMouseTracker(); // Clear out the mouse state. |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 0); |
| addTearDown(gesture.removePointer); |
| await gesture.moveTo(Offset.zero); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| }, variant: TargetPlatformVariant.mobile()); |
| |
| testWidgets('Mouse events change initial focus highlight mode on desktop.', (WidgetTester tester) async { |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| RendererBinding.instance!.initMouseTracker(); // Clear out the mouse state. |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 0); |
| addTearDown(gesture.removePointer); |
| await gesture.moveTo(Offset.zero); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| }, variant: TargetPlatformVariant.desktop()); |
| |
| testWidgets('Keyboard events change initial focus highlight mode.', (WidgetTester tester) async { |
| await tester.sendKeyEvent(LogicalKeyboardKey.enter); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| }, variant: TargetPlatformVariant.all()); |
| |
| testWidgets('Events change focus highlight mode.', (WidgetTester tester) async { |
| await setupWidget(tester); |
| int callCount = 0; |
| FocusHighlightMode? lastMode; |
| void handleModeChange(FocusHighlightMode mode) { |
| lastMode = mode; |
| callCount++; |
| } |
| FocusManager.instance.addHighlightModeListener(handleModeChange); |
| addTearDown(() => FocusManager.instance.removeHighlightModeListener(handleModeChange)); |
| expect(callCount, equals(0)); |
| expect(lastMode, isNull); |
| FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic; |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch)); |
| await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); |
| expect(callCount, equals(1)); |
| expect(lastMode, FocusHighlightMode.traditional); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| await tester.tap(find.byType(Container), warnIfMissed: false); |
| expect(callCount, equals(2)); |
| expect(lastMode, FocusHighlightMode.touch); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch)); |
| final TestGesture gesture = await tester.startGesture(Offset.zero, kind: PointerDeviceKind.mouse); |
| addTearDown(gesture.removePointer); |
| await gesture.up(); |
| expect(callCount, equals(3)); |
| expect(lastMode, FocusHighlightMode.traditional); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| await tester.tap(find.byType(Container), warnIfMissed: false); |
| expect(callCount, equals(4)); |
| expect(lastMode, FocusHighlightMode.touch); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch)); |
| FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; |
| expect(callCount, equals(5)); |
| expect(lastMode, FocusHighlightMode.traditional); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional)); |
| FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch; |
| expect(callCount, equals(6)); |
| expect(lastMode, FocusHighlightMode.touch); |
| expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch)); |
| }); |
| |
| testWidgets('implements debugFillProperties', (WidgetTester tester) async { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| FocusScopeNode( |
| debugLabel: 'Scope Label', |
| ).debugFillProperties(builder); |
| final List<String> description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList(); |
| expect(description, <String>[ |
| 'context: null', |
| 'descendantsAreFocusable: true', |
| 'canRequestFocus: true', |
| 'hasFocus: false', |
| 'hasPrimaryFocus: false', |
| ]); |
| }); |
| |
| 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' |
| ' │ primaryFocus: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n' |
| ' │ primaryFocusCreator: Container-[GlobalKey#00000] ← [root]\n' |
| ' │\n' |
| ' └─rootScope: FocusScopeNode#00000(Root Focus Scope [IN FOCUS PATH])\n' |
| ' │ IN FOCUS PATH\n' |
| ' │ focusedChildren: FocusScopeNode#00000([IN FOCUS PATH])\n' |
| ' │\n' |
| ' ├─Child 1: FocusScopeNode#00000(Scope 1)\n' |
| ' │ │ context: Container-[GlobalKey#00000]\n' |
| ' │ │\n' |
| ' │ └─Child 1: FocusNode#00000(Parent 1)\n' |
| ' │ │ context: Container-[GlobalKey#00000]\n' |
| ' │ │\n' |
| ' │ ├─Child 1: FocusNode#00000(Child 1)\n' |
| ' │ │ context: Container-[GlobalKey#00000]\n' |
| ' │ │\n' |
| ' │ └─Child 2: FocusNode#00000\n' |
| ' │ context: Container-[GlobalKey#00000]\n' |
| ' │\n' |
| ' └─Child 2: FocusScopeNode#00000([IN FOCUS PATH])\n' |
| ' │ context: Container-[GlobalKey#00000]\n' |
| ' │ IN FOCUS PATH\n' |
| ' │ focusedChildren: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n' |
| ' │\n' |
| ' └─Child 1: FocusNode#00000(Parent 2 [IN FOCUS PATH])\n' |
| ' │ context: Container-[GlobalKey#00000]\n' |
| ' │ IN FOCUS PATH\n' |
| ' │\n' |
| ' ├─Child 1: FocusNode#00000(Child 3)\n' |
| ' │ context: Container-[GlobalKey#00000]\n' |
| ' │\n' |
| ' └─Child 2: FocusNode#00000(Child 4 [PRIMARY FOCUS])\n' |
| ' context: Container-[GlobalKey#00000]\n' |
| ' PRIMARY FOCUS\n', |
| ), |
| ); |
| }); |
| }); |
| |
| |
| group('Autofocus', () { |
| testWidgets( |
| 'works when the previous focused node is detached', |
| (WidgetTester tester) async { |
| final FocusNode node1 = FocusNode(); |
| final FocusNode node2 = FocusNode(); |
| |
| await tester.pumpWidget( |
| FocusScope( |
| child: Focus(autofocus: true, focusNode: node1, child: const Placeholder()), |
| ), |
| ); |
| await tester.pump(); |
| expect(node1.hasPrimaryFocus, isTrue); |
| |
| await tester.pumpWidget( |
| FocusScope( |
| child: SizedBox( |
| child: Focus(autofocus: true, focusNode: node2, child: const Placeholder()), |
| ), |
| ), |
| ); |
| await tester.pump(); |
| expect(node2.hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets( |
| 'node detached before autofocus is applied', |
| (WidgetTester tester) async { |
| final FocusScopeNode scopeNode = FocusScopeNode(); |
| final FocusNode node1 = FocusNode(); |
| |
| await tester.pumpWidget( |
| FocusScope( |
| node: scopeNode, |
| child: Focus( |
| autofocus: true, |
| focusNode: node1, |
| child: const Placeholder(), |
| ), |
| ), |
| ); |
| await tester.pumpWidget( |
| FocusScope( |
| node: scopeNode, |
| child: const Focus(child: Placeholder()), |
| ), |
| ); |
| |
| await tester.pump(); |
| expect(node1.hasPrimaryFocus, isFalse); |
| expect(scopeNode.hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets('autofocus the first candidate', (WidgetTester tester) async { |
| final FocusNode node1 = FocusNode(); |
| final FocusNode node2 = FocusNode(); |
| final FocusNode node3 = FocusNode(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Focus>[ |
| Focus( |
| autofocus: true, |
| focusNode: node1, |
| child: const SizedBox(), |
| ), |
| Focus( |
| autofocus: true, |
| focusNode: node2, |
| child: const SizedBox(), |
| ), |
| Focus( |
| autofocus: true, |
| focusNode: node3, |
| child: const SizedBox(), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(node1.hasPrimaryFocus, isTrue); |
| }); |
| |
| testWidgets('Autofocus works with global key reparenting', (WidgetTester tester) async { |
| final FocusNode node = FocusNode(); |
| final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1'); |
| final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2'); |
| final GlobalKey key = GlobalKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Focus>[ |
| FocusScope( |
| node: scope1, |
| child: Focus( |
| key: key, |
| focusNode: node, |
| child: const SizedBox(), |
| ), |
| ), |
| FocusScope(node: scope2, child: const SizedBox()), |
| ], |
| ), |
| ), |
| ); |
| |
| // _applyFocusChange will be called before persistentCallbacks, |
| // guaranteeing the focus changes are applied before the BuildContext |
| // `node` attaches to gets reparented. |
| scope1.autofocus(node); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Column( |
| children: <Focus>[ |
| FocusScope(node: scope1, child: const SizedBox()), |
| FocusScope( |
| node: scope2, |
| child: Focus( |
| key: key, |
| focusNode: node, |
| child: const SizedBox(), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| expect(node.hasPrimaryFocus, isTrue); |
| expect(scope2.hasFocus, isTrue); |
| }); |
| }); |
| |
| testWidgets("Doesn't lose focused child when reparenting if the nearestScope doesn't change.", (WidgetTester tester) async { |
| final BuildContext context = await setupWidget(tester); |
| final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1'); |
| final FocusScopeNode parent2 = FocusScopeNode(debugLabel: 'parent2'); |
| final FocusAttachment parent1Attachment = parent1.attach(context); |
| 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); |
| parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope); |
| child1Attachment.reparent(parent: parent1); |
| child2Attachment.reparent(parent: child1); |
| parent1.autofocus(child2); |
| await tester.pump(); |
| parent2Attachment.reparent(parent: tester.binding.focusManager.rootScope); |
| parent2.requestFocus(); |
| await tester.pump(); |
| expect(parent1.focusedChild, equals(child2)); |
| child2Attachment.reparent(parent: parent1); |
| expect(parent1.focusedChild, equals(child2)); |
| parent1.requestFocus(); |
| await tester.pump(); |
| expect(parent1.focusedChild, equals(child2)); |
| }); |
| |
| testWidgets('Ancestors get notified exactly as often as needed if focused child changes focus.', (WidgetTester tester) async { |
| bool topFocus = false; |
| bool parent1Focus = false; |
| bool parent2Focus = false; |
| bool child1Focus = false; |
| bool child2Focus = false; |
| int topNotify = 0; |
| int parent1Notify = 0; |
| int parent2Notify = 0; |
| int child1Notify = 0; |
| int child2Notify = 0; |
| void clear() { |
| topFocus = false; |
| parent1Focus = false; |
| parent2Focus = false; |
| child1Focus = false; |
| child2Focus = false; |
| topNotify = 0; |
| parent1Notify = 0; |
| parent2Notify = 0; |
| child1Notify = 0; |
| child2Notify = 0; |
| } |
| final BuildContext context = await setupWidget(tester); |
| final FocusScopeNode top = FocusScopeNode(debugLabel: 'top'); |
| final FocusAttachment topAttachment = top.attach(context); |
| final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1'); |
| final FocusAttachment parent1Attachment = parent1.attach(context); |
| final FocusScopeNode parent2 = FocusScopeNode(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); |
| topAttachment.reparent(parent: tester.binding.focusManager.rootScope); |
| parent1Attachment.reparent(parent: top); |
| parent2Attachment.reparent(parent: top); |
| child1Attachment.reparent(parent: parent1); |
| child2Attachment.reparent(parent: parent2); |
| top.addListener(() { |
| topNotify++; |
| topFocus = top.hasFocus; |
| }); |
| parent1.addListener(() { |
| parent1Notify++; |
| parent1Focus = parent1.hasFocus; |
| }); |
| parent2.addListener(() { |
| parent2Notify++; |
| parent2Focus = parent2.hasFocus; |
| }); |
| child1.addListener(() { |
| child1Notify++; |
| child1Focus = child1.hasFocus; |
| }); |
| child2.addListener(() { |
| child2Notify++; |
| child2Focus = child2.hasFocus; |
| }); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(topFocus, isTrue); |
| expect(parent1Focus, isTrue); |
| expect(child1Focus, isTrue); |
| expect(parent2Focus, isFalse); |
| expect(child2Focus, isFalse); |
| expect(topNotify, equals(1)); |
| expect(parent1Notify, equals(1)); |
| expect(child1Notify, equals(1)); |
| expect(parent2Notify, equals(0)); |
| expect(child2Notify, equals(0)); |
| |
| clear(); |
| child1.unfocus(); |
| await tester.pump(); |
| expect(topFocus, isFalse); |
| expect(parent1Focus, isTrue); |
| expect(child1Focus, isFalse); |
| expect(parent2Focus, isFalse); |
| expect(child2Focus, isFalse); |
| expect(topNotify, equals(0)); |
| expect(parent1Notify, equals(1)); |
| expect(child1Notify, equals(1)); |
| expect(parent2Notify, equals(0)); |
| expect(child2Notify, equals(0)); |
| |
| clear(); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(topFocus, isFalse); |
| expect(parent1Focus, isTrue); |
| expect(child1Focus, isTrue); |
| expect(parent2Focus, isFalse); |
| expect(child2Focus, isFalse); |
| expect(topNotify, equals(0)); |
| expect(parent1Notify, equals(1)); |
| expect(child1Notify, equals(1)); |
| expect(parent2Notify, equals(0)); |
| expect(child2Notify, equals(0)); |
| |
| clear(); |
| child2.requestFocus(); |
| await tester.pump(); |
| expect(topFocus, isFalse); |
| expect(parent1Focus, isFalse); |
| expect(child1Focus, isFalse); |
| expect(parent2Focus, isTrue); |
| expect(child2Focus, isTrue); |
| expect(topNotify, equals(0)); |
| expect(parent1Notify, equals(1)); |
| expect(child1Notify, equals(1)); |
| expect(parent2Notify, equals(1)); |
| expect(child2Notify, equals(1)); |
| |
| // Changing the focus back before the pump shouldn't cause notifications. |
| clear(); |
| child1.requestFocus(); |
| child2.requestFocus(); |
| await tester.pump(); |
| expect(topFocus, isFalse); |
| expect(parent1Focus, isFalse); |
| expect(child1Focus, isFalse); |
| expect(parent2Focus, isFalse); |
| expect(child2Focus, isFalse); |
| expect(topNotify, equals(0)); |
| expect(parent1Notify, equals(0)); |
| expect(child1Notify, equals(0)); |
| expect(parent2Notify, equals(0)); |
| expect(child2Notify, equals(0)); |
| }); |
| |
| testWidgets('Focus changes notify listeners.', (WidgetTester tester) async { |
| final BuildContext context = await setupWidget(tester); |
| final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1'); |
| final FocusAttachment parent1Attachment = parent1.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); |
| parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope); |
| child1Attachment.reparent(parent: parent1); |
| child2Attachment.reparent(parent: child1); |
| |
| int notifyCount = 0; |
| void handleFocusChange() { |
| notifyCount++; |
| } |
| tester.binding.focusManager.addListener(handleFocusChange); |
| |
| parent1.autofocus(child2); |
| expect(notifyCount, equals(0)); |
| await tester.pump(); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| child1.requestFocus(); |
| child2.requestFocus(); |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| child2.requestFocus(); |
| await tester.pump(); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| child2.unfocus(); |
| await tester.pump(); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| tester.binding.focusManager.removeListener(handleFocusChange); |
| }); |
| |
| testWidgets('FocusManager notifies listeners when a widget loses focus because it was removed.', (WidgetTester tester) async { |
| final FocusNode nodeA = FocusNode(debugLabel: 'a'); |
| final FocusNode nodeB = FocusNode(debugLabel: 'b'); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.rtl, |
| child: Column( |
| children: <Widget>[ |
| Focus(focusNode: nodeA , child: const Text('a')), |
| Focus(focusNode: nodeB, child: const Text('b')), |
| ], |
| ), |
| ), |
| ); |
| int notifyCount = 0; |
| void handleFocusChange() { |
| notifyCount++; |
| } |
| tester.binding.focusManager.addListener(handleFocusChange); |
| |
| nodeA.requestFocus(); |
| await tester.pump(); |
| expect(nodeA.hasPrimaryFocus, isTrue); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.rtl, |
| child: Column( |
| children: <Widget>[ |
| Focus(focusNode: nodeB, child: const Text('b')), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pump(); |
| expect(nodeA.hasPrimaryFocus, isFalse); |
| expect(nodeB.hasPrimaryFocus, isFalse); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| tester.binding.focusManager.removeListener(handleFocusChange); |
| }); |
| |
| testWidgets('debugFocusChanges causes logging of focus changes', (WidgetTester tester) async { |
| final bool oldDebugFocusChanges = debugFocusChanges; |
| final DebugPrintCallback oldDebugPrint = debugPrint; |
| final StringBuffer messages = StringBuffer(); |
| debugPrint = (String? message, {int? wrapWidth}) { |
| messages.writeln(message ?? ''); |
| }; |
| debugFocusChanges = true; |
| try { |
| final BuildContext context = await setupWidget(tester); |
| final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1'); |
| final FocusAttachment parent1Attachment = parent1.attach(context); |
| final FocusNode child1 = FocusNode(debugLabel: 'child1'); |
| final FocusAttachment child1Attachment = child1.attach(context); |
| parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope); |
| child1Attachment.reparent(parent: parent1); |
| |
| int notifyCount = 0; |
| void handleFocusChange() { |
| notifyCount++; |
| } |
| tester.binding.focusManager.addListener(handleFocusChange); |
| |
| parent1.requestFocus(); |
| expect(notifyCount, equals(0)); |
| await tester.pump(); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| child1.requestFocus(); |
| await tester.pump(); |
| expect(notifyCount, equals(1)); |
| notifyCount = 0; |
| |
| tester.binding.focusManager.removeListener(handleFocusChange); |
| } finally { |
| debugFocusChanges = oldDebugFocusChanges; |
| debugPrint = oldDebugPrint; |
| } |
| final String messagesStr = messages.toString(); |
| expect(messagesStr.split('\n').length, equals(58)); |
| expect(messagesStr, contains(RegExp(r' └─Child 1: FocusScopeNode#[a-f0-9]{5}\(parent1 \[PRIMARY FOCUS\]\)'))); |
| expect(messagesStr, contains('FOCUS: Notifying 2 dirty nodes')); |
| expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1'))); |
| }); |
| } |