Commit 1152fd60 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Adds tester.pumpUntilNoTransientCallbacks (#5696)

Fixes https://github.com/flutter/flutter/issues/5234
parent ae565547
......@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:test/test.dart' as test_package;
import 'all_elements.dart';
......@@ -154,10 +155,10 @@ class WidgetTester extends WidgetController implements HitTestDispatcher {
/// Renders the UI from the given [widget].
///
/// Calls [runApp] with the given widget, then triggers a frame sequence and
/// flushes microtasks, by calling [pump] with the same duration (if any).
/// The supplied [EnginePhase] is the final phase reached during the pump pass;
/// if not supplied, the whole pass is executed.
/// Calls [runApp] with the given widget, then triggers a frame and flushes
/// microtasks, by calling [pump] with the same `duration` (if any). The
/// supplied [EnginePhase] is the final phase reached during the pump pass; if
/// not supplied, the whole pass is executed.
Future<Null> pumpWidget(Widget widget, [
Duration duration,
EnginePhase phase = EnginePhase.sendSemanticsTree
......@@ -168,7 +169,11 @@ class WidgetTester extends WidgetController implements HitTestDispatcher {
});
}
/// Triggers a sequence of frames for [duration] amount of time.
/// Triggers a frame after `duration` amount of time.
///
/// This makes the framework act as if the application had janked (missed
/// frames) for `duration` amount of time, and then received a v-sync signal
/// to paint the application.
///
/// This is a convenience function that just calls
/// [TestWidgetsFlutterBinding.pump].
......@@ -180,6 +185,40 @@ class WidgetTester extends WidgetController implements HitTestDispatcher {
return TestAsyncUtils.guard(() => binding.pump(duration, phase));
}
/// Repeatedly calls [pump] with the given `duration` until there are no
/// longer any transient callbacks scheduled. If no transient callbacks are
/// scheduled when the function is called, it returns without calling [pump].
///
/// 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 the function returns, it returns the number of pumps that it performed.
///
/// In general, it is better practice to figure out exactly why each frame is
/// needed, and then to [pump] exactly as many frames as necessary. This will
/// help catch regressions where, for instance, an animation is being started
/// one frame later than it should.
///
/// Alternatively, one can check that the return value from this function
/// matches the expected number of pumps.
Future<int> pumpUntilNoTransientCallbacks([
@required Duration duration,
EnginePhase phase = EnginePhase.sendSemanticsTree
]) {
assert(duration != null);
assert(duration > Duration.ZERO);
int count = 0;
return TestAsyncUtils.guard(() async {
while (binding.transientCallbackCount > 0) {
await binding.pump(duration, phase);
count += 1;
}
}).then/*<int>*/((Null _) => count);
}
@override
HitTestResult hitTestOnBinding(Point location) {
location = binding.localToGlobal(location);
......
......@@ -68,6 +68,37 @@ void main() {
expect(message, contains('Actual: ?:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo")>\n'));
expect(message, contains('Which: means one was found but none were expected\n'));
});
testWidgets('pumping', (WidgetTester tester) async {
await tester.pumpWidget(new Text('foo'));
int count;
AnimationController test = new AnimationController(duration: const Duration(milliseconds: 5100));
count = await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
expect(count, 0);
test.forward(from: 0.0);
count = await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
// 1 frame at t=0, starting the animation
// 1 frame at t=1
// 1 frame at t=2
// 1 frame at t=3
// 1 frame at t=4
// 1 frame at t=5
// 1 frame at t=6, ending the animation
expect(count, 7);
test.forward(from: 0.0);
await tester.pump(); // starts the animation
count = await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
expect(count, 6);
test.forward(from: 0.0);
await tester.pump(); // starts the animation
await tester.pump(); // has no effect
count = await tester.pumpUntilNoTransientCallbacks(const Duration(seconds: 1));
expect(count, 6);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment