| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| |
| Future<Null> pumpTest(WidgetTester tester, TargetPlatform platform) async { |
| await tester.pumpWidget(new MaterialApp( |
| theme: new ThemeData( |
| platform: platform, |
| ), |
| home: new CustomScrollView( |
| slivers: const <Widget>[ |
| const SliverToBoxAdapter(child: const SizedBox(height: 2000.0)), |
| ], |
| ), |
| )); |
| await tester.pump(const Duration(seconds: 5)); // to let the theme animate |
| return null; |
| } |
| |
| const double dragOffset = 200.0; |
| |
| double getScrollOffset(WidgetTester tester) { |
| final RenderViewport viewport = tester.renderObject(find.byType(Viewport)); |
| return viewport.offset.pixels; |
| } |
| |
| double getScrollVelocity(WidgetTester tester) { |
| final RenderViewport viewport = tester.renderObject(find.byType(Viewport)); |
| final ScrollPosition position = viewport.offset; |
| // Access for test only. |
| return position.activity.velocity; // ignore: INVALID_USE_OF_PROTECTED_MEMBER |
| } |
| |
| void resetScrollOffset(WidgetTester tester) { |
| final RenderViewport viewport = tester.renderObject(find.byType(Viewport)); |
| final ScrollPosition position = viewport.offset; |
| position.jumpTo(0.0); |
| } |
| |
| void main() { |
| testWidgets('Flings on different platforms', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.android); |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| expect(getScrollOffset(tester), dragOffset); |
| await tester.pump(); // trigger fling |
| expect(getScrollOffset(tester), dragOffset); |
| await tester.pump(const Duration(seconds: 5)); |
| final double result1 = getScrollOffset(tester); |
| |
| resetScrollOffset(tester); |
| |
| await pumpTest(tester, TargetPlatform.iOS); |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| // Scroll starts ease into the scroll on iOS. |
| expect(getScrollOffset(tester), moreOrLessEquals(197.16666666666669)); |
| await tester.pump(); // trigger fling |
| expect(getScrollOffset(tester), moreOrLessEquals(197.16666666666669)); |
| await tester.pump(const Duration(seconds: 5)); |
| final double result2 = getScrollOffset(tester); |
| |
| expect(result1, lessThan(result2)); // iOS (result2) is slipperier than Android (result1) |
| }); |
| |
| testWidgets('Holding scroll', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| await tester.drag(find.byType(Viewport), const Offset(0.0, 200.0)); |
| expect(getScrollOffset(tester), -200.0); |
| await tester.pump(); // trigger ballistic |
| await tester.pump(const Duration(milliseconds: 10)); |
| expect(getScrollOffset(tester), greaterThan(-200.0)); |
| expect(getScrollOffset(tester), lessThan(0.0)); |
| final double heldPosition = getScrollOffset(tester); |
| // Hold and let go while in overscroll. |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| expect(await tester.pumpAndSettle(), 1); |
| expect(getScrollOffset(tester), heldPosition); |
| await gesture.up(); |
| // Once the hold is let go, it should still snap back to origin. |
| expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2); |
| expect(getScrollOffset(tester), 0.0); |
| }); |
| |
| testWidgets('Repeated flings builds momentum on iOS', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| await tester.pump(); // trigger fling |
| await tester.pump(const Duration(milliseconds: 10)); |
| // Repeat the exact same motion. |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| await tester.pump(); |
| // On iOS, the velocity will be larger than the velocity of the last fling by a |
| // non-trivial amount. |
| expect(getScrollVelocity(tester), greaterThan(1100.0)); |
| |
| resetScrollOffset(tester); |
| |
| await pumpTest(tester, TargetPlatform.android); |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| await tester.pump(); // trigger fling |
| await tester.pump(const Duration(milliseconds: 10)); |
| // Repeat the exact same motion. |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| await tester.pump(); |
| // On Android, there is no momentum build. The final velocity is the same as the |
| // velocity of the last fling. |
| expect(getScrollVelocity(tester), moreOrLessEquals(1000.0)); |
| }); |
| |
| testWidgets('No iOS momentum build with flings in opposite directions', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| await tester.pump(); // trigger fling |
| await tester.pump(const Duration(milliseconds: 10)); |
| // Repeat the exact same motion in the opposite direction. |
| await tester.fling(find.byType(Viewport), const Offset(0.0, dragOffset), 1000.0); |
| await tester.pump(); |
| // The only applied velocity to the scrollable is the second fling that was in the |
| // opposite direction. |
| expect(getScrollVelocity(tester), greaterThan(-1000.0)); |
| expect(getScrollVelocity(tester), lessThan(0.0)); |
| }); |
| |
| testWidgets('No iOS momentum kept on hold gestures', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| await tester.fling(find.byType(Viewport), const Offset(0.0, -dragOffset), 1000.0); |
| await tester.pump(); // trigger fling |
| await tester.pump(const Duration(milliseconds: 10)); |
| expect(getScrollVelocity(tester), greaterThan(0.0)); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| await tester.pump(const Duration(milliseconds: 40)); |
| await gesture.up(); |
| // After a hold longer than 2 frames, previous velocity is lost. |
| expect(getScrollVelocity(tester), 0.0); |
| }); |
| |
| testWidgets('Drags creeping unaffected on Android', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.android); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| await gesture.moveBy(const Offset(0.0, -0.5)); |
| expect(getScrollOffset(tester), 0.5); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 10)); |
| expect(getScrollOffset(tester), 1.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 20)); |
| expect(getScrollOffset(tester), 1.5); |
| }); |
| |
| testWidgets('Drags creeping must break threshold on iOS', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| await gesture.moveBy(const Offset(0.0, -0.5)); |
| expect(getScrollOffset(tester), 0.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 10)); |
| expect(getScrollOffset(tester), 0.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 20)); |
| expect(getScrollOffset(tester), 0.0); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 30)); |
| // Now -2.5 in total. |
| expect(getScrollOffset(tester), 0.0); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 40)); |
| // Now -3.5, just reached threshold. |
| expect(getScrollOffset(tester), 0.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 50)); |
| // -0.5 over threshold transferred. |
| expect(getScrollOffset(tester), 0.5); |
| }); |
| |
| testWidgets('Big drag over threshold magnitude preserved on iOS', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| await gesture.moveBy(const Offset(0.0, -30.0)); |
| // No offset lost from threshold. |
| expect(getScrollOffset(tester), 30.0); |
| }); |
| |
| testWidgets('Slow threshold breaks are attenuated on iOS', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| // This is a typical 'hesitant' iOS scroll start. |
| await gesture.moveBy(const Offset(0.0, -10.0)); |
| expect(getScrollOffset(tester), moreOrLessEquals(1.1666666666666667)); |
| await gesture.moveBy(const Offset(0.0, -10.0), timeStamp: const Duration(milliseconds: 20)); |
| // Subsequent motions unaffected. |
| expect(getScrollOffset(tester), moreOrLessEquals(11.16666666666666673)); |
| }); |
| |
| testWidgets('Small continuing motion preserved on iOS', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| await gesture.moveBy(const Offset(0.0, -30.0)); // Break threshold. |
| expect(getScrollOffset(tester), 30.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 20)); |
| expect(getScrollOffset(tester), 30.5); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 40)); |
| expect(getScrollOffset(tester), 31.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 60)); |
| expect(getScrollOffset(tester), 31.5); |
| }); |
| |
| testWidgets('Motion stop resets threshold on iOS', (WidgetTester tester) async { |
| await pumpTest(tester, TargetPlatform.iOS); |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(Viewport))); |
| await gesture.moveBy(const Offset(0.0, -30.0)); // Break threshold. |
| expect(getScrollOffset(tester), 30.0); |
| await gesture.moveBy(const Offset(0.0, -0.5), timeStamp: const Duration(milliseconds: 20)); |
| expect(getScrollOffset(tester), 30.5); |
| await gesture.moveBy(Offset.zero); |
| // Stationary too long, threshold reset. |
| await gesture.moveBy(Offset.zero, timeStamp: const Duration(milliseconds: 120)); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 140)); |
| expect(getScrollOffset(tester), 30.5); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 150)); |
| expect(getScrollOffset(tester), 30.5); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 160)); |
| expect(getScrollOffset(tester), 30.5); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 170)); |
| // New threshold broken. |
| expect(getScrollOffset(tester), 31.5); |
| await gesture.moveBy(const Offset(0.0, -1.0), timeStamp: const Duration(milliseconds: 180)); |
| expect(getScrollOffset(tester), 32.5); |
| }); |
| } |