diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 600fdb16dd..fea504a93e 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -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([])); }); - 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 description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList(); expect(description, [ '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> results = >[ [KeyEventResult.ignored, KeyEventResult.ignored], [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([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 receivedAnEvent = {}; final Set shouldHandle = {}; 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 description = builder.properties.map((DiagnosticsNode n) => n.toString()).toList(); expect(description, [ '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(); diff --git a/packages/flutter/test/widgets/focus_scope_test.dart b/packages/flutter/test/widgets/focus_scope_test.dart index b11590fe8f..2f09b957ba 100644 --- a/packages/flutter/test/widgets/focus_scope_test.dart +++ b/packages/flutter/test/widgets/focus_scope_test.dart @@ -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 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 keyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey 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 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 keyA = GlobalKey(debugLabel: 'Key A'); final GlobalKey keyB = GlobalKey(debugLabel: 'Key B'); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey 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 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 keyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey keyB = GlobalKey(); final GlobalKey scopeKeyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey keyB = GlobalKey(); final GlobalKey scopeKeyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey 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 keyA = GlobalKey(); final GlobalKey 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: [ @@ -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([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 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 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 pumpTest({ bool traverseScope1 = false, @@ -1674,10 +1710,11 @@ void main() { expect(scope1.traversalDescendants, equals([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([])); }); - 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( diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index 04cf497d08..2ce49be74e 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -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 nodes = List.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 nodes = List.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([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 nodes = List.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([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 nodes = List.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 nodes = List.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 keys = List.generate(nodeCount, (int index) => String.fromCharCode('A'.codeUnits[0] + nodeCount - index - 1)); final List nodes = List.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 nodes = List.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 focus = List.generate(6, (int _) => null); final List nodes = List.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 focus = List.generate(rows * cols, (int _) => null); final List nodes = List.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 focus = List.generate(rows, (int _) => null); final List nodes = List.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 focus = List.generate(cols, (int _) => null); final List nodes = List.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 items = List.generate(11, (int index) => index).toList(); final List nodes = List.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 items = List.generate(11, (int index) => index).toList(); final List nodes = List.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 shortcuts = { 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 events = []; 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 events = []; 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: [ @@ -2744,9 +2858,11 @@ void main() { expect(FocusTraversalGroup.of(key2.currentContext!), const TypeMatcher()); }); - 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 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 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(