pump method
Advances time and completes a frame: build, layout, paint, then
post-frame callbacks. When duration is null, time does not advance.
SYNCHRONOUS: pump never turns the event loop, so it cannot observe
async work — a StreamBuilder/QueryBuilder whose first value arrives
on a microtask/timer still shows its loading state after pump(). Reach
for the async settle (or pumpAndSettle for animation-only trees) when
a test binds to async data; don't hand-roll an await Future.delayed +
pump loop.
Time is moved by stepping the scheduler at every
scheduler.frameInterval boundary across duration, firing
each registered Ticker / FrameTicker at each step (the same
cadence a real-world Timer.periodic would). A pump(300 ms)
at the default 33 ms cadence therefore delivers ~9 ticks, so
FrameTicker-driven widgets (typewriters, spinners, marquees)
advance the correct number of frames for the elapsed time —
rather than catching up just one frame regardless of duration.
Animations compute their value from absolute elapsed
time so this change is invisible to them.
Use tester.scheduler.advance(d) directly if you need the
low-level single-tick semantics (e.g. asserting one specific
frame emit).
Callbacks see the geometry just painted. A callback that schedules more
work leaves it for the next frame; pumping does not implicitly settle.
For build-only framework checks, use owner.flushBuild() explicitly.
Implementation
void pump([Duration? duration]) {
_assertNotDisposed('pump');
if (duration != null && duration > Duration.zero) {
final step = _scheduler.frameInterval;
var remaining = duration;
while (remaining > step) {
_scheduler.advance(step);
remaining -= step;
}
if (remaining > Duration.zero) {
_scheduler.advance(remaining);
}
}
if (_root != null) render();
_binding.flushPostFrameCallbacks(_clock.now);
}