pump method

void pump([
  1. Duration? duration
])

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);
}