Unverified Commit 9d42ad84 authored by Kostia Sokolovskyi's avatar Kostia Sokolovskyi Committed by GitHub

Fix memory leak in NestedScrollViewState. (#135248)

parent 5def6f2b
...@@ -441,6 +441,7 @@ class NestedScrollViewState extends State<NestedScrollView> { ...@@ -441,6 +441,7 @@ class NestedScrollViewState extends State<NestedScrollView> {
void dispose() { void dispose() {
_coordinator!.dispose(); _coordinator!.dispose();
_coordinator = null; _coordinator = null;
_absorberHandle.dispose();
super.dispose(); super.dispose();
} }
...@@ -1620,6 +1621,13 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity { ...@@ -1620,6 +1621,13 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity {
/// [SliverOverlapAbsorber] to align its children, and which shows sample /// [SliverOverlapAbsorber] to align its children, and which shows sample
/// usage for this class. /// usage for this class.
class SliverOverlapAbsorberHandle extends ChangeNotifier { class SliverOverlapAbsorberHandle extends ChangeNotifier {
/// Creates a [SliverOverlapAbsorberHandle].
SliverOverlapAbsorberHandle() {
if (kFlutterMemoryAllocationsEnabled) {
ChangeNotifier.maybeDispatchObjectCreation(this);
}
}
// Incremented when a RenderSliverOverlapAbsorber takes ownership of this // Incremented when a RenderSliverOverlapAbsorber takes ownership of this
// object, decremented when it releases it. This allows us to find cases where // object, decremented when it releases it. This allows us to find cases where
// the same handle is being passed to two render objects. // the same handle is being passed to two render objects.
......
...@@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart' show DragStartBehavior; ...@@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../rendering/rendering_tester.dart' show TestClipPaintingContext; import '../rendering/rendering_tester.dart' show TestClipPaintingContext;
...@@ -109,7 +110,7 @@ Widget buildTest({ ...@@ -109,7 +110,7 @@ Widget buildTest({
} }
void main() { void main() {
testWidgets('ScrollDirection test', (WidgetTester tester) async { testWidgetsWithLeakTracking('ScrollDirection test', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/107101 // Regression test for https://github.com/flutter/flutter/issues/107101
final List<ScrollDirection> receivedResult = <ScrollDirection>[]; final List<ScrollDirection> receivedResult = <ScrollDirection>[];
const List<ScrollDirection> expectedReverseResult = <ScrollDirection>[ScrollDirection.reverse, ScrollDirection.idle]; const List<ScrollDirection> expectedReverseResult = <ScrollDirection>[ScrollDirection.reverse, ScrollDirection.idle];
...@@ -211,7 +212,7 @@ void main() { ...@@ -211,7 +212,7 @@ void main() {
expect(context.clipBehavior, equals(Clip.antiAlias)); expect(context.clipBehavior, equals(Clip.antiAlias));
}); });
testWidgets('NestedScrollView overscroll and release and hold', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView overscroll and release and hold', (WidgetTester tester) async {
await tester.pumpWidget(buildTest()); await tester.pumpWidget(buildTest());
expect(find.text('aaa2'), findsOneWidget); expect(find.text('aaa2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 250)); await tester.pump(const Duration(milliseconds: 250));
...@@ -234,7 +235,7 @@ void main() { ...@@ -234,7 +235,7 @@ void main() {
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0); expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('NestedScrollView overscroll and release and hold', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView overscroll and release and hold', (WidgetTester tester) async {
await tester.pumpWidget(buildTest()); await tester.pumpWidget(buildTest());
expect(find.text('aaa2'), findsOneWidget); expect(find.text('aaa2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 250)); await tester.pump(const Duration(milliseconds: 250));
...@@ -259,7 +260,7 @@ void main() { ...@@ -259,7 +260,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 1000)); await tester.pump(const Duration(milliseconds: 1000));
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('NestedScrollView overscroll and release', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView overscroll and release', (WidgetTester tester) async {
await tester.pumpWidget(buildTest()); await tester.pumpWidget(buildTest());
expect(find.text('aaa2'), findsOneWidget); expect(find.text('aaa2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500));
...@@ -275,7 +276,7 @@ void main() { ...@@ -275,7 +276,7 @@ void main() {
expect(find.text('aaa2'), findsOneWidget); expect(find.text('aaa2'), findsOneWidget);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('NestedScrollView', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView', (WidgetTester tester) async {
await tester.pumpWidget(buildTest()); await tester.pumpWidget(buildTest());
expect(find.text('aaa2'), findsOneWidget); expect(find.text('aaa2'), findsOneWidget);
expect(find.text('aaa3'), findsNothing); expect(find.text('aaa3'), findsNothing);
...@@ -342,10 +343,11 @@ void main() { ...@@ -342,10 +343,11 @@ void main() {
); );
}); });
testWidgets('NestedScrollView with a ScrollController', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView with a ScrollController', (WidgetTester tester) async {
final ScrollController controller = ScrollController( final ScrollController controller = ScrollController(
initialScrollOffset: 50.0, initialScrollOffset: 50.0,
); );
addTearDown(controller.dispose);
late double scrollOffset; late double scrollOffset;
controller.addListener(() { controller.addListener(() {
...@@ -419,8 +421,9 @@ void main() { ...@@ -419,8 +421,9 @@ void main() {
expect(find.text('ddd1'), findsOneWidget); expect(find.text('ddd1'), findsOneWidget);
}); });
testWidgets('Three NestedScrollViews with one ScrollController', (WidgetTester tester) async { testWidgetsWithLeakTracking('Three NestedScrollViews with one ScrollController', (WidgetTester tester) async {
final TrackingScrollController controller = TrackingScrollController(); final TrackingScrollController controller = TrackingScrollController();
addTearDown(controller.dispose);
expect(controller.mostRecentlyUpdatedPosition, isNull); expect(controller.mostRecentlyUpdatedPosition, isNull);
expect(controller.initialScrollOffset, 0.0); expect(controller.initialScrollOffset, 0.0);
...@@ -482,7 +485,7 @@ void main() { ...@@ -482,7 +485,7 @@ void main() {
); );
}); });
testWidgets('NestedScrollViews with custom physics', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollViews with custom physics', (WidgetTester tester) async {
await tester.pumpWidget(Directionality( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: Localizations( child: Localizations(
...@@ -520,7 +523,7 @@ void main() { ...@@ -520,7 +523,7 @@ void main() {
expect(point1.dy, greaterThan(point2.dy)); expect(point1.dy, greaterThan(point2.dy));
}); });
testWidgets('NestedScrollViews respect NeverScrollableScrollPhysics', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollViews respect NeverScrollableScrollPhysics', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/113753 // Regression test for https://github.com/flutter/flutter/issues/113753
await tester.pumpWidget(Directionality( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -561,7 +564,7 @@ void main() { ...@@ -561,7 +564,7 @@ void main() {
expect(point1, point2); expect(point1, point2);
}); });
testWidgets('NestedScrollView and internal scrolling', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView and internal scrolling', (WidgetTester tester) async {
debugDisableShadows = false; debugDisableShadows = false;
const List<String> tabs = <String>['Hello', 'World']; const List<String> tabs = <String>['Hello', 'World'];
int buildCount = 0; int buildCount = 0;
...@@ -843,7 +846,7 @@ void main() { ...@@ -843,7 +846,7 @@ void main() {
debugDisableShadows = true; debugDisableShadows = true;
}); });
testWidgets('NestedScrollView and bouncing', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView and bouncing', (WidgetTester tester) async {
// This verifies that overscroll bouncing works correctly on iOS. For // This verifies that overscroll bouncing works correctly on iOS. For
// example, this checks that if you pull to overscroll, friction is applied; // example, this checks that if you pull to overscroll, friction is applied;
// it also makes sure that if you scroll back the other way, the scroll // it also makes sure that if you scroll back the other way, the scroll
...@@ -946,7 +949,7 @@ void main() { ...@@ -946,7 +949,7 @@ void main() {
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
group('NestedScrollViewState exposes inner and outer controllers', () { group('NestedScrollViewState exposes inner and outer controllers', () {
testWidgets('Scrolling by less than the outer extent does not scroll the inner body', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scrolling by less than the outer extent does not scroll the inner body', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey, key: globalKey,
...@@ -978,7 +981,7 @@ void main() { ...@@ -978,7 +981,7 @@ void main() {
expect(globalKey.currentState!.innerController.offset, 0.0); expect(globalKey.currentState!.innerController.offset, 0.0);
}); });
testWidgets('Scrolling by exactly the outer extent does not scroll the inner body', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scrolling by exactly the outer extent does not scroll the inner body', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey, key: globalKey,
...@@ -1010,7 +1013,7 @@ void main() { ...@@ -1010,7 +1013,7 @@ void main() {
expect(globalKey.currentState!.innerController.offset, 0.0); expect(globalKey.currentState!.innerController.offset, 0.0);
}); });
testWidgets('Scrolling by greater than the outer extent scrolls the inner body', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scrolling by greater than the outer extent scrolls the inner body', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey, key: globalKey,
...@@ -1046,7 +1049,7 @@ void main() { ...@@ -1046,7 +1049,7 @@ void main() {
); );
}); });
testWidgets('Inertia-cancel event does not modify either position.', (WidgetTester tester) async { testWidgetsWithLeakTracking('Inertia-cancel event does not modify either position.', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey, key: globalKey,
...@@ -1094,7 +1097,7 @@ void main() { ...@@ -1094,7 +1097,7 @@ void main() {
); );
}); });
testWidgets('scrolling by less than the expanded outer extent does not scroll the inner body', (WidgetTester tester) async { testWidgetsWithLeakTracking('scrolling by less than the expanded outer extent does not scroll the inner body', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest(key: globalKey)); await tester.pumpWidget(buildTest(key: globalKey));
...@@ -1123,7 +1126,7 @@ void main() { ...@@ -1123,7 +1126,7 @@ void main() {
expect(globalKey.currentState!.innerController.offset, 0.0); expect(globalKey.currentState!.innerController.offset, 0.0);
}); });
testWidgets('scrolling by exactly the expanded outer extent does not scroll the inner body', (WidgetTester tester) async { testWidgetsWithLeakTracking('scrolling by exactly the expanded outer extent does not scroll the inner body', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest(key: globalKey)); await tester.pumpWidget(buildTest(key: globalKey));
...@@ -1152,7 +1155,7 @@ void main() { ...@@ -1152,7 +1155,7 @@ void main() {
expect(globalKey.currentState!.innerController.offset, 0.0); expect(globalKey.currentState!.innerController.offset, 0.0);
}); });
testWidgets('scrolling by greater than the expanded outer extent scrolls the inner body', (WidgetTester tester) async { testWidgetsWithLeakTracking('scrolling by greater than the expanded outer extent scrolls the inner body', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
await tester.pumpWidget(buildTest(key: globalKey)); await tester.pumpWidget(buildTest(key: globalKey));
...@@ -1182,11 +1185,12 @@ void main() { ...@@ -1182,11 +1185,12 @@ void main() {
expect(globalKey.currentState!.innerController.offset, 50.0); expect(globalKey.currentState!.innerController.offset, 50.0);
}); });
testWidgets( testWidgetsWithLeakTracking(
'NestedScrollViewState.outerController should correspond to NestedScrollView.controller', 'NestedScrollViewState.outerController should correspond to NestedScrollView.controller',
(WidgetTester tester) async { (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
addTearDown(scrollController.dispose);
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
controller: scrollController, controller: scrollController,
...@@ -1213,7 +1217,7 @@ void main() { ...@@ -1213,7 +1217,7 @@ void main() {
); );
group('manipulating controllers when', () { group('manipulating controllers when', () {
testWidgets('outer: not scrolled, inner: not scrolled', (WidgetTester tester) async { testWidgetsWithLeakTracking('outer: not scrolled, inner: not scrolled', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey1, key: globalKey1,
...@@ -1255,7 +1259,7 @@ void main() { ...@@ -1255,7 +1259,7 @@ void main() {
expect(globalKey2.currentState!.outerController.position.pixels, 0.0); expect(globalKey2.currentState!.outerController.position.pixels, 0.0);
}); });
testWidgets('outer: not scrolled, inner: scrolled', (WidgetTester tester) async { testWidgetsWithLeakTracking('outer: not scrolled, inner: scrolled', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey1, key: globalKey1,
...@@ -1299,7 +1303,7 @@ void main() { ...@@ -1299,7 +1303,7 @@ void main() {
expect(globalKey2.currentState!.outerController.position.pixels, 0.0); expect(globalKey2.currentState!.outerController.position.pixels, 0.0);
}); });
testWidgets('outer: scrolled, inner: not scrolled', (WidgetTester tester) async { testWidgetsWithLeakTracking('outer: scrolled, inner: not scrolled', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey1, key: globalKey1,
...@@ -1343,7 +1347,7 @@ void main() { ...@@ -1343,7 +1347,7 @@ void main() {
expect(globalKey2.currentState!.outerController.position.pixels, 0.0); expect(globalKey2.currentState!.outerController.position.pixels, 0.0);
}); });
testWidgets('outer: scrolled, inner: scrolled', (WidgetTester tester) async { testWidgetsWithLeakTracking('outer: scrolled, inner: scrolled', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey(); final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
await tester.pumpWidget(buildTest( await tester.pumpWidget(buildTest(
key: globalKey1, key: globalKey1,
...@@ -1392,7 +1396,7 @@ void main() { ...@@ -1392,7 +1396,7 @@ void main() {
}); });
// Regression test for https://github.com/flutter/flutter/issues/39963. // Regression test for https://github.com/flutter/flutter/issues/39963.
testWidgets('NestedScrollView with SliverOverlapAbsorber in or out of the first screen', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView with SliverOverlapAbsorber in or out of the first screen', (WidgetTester tester) async {
await tester.pumpWidget(const _TestLayoutExtentIsNegative(1)); await tester.pumpWidget(const _TestLayoutExtentIsNegative(1));
await tester.pumpWidget(const _TestLayoutExtentIsNegative(10)); await tester.pumpWidget(const _TestLayoutExtentIsNegative(10));
}); });
...@@ -1471,7 +1475,7 @@ void main() { ...@@ -1471,7 +1475,7 @@ void main() {
return geometry.paintExtent; return geometry.paintExtent;
} }
testWidgets('float', (WidgetTester tester) async { testWidgetsWithLeakTracking('float', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
floating: true, floating: true,
...@@ -1523,7 +1527,7 @@ void main() { ...@@ -1523,7 +1527,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true);
}); });
testWidgets('float expanded', (WidgetTester tester) async { testWidgetsWithLeakTracking('float expanded', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
floating: true, floating: true,
...@@ -1578,7 +1582,7 @@ void main() { ...@@ -1578,7 +1582,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 200.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 200.0, visible: true);
}); });
testWidgets('float with pointer signal', (WidgetTester tester) async { testWidgetsWithLeakTracking('float with pointer signal', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
floating: true, floating: true,
...@@ -1635,7 +1639,7 @@ void main() { ...@@ -1635,7 +1639,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true);
}); });
testWidgets('snap with pointer signal', (WidgetTester tester) async { testWidgetsWithLeakTracking('snap with pointer signal', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
floating: true, floating: true,
...@@ -1689,7 +1693,7 @@ void main() { ...@@ -1689,7 +1693,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 0.0, visible: false); verifyGeometry(key: appBarKey, paintExtent: 0.0, visible: false);
}); });
testWidgets('float expanded with pointer signal', (WidgetTester tester) async { testWidgetsWithLeakTracking('float expanded with pointer signal', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
floating: true, floating: true,
...@@ -1749,7 +1753,7 @@ void main() { ...@@ -1749,7 +1753,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 200.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 200.0, visible: true);
}); });
testWidgets('only snap', (WidgetTester tester) async { testWidgetsWithLeakTracking('only snap', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
final GlobalKey<NestedScrollViewState> nestedKey = GlobalKey(); final GlobalKey<NestedScrollViewState> nestedKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
...@@ -1884,7 +1888,7 @@ void main() { ...@@ -1884,7 +1888,7 @@ void main() {
expect(nestedKey.currentState!.outerController.offset, 56.0); expect(nestedKey.currentState!.outerController.offset, 56.0);
}); });
testWidgets('only snap expanded', (WidgetTester tester) async { testWidgetsWithLeakTracking('only snap expanded', (WidgetTester tester) async {
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
final GlobalKey<NestedScrollViewState> nestedKey = GlobalKey(); final GlobalKey<NestedScrollViewState> nestedKey = GlobalKey();
await tester.pumpWidget(buildFloatTest( await tester.pumpWidget(buildFloatTest(
...@@ -2020,7 +2024,7 @@ void main() { ...@@ -2020,7 +2024,7 @@ void main() {
expect(nestedKey.currentState!.outerController.offset, 200.0); expect(nestedKey.currentState!.outerController.offset, 200.0);
}); });
testWidgets('float pinned', (WidgetTester tester) async { testWidgetsWithLeakTracking('float pinned', (WidgetTester tester) async {
// This configuration should have the same behavior of a pinned app bar. // This configuration should have the same behavior of a pinned app bar.
// No floating should happen, and the app bar should persist. // No floating should happen, and the app bar should persist.
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
...@@ -2075,7 +2079,7 @@ void main() { ...@@ -2075,7 +2079,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true);
}); });
testWidgets('float pinned expanded', (WidgetTester tester) async { testWidgetsWithLeakTracking('float pinned expanded', (WidgetTester tester) async {
// Only the expanded portion (flexible space) of the app bar should float // Only the expanded portion (flexible space) of the app bar should float
// in and out. // in and out.
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
...@@ -2134,7 +2138,7 @@ void main() { ...@@ -2134,7 +2138,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 200.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 200.0, visible: true);
}); });
testWidgets('float pinned with pointer signal', (WidgetTester tester) async { testWidgetsWithLeakTracking('float pinned with pointer signal', (WidgetTester tester) async {
// This configuration should have the same behavior of a pinned app bar. // This configuration should have the same behavior of a pinned app bar.
// No floating should happen, and the app bar should persist. // No floating should happen, and the app bar should persist.
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
...@@ -2194,7 +2198,7 @@ void main() { ...@@ -2194,7 +2198,7 @@ void main() {
verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true); verifyGeometry(key: appBarKey, paintExtent: 56.0, visible: true);
}); });
testWidgets('float pinned expanded with pointer signal', (WidgetTester tester) async { testWidgetsWithLeakTracking('float pinned expanded with pointer signal', (WidgetTester tester) async {
// Only the expanded portion (flexible space) of the app bar should float // Only the expanded portion (flexible space) of the app bar should float
// in and out. // in and out.
final GlobalKey appBarKey = GlobalKey(); final GlobalKey appBarKey = GlobalKey();
...@@ -2289,10 +2293,11 @@ void main() { ...@@ -2289,10 +2293,11 @@ void main() {
); );
} }
testWidgets('overscroll, hold for 0 velocity, and release', (WidgetTester tester) async { testWidgetsWithLeakTracking('overscroll, hold for 0 velocity, and release', (WidgetTester tester) async {
// Dragging into an overscroll and holding so that when released, the // Dragging into an overscroll and holding so that when released, the
// ballistic scroll activity has a 0 velocity. // ballistic scroll activity has a 0 velocity.
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
await tester.pumpWidget(buildBallisticTest(controller)); await tester.pumpWidget(buildBallisticTest(controller));
// Last item of the inner scroll view. // Last item of the inner scroll view.
expect(find.text('Item 49'), findsNothing); expect(find.text('Item 49'), findsNothing);
...@@ -2316,10 +2321,11 @@ void main() { ...@@ -2316,10 +2321,11 @@ void main() {
expect(tester.getCenter(find.text('Item 49')).dy, equals(585.0)); expect(tester.getCenter(find.text('Item 49')).dy, equals(585.0));
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('overscroll, release, and tap', (WidgetTester tester) async { testWidgetsWithLeakTracking('overscroll, release, and tap', (WidgetTester tester) async {
// Tapping while an inner ballistic scroll activity is in progress will // Tapping while an inner ballistic scroll activity is in progress will
// trigger a secondary ballistic scroll activity with a 0 velocity. // trigger a secondary ballistic scroll activity with a 0 velocity.
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
await tester.pumpWidget(buildBallisticTest(controller)); await tester.pumpWidget(buildBallisticTest(controller));
// Last item of the inner scroll view. // Last item of the inner scroll view.
expect(find.text('Item 49'), findsNothing); expect(find.text('Item 49'), findsNothing);
...@@ -2350,7 +2356,7 @@ void main() { ...@@ -2350,7 +2356,7 @@ void main() {
}); });
// Regression test for https://github.com/flutter/flutter/issues/63978 // Regression test for https://github.com/flutter/flutter/issues/63978
testWidgets('Inner _NestedScrollPosition.applyClampedDragUpdate correctly calculates range when in overscroll', (WidgetTester tester) async { testWidgetsWithLeakTracking('Inner _NestedScrollPosition.applyClampedDragUpdate correctly calculates range when in overscroll', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey(); final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey();
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Scaffold( home: Scaffold(
...@@ -2404,8 +2410,9 @@ void main() { ...@@ -2404,8 +2410,9 @@ void main() {
expect(nestedScrollView.currentState!.innerController.position.pixels, 295.0); expect(nestedScrollView.currentState!.innerController.position.pixels, 295.0);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Scroll pointer signal should not cause overscroll.', (WidgetTester tester) async { testWidgetsWithLeakTracking('Scroll pointer signal should not cause overscroll.', (WidgetTester tester) async {
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
await tester.pumpWidget(buildTest(controller: controller)); await tester.pumpWidget(buildTest(controller: controller));
final Offset scrollEventLocation = tester.getCenter(find.byType(NestedScrollView)); final Offset scrollEventLocation = tester.getCenter(find.byType(NestedScrollView));
...@@ -2426,7 +2433,7 @@ void main() { ...@@ -2426,7 +2433,7 @@ void main() {
expect(find.text('ddd1'), findsOneWidget); expect(find.text('ddd1'), findsOneWidget);
}); });
testWidgets('NestedScrollView basic scroll with pointer signal', (WidgetTester tester) async{ testWidgetsWithLeakTracking('NestedScrollView basic scroll with pointer signal', (WidgetTester tester) async{
await tester.pumpWidget(buildTest()); await tester.pumpWidget(buildTest());
expect(find.text('aaa2'), findsOneWidget); expect(find.text('aaa2'), findsOneWidget);
expect(find.text('aaa3'), findsNothing); expect(find.text('aaa3'), findsNothing);
...@@ -2466,12 +2473,13 @@ void main() { ...@@ -2466,12 +2473,13 @@ void main() {
}); });
// Related to https://github.com/flutter/flutter/issues/64266 // Related to https://github.com/flutter/flutter/issues/64266
testWidgets( testWidgetsWithLeakTracking(
'Holding scroll and Scroll pointer signal will update ScrollDirection.forward / ScrollDirection.reverse', 'Holding scroll and Scroll pointer signal will update ScrollDirection.forward / ScrollDirection.reverse',
(WidgetTester tester) async { (WidgetTester tester) async {
ScrollDirection? lastUserScrollingDirection; ScrollDirection? lastUserScrollingDirection;
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
await tester.pumpWidget(buildTest(controller: controller)); await tester.pumpWidget(buildTest(controller: controller));
controller.addListener(() { controller.addListener(() {
...@@ -2503,7 +2511,7 @@ void main() { ...@@ -2503,7 +2511,7 @@ void main() {
); );
// Regression test for https://github.com/flutter/flutter/issues/72257 // Regression test for https://github.com/flutter/flutter/issues/72257
testWidgets('NestedScrollView works well when rebuilding during scheduleWarmUpFrame', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView works well when rebuilding during scheduleWarmUpFrame', (WidgetTester tester) async {
bool? isScrolled; bool? isScrolled;
final Widget myApp = MaterialApp( final Widget myApp = MaterialApp(
home: Scaffold( home: Scaffold(
...@@ -2546,8 +2554,9 @@ void main() { ...@@ -2546,8 +2554,9 @@ void main() {
}); });
// Regression test of https://github.com/flutter/flutter/issues/74372 // Regression test of https://github.com/flutter/flutter/issues/74372
testWidgets('ScrollPosition can be accessed during `_updatePosition()`', (WidgetTester tester) async { testWidgetsWithLeakTracking('ScrollPosition can be accessed during `_updatePosition()`', (WidgetTester tester) async {
final ScrollController controller = ScrollController(); final ScrollController controller = ScrollController();
addTearDown(controller.dispose);
late ScrollPosition position; late ScrollPosition position;
Widget buildFrame({ScrollPhysics? physics}) { Widget buildFrame({ScrollPhysics? physics}) {
...@@ -2592,7 +2601,7 @@ void main() { ...@@ -2592,7 +2601,7 @@ void main() {
expect(position.pixels, 0.0); expect(position.pixels, 0.0);
}); });
testWidgets("NestedScrollView doesn't crash due to precision error", (WidgetTester tester) async { testWidgetsWithLeakTracking("NestedScrollView doesn't crash due to precision error", (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/63825 // Regression test for https://github.com/flutter/flutter/issues/63825
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
...@@ -2639,7 +2648,7 @@ void main() { ...@@ -2639,7 +2648,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
}); });
testWidgets('NestedScrollViewCoordinator.pointerScroll dispatches correct scroll notifications', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollViewCoordinator.pointerScroll dispatches correct scroll notifications', (WidgetTester tester) async {
int scrollEnded = 0; int scrollEnded = 0;
int scrollStarted = 0; int scrollStarted = 0;
bool isScrolled = false; bool isScrolled = false;
...@@ -2701,7 +2710,7 @@ void main() { ...@@ -2701,7 +2710,7 @@ void main() {
expect(scrollEnded, 2); expect(scrollEnded, 2);
}); });
testWidgets('SliverAppBar.medium collapses in NestedScrollView', (WidgetTester tester) async { testWidgetsWithLeakTracking('SliverAppBar.medium collapses in NestedScrollView', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey(); final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey();
const double collapsedAppBarHeight = 64; const double collapsedAppBarHeight = 64;
const double expandedAppBarHeight = 112; const double expandedAppBarHeight = 112;
...@@ -2783,7 +2792,7 @@ void main() { ...@@ -2783,7 +2792,7 @@ void main() {
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight); expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
}); });
testWidgets('SliverAppBar.large collapses in NestedScrollView', (WidgetTester tester) async { testWidgetsWithLeakTracking('SliverAppBar.large collapses in NestedScrollView', (WidgetTester tester) async {
final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey(); final GlobalKey<NestedScrollViewState> nestedScrollView = GlobalKey();
const double collapsedAppBarHeight = 64; const double collapsedAppBarHeight = 64;
const double expandedAppBarHeight = 152; const double expandedAppBarHeight = 152;
...@@ -2866,7 +2875,7 @@ void main() { ...@@ -2866,7 +2875,7 @@ void main() {
expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight); expect(tester.getSize(expandedTitleClip).height, expandedAppBarHeight - collapsedAppBarHeight);
}); });
testWidgets('NestedScrollView does not crash when inner scrollable changes while scrolling', (WidgetTester tester) async { testWidgetsWithLeakTracking('NestedScrollView does not crash when inner scrollable changes while scrolling', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/126454. // Regression test for https://github.com/flutter/flutter/issues/126454.
Widget buildApp({required bool nested}) { Widget buildApp({required bool nested}) {
final Widget innerScrollable = ListView( final Widget innerScrollable = ListView(
...@@ -2903,7 +2912,7 @@ void main() { ...@@ -2903,7 +2912,7 @@ void main() {
await tester.pumpWidget(buildApp(nested: true)); await tester.pumpWidget(buildApp(nested: true));
}); });
testWidgets('SliverOverlapInjector asserts when there is no SliverOverlapAbsorber', (WidgetTester tester) async { testWidgetsWithLeakTracking('SliverOverlapInjector asserts when there is no SliverOverlapAbsorber', (WidgetTester tester) async {
Widget buildApp() { Widget buildApp() {
return MaterialApp( return MaterialApp(
home: Scaffold( home: Scaffold(
...@@ -2997,7 +3006,8 @@ void main() { ...@@ -2997,7 +3006,8 @@ void main() {
) )
); );
} }
testWidgets('when headerSliverBuilder is empty', (WidgetTester tester) async {
testWidgetsWithLeakTracking('when headerSliverBuilder is empty', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/117316 // Regression test for https://github.com/flutter/flutter/issues/117316
// Regression test for https://github.com/flutter/flutter/issues/46089 // Regression test for https://github.com/flutter/flutter/issues/46089
// Short body / long body // Short body / long body
...@@ -3015,7 +3025,7 @@ void main() { ...@@ -3015,7 +3025,7 @@ void main() {
} }
}, variant: TargetPlatformVariant.all()); }, variant: TargetPlatformVariant.all());
testWidgets('when headerSliverBuilder extent is 0', (WidgetTester tester) async { testWidgetsWithLeakTracking('when headerSliverBuilder extent is 0', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/79077 // Regression test for https://github.com/flutter/flutter/issues/79077
// Short body / long body // Short body / long body
for (final _BodyLength bodyLength in _BodyLength.values) { for (final _BodyLength bodyLength in _BodyLength.values) {
...@@ -3169,7 +3179,7 @@ void main() { ...@@ -3169,7 +3179,7 @@ void main() {
} }
}, variant: TargetPlatformVariant.all()); }, variant: TargetPlatformVariant.all());
testWidgets('With a pinned SliverAppBar', (WidgetTester tester) async { testWidgetsWithLeakTracking('With a pinned SliverAppBar', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/110956 // Regression test for https://github.com/flutter/flutter/issues/110956
// Regression test for https://github.com/flutter/flutter/issues/127282 // Regression test for https://github.com/flutter/flutter/issues/127282
// Regression test for https://github.com/flutter/flutter/issues/32563 // Regression test for https://github.com/flutter/flutter/issues/32563
...@@ -3200,6 +3210,13 @@ void main() { ...@@ -3200,6 +3210,13 @@ void main() {
} }
}); });
}); });
testWidgetsWithLeakTracking('$SliverOverlapAbsorberHandle dispatches creation in constructor', (WidgetTester widgetTester) async {
await expectLater(
await memoryEvents(() => SliverOverlapAbsorberHandle().dispose(), SliverOverlapAbsorberHandle),
areCreateAndDispose,
);
});
} }
double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height; double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment