mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
A bunch of random cleanup things I found while doing MenuBar development. Changes an if test to an assert in binding.dart, since the if should always be true. Adds the default ShortcutRegistrar that should have been in the ShortcutRegistry PR. Moves a debug message in the FocusManager to print the result after the focus change instead of before. Reorders the test parameters in theme_data_test.dart to match the order of the theme data fields everywhere else.
1704 lines
74 KiB
Dart
1704 lines
74 KiB
Dart
// 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('descendantsAreTraversable disables traversal 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);
|
|
|
|
expect(scope.traversalDescendants, equals(<FocusNode>[child1, parent1, child2, parent2]));
|
|
|
|
parent2.descendantsAreTraversable = false;
|
|
expect(scope.traversalDescendants, equals(<FocusNode>[child1, parent1, parent2]));
|
|
|
|
parent1.descendantsAreTraversable = false;
|
|
expect(scope.traversalDescendants, equals(<FocusNode>[parent1, parent2]));
|
|
|
|
parent1.descendantsAreTraversable = true;
|
|
parent2.descendantsAreTraversable = true;
|
|
scope.descendantsAreTraversable = false;
|
|
expect(scope.traversalDescendants, equals(<FocusNode>[]));
|
|
});
|
|
|
|
testWidgets("canRequestFocus doesn't affect traversalChildren", (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(parent2.traversalChildren.contains(child2), isTrue);
|
|
expect(scope.traversalChildren.contains(parent2), isTrue);
|
|
|
|
parent2.canRequestFocus = false;
|
|
await tester.pump();
|
|
expect(parent2.traversalChildren.contains(child2), isTrue);
|
|
expect(scope.traversalChildren.contains(parent2), 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',
|
|
'descendantsAreTraversable: 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);
|
|
expect(scope.traversalChildren.contains(parent1), isTrue);
|
|
expect(parent1.traversalChildren.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);
|
|
expect(scope.traversalChildren.contains(parent1), isFalse);
|
|
expect(parent1.traversalChildren.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);
|
|
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);
|
|
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);
|
|
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',
|
|
'descendantsAreTraversable: 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: Notified 2 dirty nodes'));
|
|
expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1')));
|
|
});
|
|
}
|