diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 32d9b6f8b14..0de01e07202 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -141,6 +141,16 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// The default test timeout for tests when using this binding. test_package.Timeout get defaultTestTimeout; + /// The current time. + /// + /// In the automated test environment (`flutter test`), this is a fake clock + /// that begins in January 2015 at the start of the test and advances each + /// time [pump] is called with a non-zero duration. + /// + /// In the live testing environment (`flutter run`), this object shows the + /// actual current wall-clock time. + Clock get clock; + /// Triggers a frame sequence (build/layout/paint/etc), /// then flushes microtasks. /// @@ -466,6 +476,9 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { } FakeAsync _fakeAsync; + + @override + Clock get clock => _clock; Clock _clock; @override @@ -490,7 +503,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { _phase = newPhase; if (hasScheduledFrame) { handleBeginFrame(new Duration( - milliseconds: _clock.now().millisecondsSinceEpoch + milliseconds: _clock.now().millisecondsSinceEpoch, )); } _fakeAsync.flushMicrotasks(); @@ -615,6 +628,9 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { bool get inTest => _inTest; bool _inTest = false; + @override + Clock get clock => const Clock(); + @override int get microtaskCount { // Unsupported until we have a wrapper around the real async API diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index e9cc3effa0d..8fa900c13c8 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -198,9 +198,13 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// /// This essentially waits for all animations to have completed. /// - /// This function will never return (and the test will hang and eventually - /// time out and fail) if there is an infinite animation in progress (for - /// example, if there is an indeterminate progress indicator spinning). + /// If it takes longer that the given `timeout` to settle, then the test will + /// fail (this method will throw an exception). In particular, this means that + /// if there is an infinite animation in progress (for example, if there is an + /// indeterminate progress indicator spinning), this method will throw. + /// + /// The default timeout is ten minutes, which is longer than most reasonable + /// finite animations would last. /// /// If the function returns, it returns the number of pumps that it performed. /// @@ -213,13 +217,19 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// matches the expected number of pumps. Future pumpAndSettle([ Duration duration = const Duration(milliseconds: 100), - EnginePhase phase = EnginePhase.sendSemanticsTree + EnginePhase phase = EnginePhase.sendSemanticsTree, + Duration timeout = const Duration(minutes: 10), ]) { assert(duration != null); assert(duration > Duration.ZERO); + assert(timeout != null); + assert(timeout > Duration.ZERO); int count = 0; return TestAsyncUtils.guard(() async { + final DateTime endTime = binding.clock.fromNowBy(timeout); do { + if (binding.clock.now().isAfter(endTime)) + throw new FlutterError('pumpAndSettle timed out'); await binding.pump(duration, phase); count += 1; } while (hasRunningAnimations); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 9bf278b3824..f1c9ded4c14 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -201,4 +201,24 @@ void main() { await tester.pumpAndSettle(); expect(tester.hasRunningAnimations, isFalse); }); + + testWidgets('pumpAndSettle control test', (WidgetTester tester) async { + final AnimationController controller = new AnimationController( + duration: const Duration(minutes: 525600), + vsync: const TestVSync() + ); + expect(await tester.pumpAndSettle(), 1); + controller.forward(); + try { + await tester.pumpAndSettle(); + expect(true, isFalse); + } catch (e) { + expect(e, isFlutterError); + } + controller.stop(); + expect(await tester.pumpAndSettle(), 1); + controller.duration = const Duration(seconds: 1); + controller.forward(); + expect(await tester.pumpAndSettle(const Duration(milliseconds: 300)), 5); // 0, 300, 600, 900, 1200ms + }); }