diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index 8dc6db922d..023684912b 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -16,6 +16,7 @@ Future pumpTest( bool scrollable = true, bool reverse = false, ScrollController controller, + Widget Function(Widget) wrapper, }) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( @@ -33,14 +34,46 @@ Future pumpTest( await tester.pump(const Duration(seconds: 5)); // to let the theme animate } +// Pump a nested scrollable. The outer scrollable contains a sliver of a +// 300-pixel-long scrollable followed by a 2000-pixel-long content. +Future pumpDoubleScrollableTest( + WidgetTester tester, + TargetPlatform platform, +) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData( + platform: platform, + ), + home: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Container( + height: 300, + child: const CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: SizedBox(height: 2000.0)), + ], + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 2000.0)), + ], + ), + )); + await tester.pump(const Duration(seconds: 5)); // to let the theme animate +} + const double dragOffset = 200.0; final LogicalKeyboardKey modifierKey = defaultTargetPlatform == TargetPlatform.macOS ? LogicalKeyboardKey.metaLeft : LogicalKeyboardKey.controlLeft; -double getScrollOffset(WidgetTester tester) { - final RenderViewport viewport = tester.renderObject(find.byType(Viewport)); +double getScrollOffset(WidgetTester tester, {bool last = true}) { + Finder viewportFinder = find.byType(Viewport); + if (last) + viewportFinder = viewportFinder.last; + final RenderViewport viewport = tester.renderObject(viewportFinder); return viewport.offset.pixels; } @@ -252,6 +285,24 @@ void main() { expect(getScrollOffset(tester), 0.0); }); + testWidgets('Scroll pointer signals are handled when there is competion', (WidgetTester tester) async { + // This is a regression test. When there are multiple scrollables listening + // to the same event, for example when scrollables are nested, there used + // to be exceptions at scrolling events. + + await pumpDoubleScrollableTest(tester, TargetPlatform.fuchsia); + final Offset scrollEventLocation = tester.getCenter(find.byType(Viewport).last); + final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse); + // Create a hover event so that |testPointer| has a location when generating the scroll. + testPointer.hover(scrollEventLocation); + final HitTestResult result = tester.hitTestOnBinding(scrollEventLocation); + await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, 20.0)), result); + expect(getScrollOffset(tester, last: true), 20.0); + // Pointer signals should not cause overscroll. + await tester.sendEventToBinding(testPointer.scroll(const Offset(0.0, -30.0)), result); + expect(getScrollOffset(tester, last: true), 0.0); + }); + testWidgets('Scroll pointer signals are ignored when scrolling is disabled', (WidgetTester tester) async { await pumpTest(tester, TargetPlatform.fuchsia, scrollable: false); final Offset scrollEventLocation = tester.getCenter(find.byType(Viewport));