| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file is run as part of a reduced test set in CI on Mac and Windows |
| // machines. |
| @Tags(<String>['reduced-test-set']) |
| library; |
| |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| Widget buildTest( |
| GlobalKey box1Key, |
| GlobalKey box2Key, |
| GlobalKey box3Key, |
| ScrollController controller, { |
| Axis axis = Axis.vertical, |
| bool reverse = false, |
| TextDirection textDirection = TextDirection.ltr, |
| double boxHeight = 250.0, |
| double boxWidth = 300.0, |
| ScrollPhysics? physics, |
| }) { |
| final AxisDirection axisDirection; |
| switch (axis) { |
| case Axis.horizontal: |
| axisDirection = switch (textDirection) { |
| TextDirection.rtl => reverse ? AxisDirection.right : AxisDirection.left, |
| TextDirection.ltr => reverse ? AxisDirection.left : AxisDirection.right, |
| }; |
| case Axis.vertical: |
| axisDirection = reverse ? AxisDirection.up : AxisDirection.down; |
| } |
| |
| return Directionality( |
| textDirection: textDirection, |
| child: MediaQuery( |
| data: const MediaQueryData(size: Size(800.0, 600.0)), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: StretchingOverscrollIndicator( |
| axisDirection: axisDirection, |
| child: CustomScrollView( |
| physics: physics, |
| reverse: reverse, |
| scrollDirection: axis, |
| controller: controller, |
| slivers: <Widget>[ |
| SliverToBoxAdapter(child: Container( |
| color: const Color(0xD0FF0000), |
| key: box1Key, |
| height: boxHeight, |
| width: boxWidth, |
| )), |
| SliverToBoxAdapter(child: Container( |
| color: const Color(0xFFFFFF00), |
| key: box2Key, |
| height: boxHeight, |
| width: boxWidth, |
| )), |
| SliverToBoxAdapter(child: Container( |
| color: const Color(0xFF6200EA), |
| key: box3Key, |
| height: boxHeight, |
| width: boxWidth, |
| )), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| testWidgets('Stretch overscroll will do nothing when axes do not match', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(size: Size(800.0, 600.0)), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: StretchingOverscrollIndicator( |
| axisDirection: AxisDirection.right, |
| child: CustomScrollView( |
| controller: controller, |
| slivers: <Widget>[ |
| SliverToBoxAdapter(child: Container( |
| color: const Color(0xD0FF0000), |
| key: box1Key, |
| height: 250.0, |
| )), |
| SliverToBoxAdapter(child: Container( |
| color: const Color(0xFFFFFF00), |
| key: box2Key, |
| height: 250.0, |
| width: 300.0, |
| )), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ) |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start, no stretching occurs. |
| await gesture.moveBy(const Offset(0.0, 200.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dy, 250.0); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Overscroll released |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| }); |
| |
| testWidgets('Stretch overscroll vertically', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest(box1Key, box2Key, box3Key, controller), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.vertical.start.png'), |
| ); |
| |
| TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start |
| await gesture.moveBy(const Offset(0.0, 200.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dy, greaterThan(255.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, greaterThan(510.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.vertical.start.stretched.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Stretch released back to the start |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| |
| // Jump to end of the list |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pumpAndSettle(); |
| expect(controller.offset, 150.0); |
| expect(box1.localToGlobal(Offset.zero).dy, -150.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 100.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 350.0); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.vertical.end.png'), |
| ); |
| |
| gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the end |
| await gesture.moveBy(const Offset(0.0, -200.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(-165)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(90.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(350.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.vertical.end.stretched.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Stretch released back |
| expect(box1.localToGlobal(Offset.zero).dy, -150.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 100.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 350.0); |
| }); |
| |
| testWidgets('Stretch overscroll works in reverse - vertical', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest(box1Key, box2Key, box3Key, controller, reverse: true), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 350.0)); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 100.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, -150.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll |
| await gesture.moveBy(const Offset(0.0, -200.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(350.0)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(100.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(-150.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.vertical.reverse.png'), |
| ); |
| }); |
| |
| testWidgets('Stretch overscroll works in reverse - horizontal', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| axis: Axis.horizontal, |
| reverse: true, |
| ), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), const Offset(500.0, 0.0)); |
| expect(box2.localToGlobal(Offset.zero), const Offset(200.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(-100.0, 0.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll |
| await gesture.moveBy(const Offset(-200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero).dx, lessThan(500.0)); |
| expect(box2.localToGlobal(Offset.zero).dx, lessThan(200.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, lessThan(-100.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.reverse.png'), |
| ); |
| }); |
| |
| testWidgets('Stretch overscroll works in reverse - horizontal - RTL', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| axis: Axis.horizontal, |
| reverse: true, |
| textDirection: TextDirection.rtl, |
| ) |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.start.png'), |
| ); |
| |
| TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start |
| await gesture.moveBy(const Offset(200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dx, greaterThan(305.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, greaterThan(610.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.start.stretched.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Stretch released back to the start |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0)); |
| |
| // Jump to end of the list |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pumpAndSettle(); |
| expect(controller.offset, 100.0); |
| expect(box1.localToGlobal(Offset.zero).dx, -100.0); |
| expect(box2.localToGlobal(Offset.zero).dx, 200.0); |
| expect(box3.localToGlobal(Offset.zero).dx, 500.0); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.end.png'), |
| ); |
| |
| gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the end |
| await gesture.moveBy(const Offset(-200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero).dx, lessThan(-116.0)); |
| expect(box2.localToGlobal(Offset.zero).dx, lessThan(190.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, lessThan(500.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.reverse.rtl.end.stretched.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Stretch released back |
| expect(box1.localToGlobal(Offset.zero).dx, -100.0); |
| expect(box2.localToGlobal(Offset.zero).dx, 200.0); |
| expect(box3.localToGlobal(Offset.zero).dx, 500.0); |
| }); |
| |
| testWidgets('Stretch overscroll horizontally', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest(box1Key, box2Key, box3Key, controller, axis: Axis.horizontal) |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.start.png'), |
| ); |
| |
| TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start |
| await gesture.moveBy(const Offset(200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dx, greaterThan(305.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, greaterThan(610.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.start.stretched.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Stretch released back to the start |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0)); |
| |
| // Jump to end of the list |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pumpAndSettle(); |
| expect(controller.offset, 100.0); |
| expect(box1.localToGlobal(Offset.zero).dx, -100.0); |
| expect(box2.localToGlobal(Offset.zero).dx, 200.0); |
| expect(box3.localToGlobal(Offset.zero).dx, 500.0); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.end.png'), |
| ); |
| |
| gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the end |
| await gesture.moveBy(const Offset(-200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero).dx, lessThan(-116.0)); |
| expect(box2.localToGlobal(Offset.zero).dx, lessThan(190.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, lessThan(500.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.end.stretched.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Stretch released back |
| expect(box1.localToGlobal(Offset.zero).dx, -100.0); |
| expect(box2.localToGlobal(Offset.zero).dx, 200.0); |
| expect(box3.localToGlobal(Offset.zero).dx, 500.0); |
| }); |
| |
| testWidgets('Stretch overscroll horizontally RTL', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| axis: Axis.horizontal, |
| textDirection: TextDirection.rtl, |
| ) |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), const Offset(500.0, 0.0)); |
| expect(box2.localToGlobal(Offset.zero), const Offset(200.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(-100.0, 0.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll |
| await gesture.moveBy(const Offset(-200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| expect(box1.localToGlobal(Offset.zero).dx, lessThan(500.0)); |
| expect(box2.localToGlobal(Offset.zero).dx, lessThan(200.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, lessThan(-100.0)); |
| await expectLater( |
| find.byType(CustomScrollView), |
| matchesGoldenFile('overscroll_stretch.horizontal.rtl.png'), |
| ); |
| }); |
| |
| testWidgets('Disallow stretching overscroll', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| double indicatorNotification = 0; |
| await tester.pumpWidget( |
| NotificationListener<OverscrollIndicatorNotification>( |
| onNotification: (OverscrollIndicatorNotification notification) { |
| notification.disallowIndicator(); |
| indicatorNotification += 1; |
| return false; |
| }, |
| child: buildTest(box1Key, box2Key, box3Key, controller), |
| ) |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(indicatorNotification, 0.0); |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start, should not stretch |
| await gesture.moveBy(const Offset(0.0, 200.0)); |
| await tester.pumpAndSettle(); |
| expect(indicatorNotification, 1.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Stretch does not overflow bounds of container', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/90197 |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(size: Size(800.0, 600.0)), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: Column( |
| children: <Widget>[ |
| StretchingOverscrollIndicator( |
| axisDirection: AxisDirection.down, |
| child: SizedBox( |
| height: 300, |
| child: ListView.builder( |
| itemCount: 20, |
| itemBuilder: (BuildContext context, int index){ |
| return Padding( |
| padding: const EdgeInsets.all(10.0), |
| child: Text('Index $index'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| Opacity( |
| opacity: 0.5, |
| child: Container( |
| color: const Color(0xD0FF0000), |
| height: 100, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| )); |
| |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, 51.0); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Overscroll the start. |
| await gesture.moveBy(const Offset(0.0, 200.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0)); |
| // Image should not show the text overlapping the red area below the list. |
| await expectLater( |
| find.byType(Column), |
| matchesGoldenFile('overscroll_stretch.no_overflow.png'), |
| ); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Clip behavior is updated as needed', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/97867 |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(size: Size(800.0, 600.0)), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: Column( |
| children: <Widget>[ |
| StretchingOverscrollIndicator( |
| axisDirection: AxisDirection.down, |
| child: SizedBox( |
| height: 300, |
| child: ListView.builder( |
| itemCount: 20, |
| itemBuilder: (BuildContext context, int index){ |
| return Padding( |
| padding: const EdgeInsets.all(10.0), |
| child: Text('Index $index'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| Opacity( |
| opacity: 0.5, |
| child: Container( |
| color: const Color(0xD0FF0000), |
| height: 100, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, 51.0); |
| RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first; |
| // Currently not clipping |
| expect(renderClip.clipBehavior, equals(Clip.none)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Overscroll the start. |
| await gesture.moveBy(const Offset(0.0, 200.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0)); |
| renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first; |
| // Now clipping |
| expect(renderClip.clipBehavior, equals(Clip.hardEdge)); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('clipBehavior parameter updates overscroll clipping behavior', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/103491 |
| |
| Widget buildFrame(Clip clipBehavior) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(size: Size(800.0, 600.0)), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: Column( |
| children: <Widget>[ |
| StretchingOverscrollIndicator( |
| axisDirection: AxisDirection.down, |
| clipBehavior: clipBehavior, |
| child: SizedBox( |
| height: 300, |
| child: ListView.builder( |
| itemCount: 20, |
| itemBuilder: (BuildContext context, int index){ |
| return Padding( |
| padding: const EdgeInsets.all(10.0), |
| child: Text('Index $index'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| Opacity( |
| opacity: 0.5, |
| child: Container( |
| color: const Color(0xD0FF0000), |
| height: 100, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(Clip.none)); |
| |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, 51.0); |
| RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first; |
| // Currently not clipping |
| expect(renderClip.clipBehavior, equals(Clip.none)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Overscroll the start. |
| await gesture.moveBy(const Offset(0.0, 200.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0)); |
| renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first; |
| // Now clipping |
| expect(renderClip.clipBehavior, equals(Clip.none)); |
| |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Stretch limit', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/99264 |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: StretchingOverscrollIndicator( |
| axisDirection: AxisDirection.down, |
| child: SizedBox( |
| height: 300, |
| child: ListView.builder( |
| itemCount: 20, |
| itemBuilder: (BuildContext context, int index){ |
| return Padding( |
| padding: const EdgeInsets.all(10.0), |
| child: Text('Index $index'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ), |
| ) |
| ) |
| ); |
| const double maxStretchLocation = 52.63178407049861; |
| |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, 51.0); |
| |
| TestGesture pointer = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Overscroll beyond the limit (the viewport is 600.0). |
| await pointer.moveBy(const Offset(0.0, 610.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, maxStretchLocation); |
| |
| pointer = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Overscroll way way beyond the limit |
| await pointer.moveBy(const Offset(0.0, 1000.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, maxStretchLocation); |
| |
| await pointer.up(); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Multiple pointers will not exceed stretch limit', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/99264 |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: ScrollConfiguration( |
| behavior: const ScrollBehavior().copyWith(overscroll: false), |
| child: StretchingOverscrollIndicator( |
| axisDirection: AxisDirection.down, |
| child: SizedBox( |
| height: 300, |
| child: ListView.builder( |
| itemCount: 20, |
| itemBuilder: (BuildContext context, int index){ |
| return Padding( |
| padding: const EdgeInsets.all(10.0), |
| child: Text('Index $index'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ), |
| ) |
| ) |
| ); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, 51.0); |
| |
| final TestGesture pointer1 = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Overscroll the start. |
| await pointer1.moveBy(const Offset(0.0, 210.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| double lastStretchedLocation = tester.getCenter(find.text('Index 1')).dy; |
| expect(lastStretchedLocation, greaterThan(51.0)); |
| |
| final TestGesture pointer2 = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Add overscroll from an additional pointer |
| await pointer2.moveBy(const Offset(0.0, 210.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(lastStretchedLocation)); |
| lastStretchedLocation = tester.getCenter(find.text('Index 1')).dy; |
| |
| final TestGesture pointer3 = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Add overscroll from an additional pointer, exceeding the max stretch (600) |
| await pointer3.moveBy(const Offset(0.0, 210.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(lastStretchedLocation)); |
| lastStretchedLocation = tester.getCenter(find.text('Index 1')).dy; |
| |
| final TestGesture pointer4 = await tester.startGesture(tester.getCenter(find.text('Index 1'))); |
| // Since we have maxed out the overscroll, it should not have stretched |
| // further, regardless of the number of pointers. |
| await pointer4.moveBy(const Offset(0.0, 210.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Index 1'), findsOneWidget); |
| expect(tester.getCenter(find.text('Index 1')).dy, lastStretchedLocation); |
| |
| await pointer1.up(); |
| await pointer2.up(); |
| await pointer3.up(); |
| await pointer4.up(); |
| await tester.pumpAndSettle(); |
| }); |
| |
| testWidgets('Stretch overscroll vertically, change direction mid scroll', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| // Setting the `boxHeight` to 100.0 will make the boxes fit in the |
| // scrollable viewport. |
| boxHeight: 100, |
| // To make the scroll view in the test still scrollable, we need to add |
| // the `AlwaysScrollableScrollPhysics`. |
| physics: const AlwaysScrollableScrollPhysics(), |
| ), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 100.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 200.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start |
| await gesture.moveBy(const Offset(0.0, 600.0)); |
| await tester.pumpAndSettle(); |
| |
| // The boxes should now be at different locations because of the scaling. |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dy, greaterThan(103.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, greaterThan(206.0)); |
| |
| // Move the pointer up a miniscule amount to trigger a directional change. |
| await gesture.moveBy(const Offset(0.0, -20.0)); |
| await tester.pumpAndSettle(); |
| |
| // The boxes should remain roughly at the same locations, since the pointer |
| // didn't move far. |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dy, greaterThan(103.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, greaterThan(206.0)); |
| |
| // Now make the pointer overscroll to the end |
| await gesture.moveBy(const Offset(0.0, -1200.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(-19.0)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(85.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(188.0)); |
| |
| // Release the pointer |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Now the boxes should be back to their original locations. |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 100.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 200.0)); |
| }); |
| |
| testWidgets('Stretch overscroll horizontally, change direction mid scroll', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| // Setting the `boxWidth` to 100.0 will make the boxes fit in the |
| // scrollable viewport. |
| boxWidth: 100, |
| // To make the scroll view in the test still scrollable, we need to add |
| // the `AlwaysScrollableScrollPhysics`. |
| physics: const AlwaysScrollableScrollPhysics(), |
| axis: Axis.horizontal, |
| ), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(100.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(200.0, 0.0)); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll the start |
| await gesture.moveBy(const Offset(600.0, 0.0)); |
| await tester.pumpAndSettle(); |
| |
| // The boxes should now be at different locations because of the scaling. |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dx, greaterThan(102.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, greaterThan(205.0)); |
| |
| // Move the pointer up a miniscule amount to trigger a directional change. |
| await gesture.moveBy(const Offset(-20.0, 0.0)); |
| await tester.pumpAndSettle(); |
| |
| // The boxes should remain roughly at the same locations, since the pointer |
| // didn't move far. |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero).dx, greaterThan(102.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, greaterThan(205.0)); |
| |
| // Now make the pointer overscroll to the end |
| await gesture.moveBy(const Offset(-1200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(box1.localToGlobal(Offset.zero).dx, lessThan(-19.0)); |
| expect(box2.localToGlobal(Offset.zero).dx, lessThan(85.0)); |
| expect(box3.localToGlobal(Offset.zero).dx, lessThan(188.0)); |
| |
| // Release the pointer |
| await gesture.up(); |
| await tester.pumpAndSettle(); |
| |
| // Now the boxes should be back to their original locations. |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(100.0, 0.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(200.0, 0.0)); |
| }); |
| |
| testWidgets('Fling toward the trailing edge causes stretch toward the leading edge', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest(box1Key, box2Key, box3Key, controller), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| |
| await tester.fling(find.byType(CustomScrollView), const Offset(0.0, -50.0), 10000.0); |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // The boxes should now be at different locations because of the scaling. |
| expect(controller.offset, 150.0); |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(-160.0)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(93.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(347.0)); |
| |
| await tester.pumpAndSettle(); |
| |
| // The boxes should now be at their final position. |
| expect(controller.offset, 150.0); |
| expect(box1.localToGlobal(Offset.zero).dy, -150.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 100.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 350.0); |
| }); |
| |
| testWidgets('Fling toward the leading edge causes stretch toward the trailing edge', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest(box1Key, box2Key, box3Key, controller), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| |
| // We fling to the trailing edge and let it settle. |
| await tester.fling(find.byType(CustomScrollView), const Offset(0.0, -50.0), 10000.0); |
| await tester.pumpAndSettle(); |
| |
| // We are now at the trailing edge |
| expect(controller.offset, 150.0); |
| expect(box1.localToGlobal(Offset.zero).dy, -150.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 100.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 350.0); |
| |
| // Now fling to the leading edge |
| await tester.fling(find.byType(CustomScrollView), const Offset(0.0, 50.0), 10000.0); |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // The boxes should now be at different locations because of the scaling. |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero).dy, 0.0); |
| expect(box2.localToGlobal(Offset.zero).dy, greaterThan(254.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, greaterThan(508.0)); |
| |
| await tester.pumpAndSettle(); |
| |
| // The boxes should now be at their final position. |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero).dy, 0.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 250.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 500.0); |
| }); |
| |
| testWidgets('changing scroll direction during recede animation will not change the stretch direction', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest(box1Key, box2Key, box3Key, controller, boxHeight: 205.0), |
| ); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| // Fling to the trailing edge |
| await tester.fling(find.byType(CustomScrollView), const Offset(0.0, -50.0), 10000.0); |
| await tester.pumpAndSettle(); |
| |
| expect(box1.localToGlobal(Offset.zero).dy, -15.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 190.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 395.0); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll to the trailing edge |
| await gesture.moveBy(const Offset(0.0, -200.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(-25.0)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(185.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(392.0)); |
| |
| // This will trigger the recede animation |
| // The y offset of the boxes should be increasing, since the boxes were stretched |
| // toward the leading edge. |
| await gesture.moveBy(const Offset(0.0, 150.0)); |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| // Explicitly check that the box1 offset is not 0.0, since this would probably mean that |
| // the stretch direction is wrong. |
| expect(box1.localToGlobal(Offset.zero).dy, isNot(0.0)); |
| |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(-12.0)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(197.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(407.0)); |
| |
| await tester.pump(const Duration(milliseconds: 100)); |
| |
| expect(box1.localToGlobal(Offset.zero).dy, lessThan(-6.0)); |
| expect(box2.localToGlobal(Offset.zero).dy, lessThan(201.0)); |
| expect(box3.localToGlobal(Offset.zero).dy, lessThan(408.0)); |
| |
| await tester.pumpAndSettle(); |
| |
| // The recede animation is done now, we should now be at the leading edge. |
| expect(box1.localToGlobal(Offset.zero).dy, 0.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 205.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 410.0); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('Stretch overscroll only uses image filter during stretch effect', (WidgetTester tester) async { |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget( |
| buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| axis: Axis.horizontal, |
| ) |
| ); |
| |
| expect(tester.layers, isNot(contains(isA<ImageFilterLayer>()))); |
| |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView))); |
| // Overscroll |
| await gesture.moveBy(const Offset(200.0, 0.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(tester.layers, contains(isA<ImageFilterLayer>())); |
| }); |
| |
| testWidgets('Stretching animation completes after fling under scroll physics with high friction', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/146277 |
| final GlobalKey box1Key = GlobalKey(); |
| final GlobalKey box2Key = GlobalKey(); |
| final GlobalKey box3Key = GlobalKey(); |
| late final OverscrollNotification overscrollNotification; |
| final ScrollController controller = ScrollController(); |
| addTearDown(controller.dispose); |
| |
| await tester.pumpWidget(NotificationListener<OverscrollNotification>( |
| child: buildTest( |
| box1Key, |
| box2Key, |
| box3Key, |
| controller, |
| physics: const _HighFrictionClampingScrollPhysics(), |
| ), |
| onNotification: (OverscrollNotification notification) { |
| overscrollNotification = notification; |
| return false; |
| }, |
| )); |
| |
| expect(find.byType(StretchingOverscrollIndicator), findsOneWidget); |
| expect(find.byType(GlowingOverscrollIndicator), findsNothing); |
| final RenderBox box1 = tester.renderObject(find.byKey(box1Key)); |
| final RenderBox box2 = tester.renderObject(find.byKey(box2Key)); |
| final RenderBox box3 = tester.renderObject(find.byKey(box3Key)); |
| |
| expect(controller.offset, 0.0); |
| expect(box1.localToGlobal(Offset.zero), Offset.zero); |
| expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0)); |
| expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0)); |
| |
| // We fling to the trailing edge and let it settle. |
| await tester.fling(find.byType(CustomScrollView), const Offset(0.0, -50.0), 10000.0); |
| await tester.pumpAndSettle(); |
| |
| // We are now at the trailing edge |
| expect(overscrollNotification.velocity, lessThan(25)); |
| expect(controller.offset, 150.0); |
| expect(box1.localToGlobal(Offset.zero).dy, -150.0); |
| expect(box2.localToGlobal(Offset.zero).dy, 100.0); |
| expect(box3.localToGlobal(Offset.zero).dy, 350.0); |
| }); |
| } |
| |
| final class _HighFrictionClampingScrollPhysics extends ScrollPhysics { |
| const _HighFrictionClampingScrollPhysics({super.parent}); |
| |
| @override |
| ScrollPhysics applyTo(ScrollPhysics? ancestor) { |
| return _HighFrictionClampingScrollPhysics(parent: buildParent(ancestor)); |
| } |
| |
| @override |
| Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) { |
| return ClampingScrollSimulation( |
| position: position.pixels, |
| velocity: velocity, |
| friction: 0.94, |
| tolerance: tolerance, |
| ); |
| } |
| } |