Unverified Commit adaf78a6 authored by Kostia Sokolovskyi's avatar Kostia Sokolovskyi Committed by GitHub

Cover focus tests with leak tracking (#134457)

parent b90c1a87
......@@ -21,13 +21,16 @@ void main() {
}
group(FocusNode, () {
testWidgets('Can add children.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can add children.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusNode parent = FocusNode();
addTearDown(parent.dispose);
final FocusAttachment parentAttachment = parent.attach(context);
final FocusNode child1 = FocusNode();
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
parentAttachment.reparent(parent: tester.binding.focusManager.rootScope);
child1Attachment.reparent(parent: parent);
......@@ -41,13 +44,16 @@ void main() {
expect(parent.children.last, equals(child2));
});
testWidgets('Can remove children.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can remove children.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusNode parent = FocusNode();
addTearDown(parent.dispose);
final FocusAttachment parentAttachment = parent.attach(context);
final FocusNode child1 = FocusNode();
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
parentAttachment.reparent(parent: tester.binding.focusManager.rootScope);
child1Attachment.reparent(parent: parent);
......@@ -67,9 +73,12 @@ void main() {
expect(parent.children, isEmpty);
});
testWidgets('Geometry is transformed properly.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Geometry is transformed properly.', (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode(debugLabel: 'Test Node 1');
addTearDown(focusNode1.dispose);
final FocusNode focusNode2 = FocusNode(debugLabel: 'Test Node 2');
addTearDown(focusNode2.dispose);
await tester.pumpWidget(
Padding(
padding: const EdgeInsets.all(8.0),
......@@ -104,17 +113,22 @@ void main() {
expect(focusNode2.offset, equals(const Offset(443.0, 194.5)));
});
testWidgets('descendantsAreFocusable disables focus for descendants.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('descendantsAreFocusable disables focus for descendants.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parent1Attachment.reparent(parent: scope);
......@@ -152,17 +166,22 @@ void main() {
expect(scope.traversalDescendants.contains(child2), isFalse);
});
testWidgets('descendantsAreTraversable disables traversal for descendants.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('descendantsAreTraversable disables traversal for descendants.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -185,17 +204,22 @@ void main() {
expect(scope.traversalDescendants, equals(<FocusNode>[]));
});
testWidgets("canRequestFocus doesn't affect traversalChildren", (WidgetTester tester) async {
testWidgetsWithLeakTracking("canRequestFocus doesn't affect traversalChildren", (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parent1Attachment.reparent(parent: scope);
......@@ -216,11 +240,11 @@ void main() {
expect(scope.traversalChildren.contains(parent2), isFalse);
});
testWidgets('implements debugFillProperties', (WidgetTester tester) async {
testWidgetsWithLeakTracking('implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
FocusNode(
debugLabel: 'Label',
).debugFillProperties(builder);
final FocusNode focusNode = FocusNode(debugLabel: 'Label');
addTearDown(focusNode.dispose);
focusNode.debugFillProperties(builder);
final List<String> description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[
'context: null',
......@@ -232,8 +256,13 @@ void main() {
]);
});
testWidgets('onKeyEvent and onKey correctly cooperate', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node 3');
testWidgetsWithLeakTracking('onKeyEvent and onKey correctly cooperate', (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode(debugLabel: 'Test Node 1');
addTearDown(focusNode1.dispose);
final FocusNode focusNode2 = FocusNode(debugLabel: 'Test Node 2');
addTearDown(focusNode2.dispose);
final FocusNode focusNode3 = FocusNode(debugLabel: 'Test Node 3');
addTearDown(focusNode3.dispose);
List<List<KeyEventResult>> results = <List<KeyEventResult>>[
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
<KeyEventResult>[KeyEventResult.ignored, KeyEventResult.ignored],
......@@ -243,7 +272,7 @@ void main() {
await tester.pumpWidget(
Focus(
focusNode: FocusNode(debugLabel: 'Test Node 1'),
focusNode: focusNode1,
onKeyEvent: (_, KeyEvent event) {
logs.add(0);
return results[0][0];
......@@ -253,7 +282,7 @@ void main() {
return results[0][1];
},
child: Focus(
focusNode: FocusNode(debugLabel: 'Test Node 2'),
focusNode: focusNode2,
onKeyEvent: (_, KeyEvent event) {
logs.add(10);
return results[1][0];
......@@ -263,7 +292,7 @@ void main() {
return results[1][1];
},
child: Focus(
focusNode: focusNode,
focusNode: focusNode3,
onKeyEvent: (_, KeyEvent event) {
logs.add(20);
return results[2][0];
......@@ -277,7 +306,7 @@ void main() {
),
),
);
focusNode.requestFocus();
focusNode3.requestFocus();
await tester.pump();
// All ignored.
......@@ -328,15 +357,19 @@ void main() {
group(FocusScopeNode, () {
testWidgets('Can setFirstFocus on a scope with no manager.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can setFirstFocus on a scope with no manager.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
scope.attach(context);
final FocusScopeNode parent = FocusScopeNode(debugLabel: 'Parent');
addTearDown(parent.dispose);
parent.attach(context);
final FocusScopeNode child1 = FocusScopeNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusScopeNode child2 = FocusScopeNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
child2.attach(context);
scope.setFirstFocus(parent);
parent.setFirstFocus(child1);
......@@ -353,15 +386,19 @@ void main() {
expect(scope.focusedChild, equals(parent));
});
testWidgets('Removing a node removes it from scope.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Removing a node removes it from scope.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode();
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent = FocusNode();
addTearDown(parent.dispose);
final FocusAttachment parentAttachment = parent.attach(context);
final FocusNode child1 = FocusNode();
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parentAttachment.reparent(parent: scope);
......@@ -378,15 +415,19 @@ void main() {
expect(scope.focusedChild, isNull);
});
testWidgets('Can add children to scope and focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can add children to scope and focus', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode();
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent = FocusNode();
addTearDown(parent.dispose);
final FocusAttachment parentAttachment = parent.attach(context);
final FocusNode child1 = FocusNode();
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parentAttachment.reparent(parent: scope);
......@@ -418,11 +459,13 @@ void main() {
expect(child2.hasPrimaryFocus, isTrue);
});
testWidgets('Requesting focus before adding to tree results in a request after adding', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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();
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode child = FocusNode();
addTearDown(child.dispose);
child.requestFocus();
expect(child.hasPrimaryFocus, isFalse); // not attached yet.
......@@ -438,15 +481,19 @@ void main() {
expect(child.hasPrimaryFocus, isTrue); // now attached and parented, so focus finally happened.
});
testWidgets('Autofocus works.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Autofocus works.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent = FocusNode(debugLabel: 'Parent');
addTearDown(parent.dispose);
final FocusAttachment parentAttachment = parent.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parentAttachment.reparent(parent: scope);
......@@ -475,15 +522,19 @@ void main() {
expect(child2.hasPrimaryFocus, isFalse);
});
testWidgets('Adding a focusedChild to a scope sets scope as focusedChild in parent scope', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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();
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode();
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode child1 = FocusNode();
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: scope1);
......@@ -507,17 +558,22 @@ void main() {
expect(child2.hasPrimaryFocus, isFalse);
});
testWidgets('Can move node with focus without losing focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can move node with focus without losing focus', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parent1Attachment.reparent(parent: scope);
......@@ -544,17 +600,22 @@ void main() {
expect(parent2.children.first, equals(child1));
});
testWidgets('canRequestFocus affects children.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('canRequestFocus affects children.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parent1Attachment.reparent(parent: scope);
......@@ -584,17 +645,22 @@ void main() {
expect(parent1.traversalChildren.contains(child2), isFalse);
});
testWidgets("skipTraversal doesn't affect children.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("skipTraversal doesn't affect children.", (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope');
addTearDown(scope.dispose);
final FocusAttachment scopeAttachment = scope.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parent1Attachment.reparent(parent: scope);
......@@ -619,23 +685,31 @@ void main() {
expect(scope.traversalDescendants.contains(child2), isTrue);
});
testWidgets('Can move node between scopes and lose scope focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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);
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'child3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'child4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -657,23 +731,31 @@ void main() {
expect(parent2.children.contains(child1), isTrue);
});
testWidgets('ancestors and descendants are computed and recomputed properly', (WidgetTester tester) async {
testWidgetsWithLeakTracking('ancestors and descendants are computed and recomputed properly', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1');
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'child3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'child4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -693,23 +775,31 @@ void main() {
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 {
testWidgetsWithLeakTracking('Can move focus between scopes and keep focus', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode();
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode();
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode();
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode();
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode();
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode();
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode();
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode();
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -751,23 +841,31 @@ void main() {
expect(scope2.focusedChild, equals(child4));
});
testWidgets('Unfocus with disposition previouslyFocusedChild works properly', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Unfocus with disposition previouslyFocusedChild works properly', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1')..attach(context);
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'child3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'child4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -832,23 +930,31 @@ void main() {
expect(child3.hasPrimaryFocus, isTrue);
});
testWidgets('Unfocus with disposition scope works properly', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Unfocus with disposition scope works properly', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1')..attach(context);
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'child3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'child4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -917,23 +1023,31 @@ void main() {
expect(FocusManager.instance.rootScope.hasPrimaryFocus, isTrue);
});
testWidgets('Unfocus works properly when some nodes are unfocusable', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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);
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'child3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'child4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -983,23 +1097,31 @@ void main() {
expect(child2.hasPrimaryFocus, isFalse);
});
testWidgets('Requesting focus on a scope works properly when some focusedChild nodes are unfocusable', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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);
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'child3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'child4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -1037,7 +1159,7 @@ void main() {
expect(child4.hasPrimaryFocus, isTrue);
});
testWidgets('Key handling bubbles up and terminates when handled.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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) {
......@@ -1055,20 +1177,28 @@ void main() {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'Scope 1');
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context, onKey: handleEvent);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'Scope 2');
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context, onKey: handleEvent);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1', onKey: handleEvent);
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2', onKey: handleEvent);
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context, onKey: handleEvent);
final FocusNode child2 = FocusNode(debugLabel: 'Child 2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context, onKey: handleEvent);
final FocusNode child3 = FocusNode(debugLabel: 'Child 3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context, onKey: handleEvent);
final FocusNode child4 = FocusNode(debugLabel: 'Child 4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context, onKey: handleEvent);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -1101,7 +1231,7 @@ void main() {
expect(receivedAnEvent, isEmpty);
}, variant: KeySimulatorTransitModeVariant.all());
testWidgets('Initial highlight mode guesses correctly.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Initial highlight mode guesses correctly.', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic;
switch (defaultTargetPlatform) {
case TargetPlatform.fuchsia:
......@@ -1115,7 +1245,7 @@ void main() {
}
}, variant: TargetPlatformVariant.all());
testWidgets('Mouse events change initial focus highlight mode on mobile.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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);
......@@ -1123,7 +1253,7 @@ void main() {
expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional));
}, variant: TargetPlatformVariant.mobile());
testWidgets('Mouse events change initial focus highlight mode on desktop.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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);
......@@ -1131,12 +1261,12 @@ void main() {
expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.traditional));
}, variant: TargetPlatformVariant.desktop());
testWidgets('Keyboard events change initial focus highlight mode.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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 {
testWidgetsWithLeakTracking('Events change focus highlight mode.', (WidgetTester tester) async {
await setupWidget(tester);
int callCount = 0;
FocusHighlightMode? lastMode;
......@@ -1177,11 +1307,11 @@ void main() {
expect(FocusManager.instance.highlightMode, equals(FocusHighlightMode.touch));
});
testWidgets('implements debugFillProperties', (WidgetTester tester) async {
testWidgetsWithLeakTracking('implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
FocusScopeNode(
debugLabel: 'Scope Label',
).debugFillProperties(builder);
final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope Label');
addTearDown(scope.dispose);
scope.debugFillProperties(builder);
final List<String> description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList();
expect(description, <String>[
'context: null',
......@@ -1193,23 +1323,31 @@ void main() {
]);
});
testWidgets('debugDescribeFocusTree produces correct output', (WidgetTester tester) async {
testWidgetsWithLeakTracking('debugDescribeFocusTree produces correct output', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'Scope 1');
addTearDown(scope1.dispose);
final FocusAttachment scope1Attachment = scope1.attach(context);
final FocusScopeNode scope2 = FocusScopeNode(); // No label, Just to test that it works.
addTearDown(scope2.dispose);
final FocusAttachment scope2Attachment = scope2.attach(context);
final FocusNode parent1 = FocusNode(debugLabel: 'Parent 1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode parent2 = FocusNode(debugLabel: 'Parent 2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'Child 1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(); // No label, Just to test that it works.
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
final FocusNode child3 = FocusNode(debugLabel: 'Child 3');
addTearDown(child3.dispose);
final FocusAttachment child3Attachment = child3.attach(context);
final FocusNode child4 = FocusNode(debugLabel: 'Child 4');
addTearDown(child4.dispose);
final FocusAttachment child4Attachment = child4.attach(context);
scope1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
scope2Attachment.reparent(parent: tester.binding.focusManager.rootScope);
......@@ -1269,11 +1407,13 @@ void main() {
});
group('Autofocus', () {
testWidgets(
testWidgetsWithLeakTracking(
'works when the previous focused node is detached',
(WidgetTester tester) async {
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode();
addTearDown(node2.dispose);
await tester.pumpWidget(
FocusScope(
......@@ -1294,11 +1434,13 @@ void main() {
expect(node2.hasPrimaryFocus, isTrue);
});
testWidgets(
testWidgetsWithLeakTracking(
'node detached before autofocus is applied',
(WidgetTester tester) async {
final FocusScopeNode scopeNode = FocusScopeNode();
addTearDown(scopeNode.dispose);
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
await tester.pumpWidget(
FocusScope(
......@@ -1322,10 +1464,13 @@ void main() {
expect(scopeNode.hasPrimaryFocus, isTrue);
});
testWidgets('autofocus the first candidate', (WidgetTester tester) async {
testWidgetsWithLeakTracking('autofocus the first candidate', (WidgetTester tester) async {
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode();
addTearDown(node2.dispose);
final FocusNode node3 = FocusNode();
addTearDown(node3.dispose);
await tester.pumpWidget(
Directionality(
......@@ -1355,10 +1500,13 @@ void main() {
expect(node1.hasPrimaryFocus, isTrue);
});
testWidgets('Autofocus works with global key reparenting', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Autofocus works with global key reparenting', (WidgetTester tester) async {
final FocusNode node = FocusNode();
addTearDown(node.dispose);
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1');
addTearDown(scope1.dispose);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
......@@ -1409,15 +1557,19 @@ void main() {
});
});
testWidgets("Doesn't lose focused child when reparenting if the nearestScope doesn't change.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("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');
addTearDown(parent1.dispose);
final FocusScopeNode parent2 = FocusScopeNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
child1Attachment.reparent(parent: parent1);
......@@ -1435,7 +1587,7 @@ void main() {
expect(parent1.focusedChild, equals(child2));
});
testWidgets('Ancestors get notified exactly as often as needed if focused child changes focus.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('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;
......@@ -1460,14 +1612,19 @@ void main() {
}
final BuildContext context = await setupWidget(tester);
final FocusScopeNode top = FocusScopeNode(debugLabel: 'top');
addTearDown(top.dispose);
final FocusAttachment topAttachment = top.attach(context);
final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusScopeNode parent2 = FocusScopeNode(debugLabel: 'parent2');
addTearDown(parent2.dispose);
final FocusAttachment parent2Attachment = parent2.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
topAttachment.reparent(parent: tester.binding.focusManager.rootScope);
parent1Attachment.reparent(parent: top);
......@@ -1566,13 +1723,16 @@ void main() {
expect(child2Notify, equals(0));
});
testWidgets('Focus changes notify listeners.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus changes notify listeners.', (WidgetTester tester) async {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
final FocusNode child2 = FocusNode(debugLabel: 'child2');
addTearDown(child2.dispose);
final FocusAttachment child2Attachment = child2.attach(context);
parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
child1Attachment.reparent(parent: parent1);
......@@ -1618,9 +1778,12 @@ void main() {
expect(()=> FocusNode().dispose(), dispatchesMemoryEvents(FocusNode));
});
testWidgets('FocusManager notifies listeners when a widget loses focus because it was removed.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('FocusManager notifies listeners when a widget loses focus because it was removed.', (WidgetTester tester) async {
final FocusNode nodeA = FocusNode(debugLabel: 'a');
addTearDown(nodeA.dispose);
final FocusNode nodeB = FocusNode(debugLabel: 'b');
addTearDown(nodeB.dispose);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
......@@ -1664,7 +1827,7 @@ void main() {
tester.binding.focusManager.removeListener(handleFocusChange);
});
testWidgets('debugFocusChanges causes logging of focus changes', (WidgetTester tester) async {
testWidgetsWithLeakTracking('debugFocusChanges causes logging of focus changes', (WidgetTester tester) async {
final bool oldDebugFocusChanges = debugFocusChanges;
final DebugPrintCallback oldDebugPrint = debugPrint;
final StringBuffer messages = StringBuffer();
......@@ -1675,8 +1838,10 @@ void main() {
try {
final BuildContext context = await setupWidget(tester);
final FocusScopeNode parent1 = FocusScopeNode(debugLabel: 'parent1');
addTearDown(parent1.dispose);
final FocusAttachment parent1Attachment = parent1.attach(context);
final FocusNode child1 = FocusNode(debugLabel: 'child1');
addTearDown(child1.dispose);
final FocusAttachment child1Attachment = child1.attach(context);
parent1Attachment.reparent(parent: tester.binding.focusManager.rootScope);
child1Attachment.reparent(parent: parent1);
......@@ -1709,7 +1874,7 @@ void main() {
expect(messagesStr, contains(RegExp(r'FOCUS: Scheduling update, current focus is null, next focus will be FocusScopeNode#.*parent1')));
});
testWidgets("doesn't call toString on a focus node when debugFocusChanges is false", (WidgetTester tester) async {
testWidgetsWithLeakTracking("doesn't call toString on a focus node when debugFocusChanges is false", (WidgetTester tester) async {
final bool oldDebugFocusChanges = debugFocusChanges;
final DebugPrintCallback oldDebugPrint = debugPrint;
final StringBuffer messages = StringBuffer();
......
......@@ -6,12 +6,13 @@ import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import 'semantics_tester.dart';
void main() {
group('FocusScope', () {
testWidgets('Can focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can focus', (WidgetTester tester) async {
final GlobalKey<TestFocusState> key = GlobalKey();
await tester.pumpWidget(
......@@ -27,7 +28,7 @@ void main() {
expect(find.text('A FOCUSED'), findsOneWidget);
});
testWidgets('Can unfocus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can unfocus', (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
await tester.pumpWidget(
......@@ -62,7 +63,7 @@ void main() {
expect(find.text('B FOCUSED'), findsOneWidget);
});
testWidgets('Autofocus works', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Autofocus works', (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
await tester.pumpWidget(
......@@ -82,7 +83,7 @@ void main() {
expect(find.text('B FOCUSED'), findsOneWidget);
});
testWidgets('Can have multiple focused children and they update accordingly', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can have multiple focused children and they update accordingly', (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
......@@ -129,9 +130,11 @@ void main() {
// This moves a focus node first into a focus scope that is added to its
// parent, and then out of that focus scope again.
testWidgets('Can move focus in and out of FocusScope', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can move focus in and out of FocusScope', (WidgetTester tester) async {
final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node');
addTearDown(parentFocusScope.dispose);
final FocusScopeNode childFocusScope = FocusScopeNode(debugLabel: 'Child Scope Node');
addTearDown(childFocusScope.dispose);
final GlobalKey<TestFocusState> key = GlobalKey();
// Initially create the focus inside of the parent FocusScope.
......@@ -274,10 +277,13 @@ void main() {
childAttachment.detach();
});
testWidgets('Setting first focus requests focus for the scope properly.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Setting first focus requests focus for the scope properly.', (WidgetTester tester) async {
final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node');
addTearDown(parentFocusScope.dispose);
final FocusScopeNode childFocusScope1 = FocusScopeNode(debugLabel: 'Child Scope Node 1');
addTearDown(childFocusScope1.dispose);
final FocusScopeNode childFocusScope2 = FocusScopeNode(debugLabel: 'Child Scope Node 2');
addTearDown(childFocusScope2.dispose);
final GlobalKey<TestFocusState> keyA = GlobalKey(debugLabel: 'Key A');
final GlobalKey<TestFocusState> keyB = GlobalKey(debugLabel: 'Key B');
final GlobalKey<TestFocusState> keyC = GlobalKey(debugLabel: 'Key C');
......@@ -376,7 +382,7 @@ void main() {
expect(childFocusScope2.isFirstFocus, isFalse);
});
testWidgets('Removing focused widget moves focus to next widget', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Removing focused widget moves focus to next widget', (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
......@@ -420,10 +426,12 @@ void main() {
expect(find.text('b'), findsOneWidget);
});
testWidgets('Adding a new FocusScope attaches the child to its parent.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Adding a new FocusScope attaches the child to its parent.', (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope Node');
addTearDown(parentFocusScope.dispose);
final FocusScopeNode childFocusScope = FocusScopeNode(debugLabel: 'Child Scope Node');
addTearDown(childFocusScope.dispose);
await tester.pumpWidget(
FocusScope(
......@@ -466,11 +474,15 @@ void main() {
expect(find.text('A FOCUSED'), findsOneWidget);
});
testWidgets('Setting parentNode determines focus tree hierarchy.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Setting parentNode determines focus tree hierarchy.', (WidgetTester tester) async {
final FocusNode topNode = FocusNode(debugLabel: 'Top');
addTearDown(topNode.dispose);
final FocusNode parentNode = FocusNode(debugLabel: 'Parent');
addTearDown(parentNode.dispose);
final FocusNode childNode = FocusNode(debugLabel: 'Child');
addTearDown(childNode.dispose);
final FocusNode insertedNode = FocusNode(debugLabel: 'Inserted');
addTearDown(insertedNode.dispose);
await tester.pumpWidget(
FocusScope(
......@@ -532,11 +544,15 @@ void main() {
expect(insertedNode.hasFocus, isFalse);
});
testWidgets('Setting parentNode determines focus scope tree hierarchy.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Setting parentNode determines focus scope tree hierarchy.', (WidgetTester tester) async {
final FocusScopeNode topNode = FocusScopeNode(debugLabel: 'Top');
addTearDown(topNode.dispose);
final FocusScopeNode parentNode = FocusScopeNode(debugLabel: 'Parent');
addTearDown(parentNode.dispose);
final FocusScopeNode childNode = FocusScopeNode(debugLabel: 'Child');
addTearDown(childNode.dispose);
final FocusScopeNode insertedNode = FocusScopeNode(debugLabel: 'Inserted');
addTearDown(insertedNode.dispose);
await tester.pumpWidget(
FocusScope.withExternalFocusNode(
......@@ -599,10 +615,11 @@ void main() {
});
// Arguably, this isn't correct behavior, but it is what happens now.
testWidgets("Removing focused widget doesn't move focus to next widget within FocusScope", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Removing focused widget doesn't move focus to next widget within FocusScope", (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope');
addTearDown(parentFocusScope.dispose);
await tester.pumpWidget(
FocusScope(
......@@ -656,12 +673,13 @@ void main() {
expect(find.text('b'), findsOneWidget);
});
testWidgets('Removing a FocusScope removes its node from the tree', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Removing a FocusScope removes its node from the tree', (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
final GlobalKey<TestFocusState> scopeKeyA = GlobalKey();
final GlobalKey<TestFocusState> scopeKeyB = GlobalKey();
final FocusScopeNode parentFocusScope = FocusScopeNode(debugLabel: 'Parent Scope');
addTearDown(parentFocusScope.dispose);
// This checks both FocusScopes that have their own nodes, as well as those
// that use external nodes.
......@@ -719,13 +737,15 @@ void main() {
});
// By "pinned", it means kept in the tree by a GlobalKey.
testWidgets("Removing pinned focused scope doesn't move focus to focused widget within next FocusScope", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Removing pinned focused scope doesn't move focus to focused widget within next FocusScope", (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
final GlobalKey<TestFocusState> scopeKeyA = GlobalKey();
final GlobalKey<TestFocusState> scopeKeyB = GlobalKey();
final FocusScopeNode parentFocusScope1 = FocusScopeNode(debugLabel: 'Parent Scope 1');
addTearDown(parentFocusScope1.dispose);
final FocusScopeNode parentFocusScope2 = FocusScopeNode(debugLabel: 'Parent Scope 2');
addTearDown(parentFocusScope2.dispose);
await tester.pumpWidget(
FocusTraversalGroup(
......@@ -805,11 +825,13 @@ void main() {
expect(find.text('B FOCUSED'), findsOneWidget);
});
testWidgets("Removing unpinned focused scope doesn't move focus to focused widget within next FocusScope", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Removing unpinned focused scope doesn't move focus to focused widget within next FocusScope", (WidgetTester tester) async {
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
final FocusScopeNode parentFocusScope1 = FocusScopeNode(debugLabel: 'Parent Scope 1');
addTearDown(parentFocusScope1.dispose);
final FocusScopeNode parentFocusScope2 = FocusScopeNode(debugLabel: 'Parent Scope 2');
addTearDown(parentFocusScope2.dispose);
await tester.pumpWidget(
FocusTraversalGroup(
......@@ -885,9 +907,11 @@ void main() {
expect(find.text('B FOCUSED'), findsOneWidget);
});
testWidgets('Moving widget from one scope to another retains focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Moving widget from one scope to another retains focus', (WidgetTester tester) async {
final FocusScopeNode parentFocusScope1 = FocusScopeNode();
addTearDown(parentFocusScope1.dispose);
final FocusScopeNode parentFocusScope2 = FocusScopeNode();
addTearDown(parentFocusScope2.dispose);
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
......@@ -966,9 +990,11 @@ void main() {
expect(find.text('b'), findsOneWidget);
});
testWidgets('Moving FocusScopeNodes retains focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Moving FocusScopeNodes retains focus', (WidgetTester tester) async {
final FocusScopeNode parentFocusScope1 = FocusScopeNode(debugLabel: 'Scope 1');
addTearDown(parentFocusScope1.dispose);
final FocusScopeNode parentFocusScope2 = FocusScopeNode(debugLabel: 'Scope 2');
addTearDown(parentFocusScope2.dispose);
final GlobalKey<TestFocusState> keyA = GlobalKey();
final GlobalKey<TestFocusState> keyB = GlobalKey();
......@@ -1052,7 +1078,7 @@ void main() {
expect(find.text('b'), findsOneWidget);
});
testWidgets('Can focus root node.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can focus root node.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
await tester.pumpWidget(
Focus(
......@@ -1071,8 +1097,9 @@ void main() {
expect(rootNode, equals(firstElement.owner!.focusManager.rootScope));
});
testWidgets('Can autofocus a node.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can autofocus a node.', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
addTearDown(focusNode.dispose);
await tester.pumpWidget(
Focus(
focusNode: focusNode,
......@@ -1095,9 +1122,11 @@ void main() {
expect(focusNode.hasPrimaryFocus, isTrue);
});
testWidgets("Won't autofocus a node if one is already focused.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Won't autofocus a node if one is already focused.", (WidgetTester tester) async {
final FocusNode focusNodeA = FocusNode(debugLabel: 'Test Node A');
addTearDown(focusNodeA.dispose);
final FocusNode focusNodeB = FocusNode(debugLabel: 'Test Node B');
addTearDown(focusNodeB.dispose);
await tester.pumpWidget(
Column(
children: <Widget>[
......@@ -1134,9 +1163,10 @@ void main() {
expect(focusNodeA.hasPrimaryFocus, isTrue);
});
testWidgets("FocusScope doesn't update the focusNode attributes when the widget updates if withExternalFocusNode is used", (WidgetTester tester) async {
testWidgetsWithLeakTracking("FocusScope doesn't update the focusNode attributes when the widget updates if withExternalFocusNode is used", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusScopeNode focusScopeNode = FocusScopeNode();
addTearDown(focusScopeNode.dispose);
bool? keyEventHandled;
KeyEventResult handleCallback(FocusNode node, RawKeyEvent event) {
keyEventHandled = true;
......@@ -1205,7 +1235,7 @@ void main() {
});
group('Focus', () {
testWidgets('Focus.of stops at the nearest Focus widget.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus.of stops at the nearest Focus widget.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -1213,6 +1243,7 @@ void main() {
final GlobalKey key5 = GlobalKey(debugLabel: '5');
final GlobalKey key6 = GlobalKey(debugLabel: '6');
final FocusScopeNode scopeNode = FocusScopeNode();
addTearDown(scopeNode.dispose);
await tester.pumpWidget(
FocusScope(
key: key1,
......@@ -1252,7 +1283,7 @@ void main() {
expect(Focus.of(element5).parent!.parent, equals(root));
expect(Focus.of(element6).parent!.parent!.parent, equals(root));
});
testWidgets('Can traverse Focus children.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can traverse Focus children.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -1326,7 +1357,7 @@ void main() {
expect(keys, equals(<Key>[key7, key8]));
});
testWidgets('Can set focus.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can set focus.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
late bool gotFocus;
await tester.pumpWidget(
......@@ -1346,7 +1377,7 @@ void main() {
expect(node.hasFocus, isTrue);
});
testWidgets('Focus is ignored when set to not focusable.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus is ignored when set to not focusable.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
bool? gotFocus;
await tester.pumpWidget(
......@@ -1367,7 +1398,7 @@ void main() {
expect(node.hasFocus, isFalse);
});
testWidgets('Focus is lost when set to not focusable.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus is lost when set to not focusable.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
bool? gotFocus;
await tester.pumpWidget(
......@@ -1407,10 +1438,11 @@ void main() {
expect(node.hasFocus, isFalse);
});
testWidgets('Child of unfocusable Focus can get focus.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Child of unfocusable Focus can get focus.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? gotFocus;
await tester.pumpWidget(
Focus(
......@@ -1439,7 +1471,7 @@ void main() {
expect(unfocusableNode.hasFocus, isTrue);
});
testWidgets('Nodes are removed when all Focuses are removed.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Nodes are removed when all Focuses are removed.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
late bool gotFocus;
await tester.pumpWidget(
......@@ -1465,7 +1497,7 @@ void main() {
expect(FocusManager.instance.rootScope.descendants, isEmpty);
});
testWidgets('Focus widgets set Semantics information about focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus widgets set Semantics information about focus', (WidgetTester tester) async {
final GlobalKey<TestFocusState> key = GlobalKey();
await tester.pumpWidget(
......@@ -1494,7 +1526,7 @@ void main() {
expect(semantics.hasFlag(SemanticsFlag.isFocusable), isFalse);
});
testWidgets('Setting canRequestFocus on focus node causes update.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Setting canRequestFocus on focus node causes update.', (WidgetTester tester) async {
final GlobalKey<TestFocusState> key = GlobalKey();
final TestFocus testFocus = TestFocus(key: key);
......@@ -1511,7 +1543,7 @@ void main() {
expect(key.currentState!.focusNode.canRequestFocus, isFalse);
});
testWidgets('canRequestFocus causes descendants of scope to be skipped.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('canRequestFocus causes descendants of scope to be skipped.', (WidgetTester tester) async {
final GlobalKey scope1 = GlobalKey(debugLabel: 'scope1');
final GlobalKey scope2 = GlobalKey(debugLabel: 'scope2');
final GlobalKey focus1 = GlobalKey(debugLabel: 'focus1');
......@@ -1620,11 +1652,15 @@ void main() {
expect(Focus.of(container1.currentContext!).hasFocus, isTrue);
});
testWidgets('skipTraversal works as expected.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('skipTraversal works as expected.', (WidgetTester tester) async {
final FocusScopeNode scope1 = FocusScopeNode(debugLabel: 'scope1');
addTearDown(scope1.dispose);
final FocusScopeNode scope2 = FocusScopeNode(debugLabel: 'scope2');
addTearDown(scope2.dispose);
final FocusNode focus1 = FocusNode(debugLabel: 'focus1');
addTearDown(focus1.dispose);
final FocusNode focus2 = FocusNode(debugLabel: 'focus2');
addTearDown(focus2.dispose);
Future<void> pumpTest({
bool traverseScope1 = false,
......@@ -1674,10 +1710,11 @@ void main() {
expect(scope1.traversalDescendants, equals(<FocusNode>[focus2, focus1, scope2]));
});
testWidgets('descendantsAreFocusable works as expected.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('descendantsAreFocusable works as expected.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? gotFocus;
await tester.pumpWidget(
Focus(
......@@ -1713,11 +1750,15 @@ void main() {
expect(unfocusableNode.hasFocus, isFalse);
});
testWidgets('descendantsAreTraversable works as expected.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('descendantsAreTraversable works as expected.', (WidgetTester tester) async {
final FocusScopeNode scopeNode = FocusScopeNode(debugLabel: 'scope');
addTearDown(scopeNode.dispose);
final FocusNode node1 = FocusNode(debugLabel: 'node 1');
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode(debugLabel: 'node 2');
addTearDown(node2.dispose);
final FocusNode node3 = FocusNode(debugLabel: 'node 3');
addTearDown(node3.dispose);
await tester.pumpWidget(
FocusScope(
......@@ -1746,7 +1787,7 @@ void main() {
expect(node2.traversalDescendants, equals(<FocusNode>[]));
});
testWidgets("Focus doesn't introduce a Semantics node when includeSemantics is false", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Focus doesn't introduce a Semantics node when includeSemantics is false", (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(Focus(includeSemantics: false, child: Container()));
final TestSemantics expectedSemantics = TestSemantics.root();
......@@ -1754,9 +1795,10 @@ void main() {
semantics.dispose();
});
testWidgets('Focus updates the onKey handler when the widget updates', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus updates the onKey handler when the widget updates', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? keyEventHandled;
KeyEventResult handleCallback(FocusNode node, RawKeyEvent event) {
keyEventHandled = true;
......@@ -1803,9 +1845,10 @@ void main() {
expect(keyEventHandled, isTrue);
});
testWidgets('Focus updates the onKeyEvent handler when the widget updates', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus updates the onKeyEvent handler when the widget updates', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? keyEventHandled;
KeyEventResult handleEventCallback(FocusNode node, KeyEvent event) {
keyEventHandled = true;
......@@ -1852,9 +1895,10 @@ void main() {
expect(keyEventHandled, isTrue);
});
testWidgets("Focus doesn't update the focusNode attributes when the widget updates if withExternalFocusNode is used", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Focus doesn't update the focusNode attributes when the widget updates if withExternalFocusNode is used", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? keyEventHandled;
KeyEventResult handleCallback(FocusNode node, RawKeyEvent event) {
keyEventHandled = true;
......@@ -1921,7 +1965,7 @@ void main() {
expect(keyEventHandled, isTrue);
});
testWidgets('Focus passes changes in attribute values to its focus node', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus passes changes in attribute values to its focus node', (WidgetTester tester) async {
await tester.pumpWidget(
Focus(
child: Container(),
......@@ -1931,10 +1975,11 @@ void main() {
});
group('ExcludeFocus', () {
testWidgets("Descendants of ExcludeFocus aren't focusable.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Descendants of ExcludeFocus aren't focusable.", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? gotFocus;
await tester.pumpWidget(
ExcludeFocus(
......@@ -1970,10 +2015,13 @@ void main() {
});
// Regression test for https://github.com/flutter/flutter/issues/61700
testWidgets("ExcludeFocus doesn't transfer focus to another descendant.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("ExcludeFocus doesn't transfer focus to another descendant.", (WidgetTester tester) async {
final FocusNode parentFocusNode = FocusNode(debugLabel: 'group');
addTearDown(parentFocusNode.dispose);
final FocusNode focusNode1 = FocusNode(debugLabel: 'node 1');
addTearDown(focusNode1.dispose);
final FocusNode focusNode2 = FocusNode(debugLabel: 'node 2');
addTearDown(focusNode2.dispose);
await tester.pumpWidget(
ExcludeFocus(
excluding: false,
......@@ -2039,7 +2087,7 @@ void main() {
expect(parentFocusNode.enclosingScope!.hasPrimaryFocus, isTrue);
});
testWidgets("ExcludeFocus doesn't introduce a Semantics node", (WidgetTester tester) async {
testWidgetsWithLeakTracking("ExcludeFocus doesn't introduce a Semantics node", (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(ExcludeFocus(child: Container()));
final TestSemantics expectedSemantics = TestSemantics.root();
......@@ -2048,8 +2096,9 @@ void main() {
});
// Regression test for https://github.com/flutter/flutter/issues/92693
testWidgets('Setting parent FocusScope.canRequestFocus to false, does not set descendant Focus._internalNode._canRequestFocus to false', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Setting parent FocusScope.canRequestFocus to false, does not set descendant Focus._internalNode._canRequestFocus to false', (WidgetTester tester) async {
final FocusNode childFocusNode = FocusNode(debugLabel: 'node 1');
addTearDown(childFocusNode.dispose);
Widget buildFocusTree({required bool parentCanRequestFocus}) {
return FocusScope(
......
......@@ -8,12 +8,13 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import 'semantics_tester.dart';
void main() {
group(WidgetOrderTraversalPolicy, () {
testWidgets('Find the initial focus if there is none yet.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Find the initial focus if there is none yet.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -52,7 +53,7 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Find the initial focus if there is none yet and traversing backwards.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Find the initial focus if there is none yet and traversing backwards.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -95,7 +96,7 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Move focus to next node.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move focus to next node.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -212,7 +213,7 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Move focus to previous node.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move focus to previous node.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -286,9 +287,15 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Move focus to next/previous node while skipping nodes in policy', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move focus to next/previous node while skipping nodes in policy', (WidgetTester tester) async {
final List<FocusNode> nodes =
List<FocusNode>.generate(7, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
await tester.pumpWidget(
FocusTraversalGroup(
policy: SkipAllButFirstAndLastPolicy(),
......@@ -320,11 +327,14 @@ void main() {
expect(nodes[0].hasPrimaryFocus, isTrue);
});
testWidgets('Find the initial focus when a route is pushed or popped.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Find the initial focus when a route is pushed or popped.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode testNode1 = FocusNode(debugLabel: 'First Focus Node');
addTearDown(testNode1.dispose);
final FocusNode testNode2 = FocusNode(debugLabel: 'Second Focus Node');
addTearDown(testNode2.dispose);
await tester.pumpWidget(
MaterialApp(
home: FocusTraversalGroup(
......@@ -386,9 +396,10 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Custom requestFocusCallback gets called on the next/previous focus.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Custom requestFocusCallback gets called on the next/previous focus.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode testNode1 = FocusNode(debugLabel: 'Focus Node');
addTearDown(testNode1.dispose);
bool calledCallback = false;
await tester.pumpWidget(
......@@ -430,10 +441,14 @@ void main() {
});
testWidgets('Nested navigator does not trap focus', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Nested navigator does not trap focus', (WidgetTester tester) async {
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode();
addTearDown(node2.dispose);
final FocusNode node3 = FocusNode();
addTearDown(node3.dispose);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
......@@ -517,7 +532,7 @@ void main() {
});
group(ReadingOrderTraversalPolicy, () {
testWidgets('Find the initial focus if there is none yet.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Find the initial focus if there is none yet.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -556,7 +571,7 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Move reading focus to next node.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move reading focus to next node.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -671,7 +686,7 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Move reading focus to previous node.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move reading focus to previous node.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
......@@ -745,10 +760,17 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Focus order is correct in the presence of different directionalities.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus order is correct in the presence of different directionalities.', (WidgetTester tester) async {
const int nodeCount = 10;
final FocusScopeNode scopeNode = FocusScopeNode();
addTearDown(scopeNode.dispose);
final List<FocusNode> nodes = List<FocusNode>.generate(nodeCount, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
Widget buildTest(TextDirection topDirection) {
return Directionality(
textDirection: topDirection,
......@@ -860,9 +882,15 @@ void main() {
expect(order, orderedEquals(<int>[0, 1, 2, 4, 3, 5, 6, 8, 7, 9]));
});
testWidgets('Focus order is reading order regardless of widget order, even when overlapping.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus order is reading order regardless of widget order, even when overlapping.', (WidgetTester tester) async {
const int nodeCount = 10;
final List<FocusNode> nodes = List<FocusNode>.generate(nodeCount, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
......@@ -954,9 +982,10 @@ void main() {
expect(order, orderedEquals(<int>[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]));
});
testWidgets('Custom requestFocusCallback gets called on the next/previous focus.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Custom requestFocusCallback gets called on the next/previous focus.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode testNode1 = FocusNode(debugLabel: 'Focus Node');
addTearDown(testNode1.dispose);
bool calledCallback = false;
await tester.pumpWidget(
......@@ -1001,7 +1030,7 @@ void main() {
});
group(OrderedTraversalPolicy, () {
testWidgets('Find the initial focus if there is none yet.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Find the initial focus if there is none yet.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
await tester.pumpWidget(FocusTraversalGroup(
......@@ -1040,9 +1069,15 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Fall back to the secondary sort if no FocusTraversalOrder exists.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Fall back to the secondary sort if no FocusTraversalOrder exists.', (WidgetTester tester) async {
const int nodeCount = 10;
final List<FocusNode> nodes = List<FocusNode>.generate(nodeCount, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
......@@ -1079,9 +1114,15 @@ void main() {
}
});
testWidgets('Move focus to next/previous node using numerical order.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move focus to next/previous node using numerical order.', (WidgetTester tester) async {
const int nodeCount = 10;
final List<FocusNode> nodes = List<FocusNode>.generate(nodeCount, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
......@@ -1120,12 +1161,18 @@ void main() {
}
});
testWidgets('Move focus to next/previous node using lexical order.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move focus to next/previous node using lexical order.', (WidgetTester tester) async {
const int nodeCount = 10;
/// Generate ['J' ... 'A'];
final List<String> keys = List<String>.generate(nodeCount, (int index) => String.fromCharCode('A'.codeUnits[0] + nodeCount - index - 1));
final List<FocusNode> nodes = List<FocusNode>.generate(nodeCount, (int index) => FocusNode(debugLabel: 'Node ${keys[index]}'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
......@@ -1164,10 +1211,17 @@ void main() {
}
});
testWidgets('Focus order is correct in the presence of FocusTraversalPolicyGroups.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus order is correct in the presence of FocusTraversalPolicyGroups.', (WidgetTester tester) async {
const int nodeCount = 10;
final FocusScopeNode scopeNode = FocusScopeNode();
addTearDown(scopeNode.dispose);
final List<FocusNode> nodes = List<FocusNode>.generate(nodeCount, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
......@@ -1291,11 +1345,14 @@ void main() {
expect(order, orderedEquals(expectedOrder));
});
testWidgets('Find the initial focus when a route is pushed or popped.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Find the initial focus when a route is pushed or popped.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode testNode1 = FocusNode(debugLabel: 'First Focus Node');
addTearDown(testNode1.dispose);
final FocusNode testNode2 = FocusNode(debugLabel: 'Second Focus Node');
addTearDown(testNode2.dispose);
await tester.pumpWidget(
MaterialApp(
home: FocusTraversalGroup(
......@@ -1363,9 +1420,10 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Custom requestFocusCallback gets called on the next/previous focus.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Custom requestFocusCallback gets called on the next/previous focus.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode testNode1 = FocusNode(debugLabel: 'Focus Node');
addTearDown(testNode1.dispose);
bool calledCallback = false;
await tester.pumpWidget(
......@@ -1410,7 +1468,7 @@ void main() {
});
group(DirectionalFocusTraversalPolicyMixin, () {
testWidgets('Move focus in all directions.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Move focus in all directions.', (WidgetTester tester) async {
final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey');
final GlobalKey upperRightKey = GlobalKey(debugLabel: 'upperRightKey');
final GlobalKey lowerLeftKey = GlobalKey(debugLabel: 'lowerLeftKey');
......@@ -1550,9 +1608,15 @@ void main() {
expect(scope.hasFocus, isTrue);
});
testWidgets('Directional focus avoids hysteresis.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Directional focus avoids hysteresis.', (WidgetTester tester) async {
List<bool?> focus = List<bool?>.generate(6, (int _) => null);
final List<FocusNode> nodes = List<FocusNode>.generate(6, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
Focus makeFocus(int index) {
return Focus(
debugLabel: '[$index]',
......@@ -1668,11 +1732,16 @@ void main() {
clear();
});
testWidgets('Directional prefers the closest node even on irregular grids', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Directional prefers the closest node even on irregular grids', (WidgetTester tester) async {
const int cols = 3;
const int rows = 3;
List<bool?> focus = List<bool?>.generate(rows * cols, (int _) => null);
final List<FocusNode> nodes = List<FocusNode>.generate(rows * cols, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
Widget makeFocus(int row, int col) {
final int index = row * rows + col;
......@@ -1804,10 +1873,15 @@ void main() {
clear();
});
testWidgets('Closest vertical is picked when only out of band items are considered', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Closest vertical is picked when only out of band items are considered', (WidgetTester tester) async {
const int rows = 4;
List<bool?> focus = List<bool?>.generate(rows, (int _) => null);
final List<FocusNode> nodes = List<FocusNode>.generate(rows, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
Widget makeFocus(int row) {
return Padding(
......@@ -1890,10 +1964,15 @@ void main() {
clear();
});
testWidgets('Closest horizontal is picked when only out of band items are considered', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Closest horizontal is picked when only out of band items are considered', (WidgetTester tester) async {
const int cols = 4;
List<bool?> focus = List<bool?>.generate(cols, (int _) => null);
final List<FocusNode> nodes = List<FocusNode>.generate(cols, (int index) => FocusNode(debugLabel: 'Node $index'));
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
Widget makeFocus(int col) {
return Padding(
......@@ -1976,7 +2055,7 @@ void main() {
clear();
});
testWidgets('Can find first focus in all directions.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can find first focus in all directions.', (WidgetTester tester) async {
final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey');
final GlobalKey upperRightKey = GlobalKey(debugLabel: 'upperRightKey');
final GlobalKey lowerLeftKey = GlobalKey(debugLabel: 'lowerLeftKey');
......@@ -2036,10 +2115,13 @@ void main() {
expect(policy.findFirstFocusInDirection(scope, TraversalDirection.right), equals(upperLeftNode));
});
testWidgets('Can find focus when policy data dirty', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Can find focus when policy data dirty', (WidgetTester tester) async {
final FocusNode focusTop = FocusNode(debugLabel: 'top');
addTearDown(focusTop.dispose);
final FocusNode focusCenter = FocusNode(debugLabel: 'center');
addTearDown(focusCenter.dispose);
final FocusNode focusBottom = FocusNode(debugLabel: 'bottom');
addTearDown(focusBottom.dispose);
final FocusTraversalPolicy policy = ReadingOrderTraversalPolicy();
await tester.pumpWidget(FocusTraversalGroup(
......@@ -2087,7 +2169,7 @@ void main() {
expect(focusTop.hasFocus, isTrue);
});
testWidgets('Focus traversal actions are invoked when shortcuts are used.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal actions are invoked when shortcuts are used.', (WidgetTester tester) async {
final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey');
final GlobalKey upperRightKey = GlobalKey(debugLabel: 'upperRightKey');
final GlobalKey lowerLeftKey = GlobalKey(debugLabel: 'lowerLeftKey');
......@@ -2176,7 +2258,7 @@ void main() {
expect(Focus.of(upperLeftKey.currentContext!).hasPrimaryFocus, isTrue);
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
testWidgets('Focus traversal actions works when current focus skip traversal', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal actions works when current focus skip traversal', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: 'key1');
final GlobalKey key2 = GlobalKey(debugLabel: 'key2');
final GlobalKey key3 = GlobalKey(debugLabel: 'key3');
......@@ -2231,12 +2313,21 @@ void main() {
expect(Focus.of(key3.currentContext!).hasPrimaryFocus, isTrue);
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
testWidgets('Focus traversal inside a vertical scrollable scrolls to stay visible.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal inside a vertical scrollable scrolls to stay visible.', (WidgetTester tester) async {
final List<int> items = List<int>.generate(11, (int index) => index).toList();
final List<FocusNode> nodes = List<FocusNode>.generate(11, (int index) => FocusNode(debugLabel: 'Item ${index + 1}')).toList();
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
final FocusNode topNode = FocusNode(debugLabel: 'Header');
addTearDown(topNode.dispose);
final FocusNode bottomNode = FocusNode(debugLabel: 'Footer');
addTearDown(bottomNode.dispose);
final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
await tester.pumpWidget(
MaterialApp(
home: Column(
......@@ -2328,12 +2419,21 @@ void main() {
expect(controller.offset, equals(0.0));
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
testWidgets('Focus traversal inside a horizontal scrollable scrolls to stay visible.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal inside a horizontal scrollable scrolls to stay visible.', (WidgetTester tester) async {
final List<int> items = List<int>.generate(11, (int index) => index).toList();
final List<FocusNode> nodes = List<FocusNode>.generate(11, (int index) => FocusNode(debugLabel: 'Item ${index + 1}')).toList();
addTearDown(() {
for (final FocusNode node in nodes) {
node.dispose();
}
});
final FocusNode leftNode = FocusNode(debugLabel: 'Left Side');
addTearDown(leftNode.dispose);
final FocusNode rightNode = FocusNode(debugLabel: 'Right Side');
addTearDown(rightNode.dispose);
final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
await tester.pumpWidget(
MaterialApp(
home: Row(
......@@ -2426,23 +2526,31 @@ void main() {
expect(controller.offset, equals(0.0));
}, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347
testWidgets('Arrow focus traversal actions can be re-enabled for text fields.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Arrow focus traversal actions can be re-enabled for text fields.', (WidgetTester tester) async {
final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey');
final GlobalKey upperRightKey = GlobalKey(debugLabel: 'upperRightKey');
final GlobalKey lowerLeftKey = GlobalKey(debugLabel: 'lowerLeftKey');
final GlobalKey lowerRightKey = GlobalKey(debugLabel: 'lowerRightKey');
final TextEditingController controller1 = TextEditingController();
addTearDown(controller1.dispose);
final TextEditingController controller2 = TextEditingController();
addTearDown(controller2.dispose);
final TextEditingController controller3 = TextEditingController();
addTearDown(controller3.dispose);
final TextEditingController controller4 = TextEditingController();
addTearDown(controller4.dispose);
final FocusNode focusNodeUpperLeft = FocusNode(debugLabel: 'upperLeft');
addTearDown(focusNodeUpperLeft.dispose);
final FocusNode focusNodeUpperRight = FocusNode(debugLabel: 'upperRight');
addTearDown(focusNodeUpperRight.dispose);
final FocusNode focusNodeLowerLeft = FocusNode(debugLabel: 'lowerLeft');
addTearDown(focusNodeLowerLeft.dispose);
final FocusNode focusNodeLowerRight = FocusNode(debugLabel: 'lowerRight');
addTearDown(focusNodeLowerRight.dispose);
Widget generateTestWidgets(bool ignoreTextFields) {
Widget generatetestWidgetsWithLeakTracking(bool ignoreTextFields) {
final Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{
const SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left, ignoreTextFields: ignoreTextFields),
const SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right, ignoreTextFields: ignoreTextFields),
......@@ -2521,7 +2629,7 @@ void main() {
);
}
await tester.pumpWidget(generateTestWidgets(false));
await tester.pumpWidget(generatetestWidgetsWithLeakTracking(false));
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
......@@ -2533,7 +2641,7 @@ void main() {
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
await tester.pumpWidget(generateTestWidgets(true));
await tester.pumpWidget(generatetestWidgetsWithLeakTracking(true));
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
......@@ -2549,7 +2657,7 @@ void main() {
expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue);
}, variant: KeySimulatorTransitModeVariant.all());
testWidgets('Focus traversal does not break when no focusable is available on a MaterialApp', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal does not break when no focusable is available on a MaterialApp', (WidgetTester tester) async {
final List<Object> events = <Object>[];
await tester.pumpWidget(MaterialApp(home: Container()));
......@@ -2565,7 +2673,7 @@ void main() {
expect(events.length, 2);
}, variant: KeySimulatorTransitModeVariant.all());
testWidgets('Focus traversal does not throw when no focusable is available in a group', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal does not throw when no focusable is available in a group', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: Scaffold(body: ListTile(title: Text('title')))));
final FocusNode? initialFocus = primaryFocus;
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
......@@ -2573,7 +2681,7 @@ void main() {
expect(primaryFocus, equals(initialFocus));
});
testWidgets('Focus traversal does not break when no focusable is available on a WidgetsApp', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Focus traversal does not break when no focusable is available on a WidgetsApp', (WidgetTester tester) async {
final List<RawKeyEvent> events = <RawKeyEvent>[];
await tester.pumpWidget(
......@@ -2599,9 +2707,10 @@ void main() {
expect(events.length, 2);
}, variant: KeySimulatorTransitModeVariant.all());
testWidgets('Custom requestFocusCallback gets called on focusInDirection up/down/left/right.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Custom requestFocusCallback gets called on focusInDirection up/down/left/right.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final FocusNode testNode1 = FocusNode(debugLabel: 'Focus Node');
addTearDown(testNode1.dispose);
bool calledCallback = false;
await tester.pumpWidget(
......@@ -2655,7 +2764,7 @@ void main() {
});
group(FocusTraversalGroup, () {
testWidgets("Focus traversal group doesn't introduce a Semantics node", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Focus traversal group doesn't introduce a Semantics node", (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(FocusTraversalGroup(child: Container()));
final TestSemantics expectedSemantics = TestSemantics.root();
......@@ -2663,11 +2772,13 @@ void main() {
semantics.dispose();
});
testWidgets("Descendants of FocusTraversalGroup aren't focusable if descendantsAreFocusable is false.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Descendants of FocusTraversalGroup aren't focusable if descendantsAreFocusable is false.", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? gotFocus;
await tester.pumpWidget(
FocusTraversalGroup(
descendantsAreFocusable: false,
......@@ -2702,13 +2813,16 @@ void main() {
expect(unfocusableNode.hasFocus, isFalse);
});
testWidgets('Group applies correct policy if focus tree is different from widget tree.', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Group applies correct policy if focus tree is different from widget tree.', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final GlobalKey key3 = GlobalKey(debugLabel: '3');
final GlobalKey key4 = GlobalKey(debugLabel: '4');
final FocusNode focusNode = FocusNode(debugLabel: 'child');
addTearDown(focusNode.dispose);
final FocusNode parentFocusNode = FocusNode(debugLabel: 'parent');
addTearDown(parentFocusNode.dispose);
await tester.pumpWidget(
Column(
children: <Widget>[
......@@ -2744,9 +2858,11 @@ void main() {
expect(FocusTraversalGroup.of(key2.currentContext!), const TypeMatcher<SkipAllButFirstAndLastPolicy>());
});
testWidgets("Descendants of FocusTraversalGroup aren't traversable if descendantsAreTraversable is false.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Descendants of FocusTraversalGroup aren't traversable if descendantsAreTraversable is false.", (WidgetTester tester) async {
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode();
addTearDown(node2.dispose);
await tester.pumpWidget(
FocusTraversalGroup(
......@@ -2779,9 +2895,11 @@ void main() {
expect(node2.hasPrimaryFocus, isFalse);
});
testWidgets("FocusTraversalGroup with skipTraversal for all descendants set to true doesn't cause an exception.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("FocusTraversalGroup with skipTraversal for all descendants set to true doesn't cause an exception.", (WidgetTester tester) async {
final FocusNode node1 = FocusNode();
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode();
addTearDown(node2.dispose);
await tester.pumpWidget(
FocusTraversalGroup(
......@@ -2815,11 +2933,13 @@ void main() {
expect(node2.hasPrimaryFocus, isFalse);
});
testWidgets("Nested FocusTraversalGroup with unfocusable children doesn't assert.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Nested FocusTraversalGroup with unfocusable children doesn't assert.", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: '1');
final GlobalKey key2 = GlobalKey(debugLabel: '2');
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
bool? gotFocus;
await tester.pumpWidget(
FocusTraversalGroup(
child: Column(
......@@ -2864,9 +2984,11 @@ void main() {
expect(unfocusableNode.hasFocus, isFalse);
});
testWidgets("Empty FocusTraversalGroup doesn't cause an exception.", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Empty FocusTraversalGroup doesn't cause an exception.", (WidgetTester tester) async {
final GlobalKey key = GlobalKey(debugLabel: 'Test Key');
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
addTearDown(focusNode.dispose);
await tester.pumpWidget(
FocusTraversalGroup(
child: Directionality(
......@@ -2895,9 +3017,11 @@ void main() {
});
group(RawKeyboardListener, () {
testWidgets('Raw keyboard listener introduces a Semantics node by default', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Raw keyboard listener introduces a Semantics node by default', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
await tester.pumpWidget(
RawKeyboardListener(
focusNode: focusNode,
......@@ -2922,9 +3046,11 @@ void main() {
semantics.dispose();
});
testWidgets("Raw keyboard listener doesn't introduce a Semantics node when specified", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Raw keyboard listener doesn't introduce a Semantics node when specified", (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
await tester.pumpWidget(
RawKeyboardListener(
focusNode: focusNode,
......@@ -2939,11 +3065,15 @@ void main() {
});
group(ExcludeFocusTraversal, () {
testWidgets("Descendants aren't traversable", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Descendants aren't traversable", (WidgetTester tester) async {
final FocusNode node1 = FocusNode(debugLabel: 'node 1');
addTearDown(node1.dispose);
final FocusNode node2 = FocusNode(debugLabel: 'node 2');
addTearDown(node2.dispose);
final FocusNode node3 = FocusNode(debugLabel: 'node 3');
addTearDown(node3.dispose);
final FocusNode node4 = FocusNode(debugLabel: 'node 4');
addTearDown(node4.dispose);
await tester.pumpWidget(
FocusTraversalGroup(
......@@ -2987,7 +3117,7 @@ void main() {
expect(node4.hasPrimaryFocus, isTrue);
});
testWidgets("Doesn't introduce a Semantics node", (WidgetTester tester) async {
testWidgetsWithLeakTracking("Doesn't introduce a Semantics node", (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(ExcludeFocusTraversal(child: Container()));
final TestSemantics expectedSemantics = TestSemantics.root();
......@@ -3003,9 +3133,11 @@ void main() {
// other focusable HTML elements surrounding Flutter.
//
// See also: https://github.com/flutter/flutter/issues/114463
testWidgets('Default route edge traversal behavior', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Default route edge traversal behavior', (WidgetTester tester) async {
final FocusNode nodeA = FocusNode();
addTearDown(nodeA.dispose);
final FocusNode nodeB = FocusNode();
addTearDown(nodeB.dispose);
Future<bool> nextFocus() async {
final bool result = Actions.invoke(
......@@ -3083,12 +3215,15 @@ void main() {
// This test creates a FocusScopeNode configured to traverse focus in a closed
// loop. After traversing one loop, it changes the behavior to leave the
// FlutterView, then verifies that the new behavior did indeed take effect.
testWidgets('FocusScopeNode.traversalEdgeBehavior takes effect after update', (WidgetTester tester) async {
testWidgetsWithLeakTracking('FocusScopeNode.traversalEdgeBehavior takes effect after update', (WidgetTester tester) async {
final FocusScopeNode scope = FocusScopeNode();
addTearDown(scope.dispose);
expect(scope.traversalEdgeBehavior, TraversalEdgeBehavior.closedLoop);
final FocusNode nodeA = FocusNode();
addTearDown(nodeA.dispose);
final FocusNode nodeB = FocusNode();
addTearDown(nodeB.dispose);
Future<bool> nextFocus() async {
final bool result = Actions.invoke(
......@@ -3172,7 +3307,7 @@ void main() {
expect(nodeB.hasFocus, true);
});
testWidgets('NextFocusAction converts invoke result to KeyEventResult', (WidgetTester tester) async {
testWidgetsWithLeakTracking('NextFocusAction converts invoke result to KeyEventResult', (WidgetTester tester) async {
expect(
NextFocusAction().toKeyEventResult(const NextFocusIntent(), true),
KeyEventResult.handled,
......@@ -3183,7 +3318,7 @@ void main() {
);
});
testWidgets('PreviousFocusAction converts invoke result to KeyEventResult', (WidgetTester tester) async {
testWidgetsWithLeakTracking('PreviousFocusAction converts invoke result to KeyEventResult', (WidgetTester tester) async {
expect(
PreviousFocusAction().toKeyEventResult(const PreviousFocusIntent(), true),
KeyEventResult.handled,
......@@ -3194,9 +3329,10 @@ void main() {
);
});
testWidgets('RequestFocusAction calls the RequestFocusIntent.requestFocusCallback', (WidgetTester tester) async {
testWidgetsWithLeakTracking('RequestFocusAction calls the RequestFocusIntent.requestFocusCallback', (WidgetTester tester) async {
bool calledCallback = false;
final FocusNode nodeA = FocusNode();
addTearDown(nodeA.dispose);
await tester.pumpWidget(
MaterialApp(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment