From 8a186f3bbdee9487ee2986e1305f146cfbf53fd0 Mon Sep 17 00:00:00 2001 From: Dan Rubel Date: Thu, 22 Sep 2016 07:43:29 -0400 Subject: [PATCH] tests to assert futures complete as expected (#5947) --- .../src/animation/animation_controller.dart | 6 +++ .../flutter/lib/src/widgets/scrollable.dart | 4 ++ .../test/widget/bottom_sheet_test.dart | 13 ++++- .../flutter/test/widget/positioned_test.dart | 13 ++++- .../widget/remember_scroll_position_test.dart | 7 ++- .../test/widget/scroll_events_test.dart | 49 ++++++++++++++----- .../widget/scrollable_lazy_list_test.dart | 37 ++++++++++++-- .../test/widget/snap_scrolling_test.dart | 32 +++++++----- 8 files changed, 128 insertions(+), 33 deletions(-) diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index e20e4bd87ad..a7bf24fb42d 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -154,6 +154,8 @@ class AnimationController extends Animation AnimationStatus _status; /// Starts running this animation forwards (towards the end). + /// + /// Returns a [Future] that completes when the animation is complete. Future forward({ double from }) { _direction = _AnimationDirection.forward; if (from != null) @@ -162,6 +164,8 @@ class AnimationController extends Animation } /// Starts running this animation in reverse (towards the beginning). + /// + /// Returns a [Future] that completes when the animation is complete. Future reverse({ double from }) { _direction = _AnimationDirection.reverse; if (from != null) @@ -170,6 +174,8 @@ class AnimationController extends Animation } /// Drives the animation from its current value to target. + /// + /// Returns a [Future] that completes when the animation is complete. Future animateTo(double target, { Duration duration, Curve curve: Curves.linear }) { Duration simulationDuration = duration; if (simulationDuration == null) { diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index b80ead833f3..3dc4a6bd808 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -472,6 +472,8 @@ class ScrollableState extends State { /// This function does not accept a zero duration. To jump-scroll to /// the new offset, do not provide a duration, rather than providing /// a zero duration. + /// + /// The returned [Future] completes when the scrolling animation is complete. Future scrollTo(double newScrollOffset, { Duration duration, Curve curve: Curves.ease, @@ -550,6 +552,8 @@ class ScrollableState extends State { /// Calling this function starts a physics-based animation of the scroll /// offset with the given value as the initial velocity. The physics /// simulation is determined by the scroll behavior. + /// + /// The returned [Future] completes when the scrolling animation is complete. Future fling(double scrollVelocity) { if (scrollVelocity.abs() > kPixelScrollTolerance.velocity) return _startToEndAnimation(scrollVelocity); diff --git a/packages/flutter/test/widget/bottom_sheet_test.dart b/packages/flutter/test/widget/bottom_sheet_test.dart index 14346d234b3..205a98996d5 100644 --- a/packages/flutter/test/widget/bottom_sheet_test.dart +++ b/packages/flutter/test/widget/bottom_sheet_test.dart @@ -9,7 +9,6 @@ import 'package:flutter/widgets.dart'; void main() { testWidgets('Verify that a tap dismisses a modal BottomSheet', (WidgetTester tester) async { BuildContext savedContext; - bool showBottomSheetThenCalled = false; await tester.pumpWidget(new MaterialApp( home: new Builder( @@ -23,6 +22,7 @@ void main() { await tester.pump(); expect(find.text('BottomSheet'), findsNothing); + bool showBottomSheetThenCalled = false; showModalBottomSheet/**/( context: savedContext, builder: (BuildContext context) => new Text('BottomSheet') @@ -44,14 +44,23 @@ void main() { await tester.pump(new Duration(seconds: 1)); // frame after the animation (sheet has been removed) expect(find.text('BottomSheet'), findsNothing); - showModalBottomSheet/**/(context: savedContext, builder: (BuildContext context) => new Text('BottomSheet')); + showBottomSheetThenCalled = false; + showModalBottomSheet/**/( + context: savedContext, + builder: (BuildContext context) => new Text('BottomSheet'), + ).then((Null result) { + expectSync(result, isNull); + showBottomSheetThenCalled = true; + }); await tester.pump(); // bottom sheet show animation starts await tester.pump(new Duration(seconds: 1)); // animation done expect(find.text('BottomSheet'), findsOneWidget); + expect(showBottomSheetThenCalled, isFalse); // Tap above the the bottom sheet to dismiss it await tester.tapAt(new Point(20.0, 20.0)); await tester.pump(); // bottom sheet dismiss animation starts + expect(showBottomSheetThenCalled, isTrue); await tester.pump(new Duration(seconds: 1)); // animation done await tester.pump(new Duration(seconds: 1)); // rebuild frame expect(find.text('BottomSheet'), findsNothing); diff --git a/packages/flutter/test/widget/positioned_test.dart b/packages/flutter/test/widget/positioned_test.dart index 311e673b9e9..1ae39d402b6 100644 --- a/packages/flutter/test/widget/positioned_test.dart +++ b/packages/flutter/test/widget/positioned_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -53,22 +55,31 @@ void main() { ) ); // t=0 recordMetrics(); - controller.forward(); + Completer completer = new Completer(); + controller.forward().whenComplete(completer.complete); + expect(completer.isCompleted, isFalse); await tester.pump(); // t=0 again + expect(completer.isCompleted, isFalse); recordMetrics(); await tester.pump(const Duration(seconds: 1)); // t=1 + expect(completer.isCompleted, isFalse); recordMetrics(); await tester.pump(const Duration(seconds: 1)); // t=2 + expect(completer.isCompleted, isFalse); recordMetrics(); await tester.pump(const Duration(seconds: 3)); // t=5 + expect(completer.isCompleted, isFalse); recordMetrics(); await tester.pump(const Duration(seconds: 5)); // t=10 + expect(completer.isCompleted, isFalse); recordMetrics(); expect(sizes, equals([const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0), const Size(10.0, 10.0)])); expect(positions, equals([const Offset(10.0, 10.0), const Offset(10.0, 10.0), const Offset(17.0, 17.0), const Offset(24.0, 24.0), const Offset(45.0, 45.0), const Offset(80.0, 80.0)])); controller.stop(); + await tester.pump(); + expect(completer.isCompleted, isTrue); }); } diff --git a/packages/flutter/test/widget/remember_scroll_position_test.dart b/packages/flutter/test/widget/remember_scroll_position_test.dart index 73bcb53647e..6c64127bfcc 100644 --- a/packages/flutter/test/widget/remember_scroll_position_test.dart +++ b/packages/flutter/test/widget/remember_scroll_position_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:meta/meta.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; @@ -57,8 +59,11 @@ Future performTest(WidgetTester tester, bool maintainState) async { expect(find.text('10'), findsNothing); expect(find.text('100'), findsNothing); - tester.state/**/(find.byType(Scrollable)).scrollTo(1000.0); + Completer completer = new Completer(); + tester.state/**/(find.byType(Scrollable)).scrollTo(1000.0).whenComplete(completer.complete); + expect(completer.isCompleted, isFalse); await tester.pump(new Duration(seconds: 1)); + expect(completer.isCompleted, isTrue); // we're 600 pixels high, each item is 100 pixels high, scroll position is // 1000, so we should have exactly 6 items, 10..15. diff --git a/packages/flutter/test/widget/scroll_events_test.dart b/packages/flutter/test/widget/scroll_events_test.dart index 8e4266dbea6..89519818716 100644 --- a/packages/flutter/test/widget/scroll_events_test.dart +++ b/packages/flutter/test/widget/scroll_events_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; @@ -22,6 +24,14 @@ Widget _buildScroller({Key key, List log}) { } void main() { + GlobalKey> scrollKey; + + Completer scrollTo(double newScrollOffset, { Duration duration }) { + Completer completer = new Completer(); + scrollKey.currentState.scrollTo(newScrollOffset, duration: duration).whenComplete(completer.complete); + return completer; + } + testWidgets('Scroll event drag', (WidgetTester tester) async { List log = []; await tester.pumpWidget(_buildScroller(log: log)); @@ -42,12 +52,13 @@ void main() { }); testWidgets('Scroll scrollTo animation', (WidgetTester tester) async { - GlobalKey> scrollKey = new GlobalKey>(); + scrollKey = new GlobalKey>(); List log = []; await tester.pumpWidget(_buildScroller(key: scrollKey, log: log)); expect(log, equals([])); - scrollKey.currentState.scrollTo(100.0, duration: const Duration(seconds: 1)); + Completer completer = scrollTo(100.0, duration: const Duration(seconds: 1)); + expect(completer.isCompleted, isFalse); expect(log, equals(['scrollstart'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart'])); @@ -55,60 +66,74 @@ void main() { expect(log, equals(['scrollstart', 'scroll'])); await tester.pump(const Duration(milliseconds: 1500)); expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend'])); + expect(completer.isCompleted, isTrue); }); testWidgets('Scroll scrollTo no animation', (WidgetTester tester) async { - GlobalKey> scrollKey = new GlobalKey>(); + scrollKey = new GlobalKey>(); List log = []; await tester.pumpWidget(_buildScroller(key: scrollKey, log: log)); expect(log, equals([])); - scrollKey.currentState.scrollTo(100.0); + Completer completer = scrollTo(100.0); + expect(completer.isCompleted, isFalse); expect(log, equals(['scrollstart', 'scroll', 'scrollend'])); + await tester.pump(); + expect(completer.isCompleted, isTrue); }); testWidgets('Scroll during animation', (WidgetTester tester) async { - GlobalKey> scrollKey = new GlobalKey>(); + scrollKey = new GlobalKey>(); List log = []; await tester.pumpWidget(_buildScroller(key: scrollKey, log: log)); expect(log, equals([])); - scrollKey.currentState.scrollTo(100.0, duration: const Duration(seconds: 1)); + Completer completer = scrollTo(100.0, duration: const Duration(seconds: 1)); + expect(completer.isCompleted, isFalse); expect(log, equals(['scrollstart'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart', 'scroll'])); - scrollKey.currentState.scrollTo(100.0); + expect(completer.isCompleted, isFalse); + + completer = scrollTo(100.0); + expect(completer.isCompleted, isFalse); expect(log, equals(['scrollstart', 'scroll', 'scroll'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend'])); await tester.pump(const Duration(milliseconds: 1500)); expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend'])); + expect(completer.isCompleted, isTrue); }); testWidgets('Scroll during animation', (WidgetTester tester) async { - GlobalKey> scrollKey = new GlobalKey>(); + scrollKey = new GlobalKey>(); List log = []; await tester.pumpWidget(_buildScroller(key: scrollKey, log: log)); expect(log, equals([])); - scrollKey.currentState.scrollTo(100.0, duration: const Duration(seconds: 1)); + Completer completer = scrollTo(100.0, duration: const Duration(seconds: 1)); + expect(completer.isCompleted, isFalse); expect(log, equals(['scrollstart'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart', 'scroll'])); - scrollKey.currentState.scrollTo(100.0, duration: const Duration(seconds: 1)); + expect(completer.isCompleted, isFalse); + + completer = scrollTo(100.0, duration: const Duration(seconds: 1)); + expect(completer.isCompleted, isFalse); expect(log, equals(['scrollstart', 'scroll'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(['scrollstart', 'scroll'])); await tester.pump(const Duration(milliseconds: 1500)); expect(log, equals(['scrollstart', 'scroll', 'scroll', 'scrollend'])); + expect(completer.isCompleted, isTrue); }); testWidgets('fling, fling generates two start/end pairs', (WidgetTester tester) async { - GlobalKey> scrollKey = new GlobalKey>(); + scrollKey = new GlobalKey>(); List log = []; await tester.pumpWidget(_buildScroller(key: scrollKey, log: log)); @@ -127,7 +152,7 @@ void main() { }); testWidgets('fling up ends', (WidgetTester tester) async { - GlobalKey> scrollKey = new GlobalKey>(); + scrollKey = new GlobalKey>(); List log = []; await tester.pumpWidget(_buildScroller(key: scrollKey, log: log)); diff --git a/packages/flutter/test/widget/scrollable_lazy_list_test.dart b/packages/flutter/test/widget/scrollable_lazy_list_test.dart index 5dcd89ee65c..090790d373f 100644 --- a/packages/flutter/test/widget/scrollable_lazy_list_test.dart +++ b/packages/flutter/test/widget/scrollable_lazy_list_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; @@ -85,6 +87,11 @@ void main() { ), right: new Text('Not Today') ); + Completer scrollTo(double newScrollOffset) { + Completer completer = new Completer(); + scrollableKey.currentState.scrollTo(newScrollOffset).whenComplete(completer.complete); + return completer; + } await tester.pumpWidget(testWidget); @@ -92,12 +99,14 @@ void main() { callbackTracker.clear(); - scrollableKey.currentState.scrollTo(400.0); + Completer completer = scrollTo(400.0); + expect(completer.isCompleted, isFalse); // now only 3 should fit, numbered 2-4. await tester.pumpWidget(testWidget); expect(callbackTracker, equals([2, 3, 4])); + expect(completer.isCompleted, isTrue); callbackTracker.clear(); }); @@ -134,6 +143,11 @@ void main() { ), right: new Text('Not Today') ); + Completer scrollTo(double newScrollOffset) { + Completer completer = new Completer(); + scrollableKey.currentState.scrollTo(newScrollOffset).whenComplete(completer.complete); + return completer; + } await tester.pumpWidget(testWidget); @@ -141,12 +155,14 @@ void main() { callbackTracker.clear(); - scrollableKey.currentState.scrollTo(400.0); + Completer completer = scrollTo(400.0); + expect(completer.isCompleted, isFalse); // now only 4 should fit, numbered 2-5. await tester.pumpWidget(testWidget); expect(callbackTracker, equals([2, 3, 4, 5])); + expect(completer.isCompleted, isTrue); callbackTracker.clear(); }); @@ -174,24 +190,35 @@ void main() { itemExtent: 300.0, itemCount: 10 ); + Completer scrollTo(double newScrollOffset) { + Completer completer = new Completer(); + scrollableKey.currentState.scrollTo(newScrollOffset).whenComplete(completer.complete); + return completer; + } await tester.pumpWidget(testWidget); expect(callbackTracker, equals([0, 1])); callbackTracker.clear(); - scrollableKey.currentState.scrollTo(150.0); + Completer completer = scrollTo(150.0); + expect(completer.isCompleted, isFalse); await tester.pumpWidget(testWidget); expect(callbackTracker, equals([0, 1, 2])); + expect(completer.isCompleted, isTrue); callbackTracker.clear(); - scrollableKey.currentState.scrollTo(600.0); + completer = scrollTo(600.0); + expect(completer.isCompleted, isFalse); await tester.pumpWidget(testWidget); expect(callbackTracker, equals([2, 3])); + expect(completer.isCompleted, isTrue); callbackTracker.clear(); - scrollableKey.currentState.scrollTo(750.0); + completer = scrollTo(750.0); + expect(completer.isCompleted, isFalse); await tester.pumpWidget(testWidget); expect(callbackTracker, equals([2, 3, 4])); + expect(completer.isCompleted, isTrue); callbackTracker.clear(); }); diff --git a/packages/flutter/test/widget/snap_scrolling_test.dart b/packages/flutter/test/widget/snap_scrolling_test.dart index 7369ce0b55e..dda4984acc7 100644 --- a/packages/flutter/test/widget/snap_scrolling_test.dart +++ b/packages/flutter/test/widget/snap_scrolling_test.dart @@ -46,8 +46,10 @@ set scrollOffset(double value) { scrollableState.scrollTo(value); } -Future fling(double velocity) { - return scrollableState.fling(velocity); +Completer fling(double velocity) { + Completer completer = new Completer(); + scrollableState.fling(velocity).whenComplete(completer.complete); + return completer; } void main() { @@ -60,49 +62,55 @@ void main() { Duration dt = const Duration(seconds: 2); - fling(1000.0); + Completer completer = fling(1000.0); + expect(completer.isCompleted, isFalse); await tester.pump(); // Start the scheduler at 0.0 await tester.pump(dt); expect(scrollOffset, closeTo(200.0, 1.0)); + expect(completer.isCompleted, isTrue); scrollOffset = 0.0; await tester.pump(); expect(scrollOffset, 0.0); - fling(2000.0); + completer = fling(2000.0); + expect(completer.isCompleted, isFalse); await tester.pump(); await tester.pump(dt); expect(scrollOffset, closeTo(400.0, 1.0)); + expect(completer.isCompleted, isTrue); scrollOffset = 400.0; await tester.pump(); expect(scrollOffset, 400.0); - fling(-800.0); + completer = fling(-800.0); + expect(completer.isCompleted, isFalse); await tester.pump(); await tester.pump(dt); expect(scrollOffset, closeTo(0.0, 1.0)); + expect(completer.isCompleted, isTrue); scrollOffset = 800.0; await tester.pump(); expect(scrollOffset, 800.0); - fling(-2000.0); + completer = fling(-2000.0); + expect(completer.isCompleted, isFalse); await tester.pump(); await tester.pump(dt); expect(scrollOffset, closeTo(200.0, 1.0)); + expect(completer.isCompleted, isTrue); scrollOffset = 800.0; await tester.pump(); expect(scrollOffset, 800.0); - bool completed = false; - fling(-2000.0).then((_) { - completed = true; - expectSync(scrollOffset, closeTo(200.0, 1.0)); - }); + completer = fling(-2000.0); + expect(completer.isCompleted, isFalse); await tester.pump(); await tester.pump(dt); - expect(completed, true); + expect(completer.isCompleted, isTrue); + expectSync(scrollOffset, closeTo(200.0, 1.0)); }); }