Add WidgetTester.hasRunningAnimation (#8886)

Fixes #4017
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index 8d127c7..9b43aff 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -222,10 +222,15 @@
       do {
         await binding.pump(duration, phase);
         count += 1;
-      } while (binding.transientCallbackCount > 0);
+      } while (hasRunningAnimations);
     }).then<int>((Null _) => count);
   }
 
+  /// Whether ther are any any transient callbacks scheduled.
+  ///
+  /// This essentially checks whether all animations have completed.
+  bool get hasRunningAnimations => binding.transientCallbackCount > 0;
+
   @override
   HitTestResult hitTestOnBinding(Point location) {
     location = binding.localToGlobal(location);
diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart
index e4253f7..9bf278b 100644
--- a/packages/flutter_test/test/widget_tester_test.dart
+++ b/packages/flutter_test/test/widget_tester_test.dart
@@ -185,4 +185,20 @@
       );
     });
   });
+
+  testWidgets('hasRunningAnimations control test', (WidgetTester tester) async {
+    final AnimationController controller = new AnimationController(
+      duration: const Duration(seconds: 1),
+      vsync: const TestVSync()
+    );
+    expect(tester.hasRunningAnimations, isFalse);
+    controller.forward();
+    expect(tester.hasRunningAnimations, isTrue);
+    controller.stop();
+    expect(tester.hasRunningAnimations, isFalse);
+    controller.forward();
+    expect(tester.hasRunningAnimations, isTrue);
+    await tester.pumpAndSettle();
+    expect(tester.hasRunningAnimations, isFalse);
+  });
 }