angular/angular.js · error · Error

No pending animations ready to be closed or flushed

Error message

No pending animations ready to be closed or flushed

What it means

ngMock's mock $animate queues element animations; its flush() closes everything queued by repeatedly draining $$rAF and $$animateAsyncRun queues (looping until a pass flushes nothing) and finishes with a $digest. If a full pass flushes nothing and hideErrors is falsy, there was no queued animation work, so flush() throws 'No pending animations ready to be closed or flushed'.

Source

Thrown at src/ngMock/angular-mocks.js:1033

        flush: function(hideErrors) {
          $rootScope.$digest();

          var doNextRun, somethingFlushed = false;
          do {
            doNextRun = false;

            if ($$rAF.queue.length) {
              $$rAF.flush();
              doNextRun = somethingFlushed = true;
            }

            if ($$animateAsyncRun.flush()) {
              doNextRun = somethingFlushed = true;
            }
          } while (doNextRun);

          if (!somethingFlushed && !hideErrors) {
            throw new Error('No pending animations ready to be closed or flushed');
          }

          $rootScope.$digest();
        }
      };

      angular.forEach(
        ['animate','enter','leave','move','addClass','removeClass','setClass'], function(method) {
        animate[method] = function() {
          animate.queue.push({
            event: method,
            element: arguments[0],
            options: arguments[arguments.length - 1],
            args: arguments
          });
          return $delegate[method].apply($delegate, arguments);
        };
      });

View on GitHub (pinned to d8f77817eb)

Solutions

  1. Trigger the DOM/class change that queues the animation (run the scope/digest path that calls $animate) before calling $animate.flush().
  2. Guard the flush: only call it when $animate.queue.length > 0 or $$rAF.queue.length > 0.
  3. If an empty queue is legitimate in the test's flow, call $animate.flush(true) to skip the throw — but then you are only paying for the closing $digest.

Example fix

// before
it('adds .highlight', inject(function($animate, $rootScope) {
  $rootScope.$apply('vm.active = false'); // condition false: nothing queued
  $animate.flush(); // throws
}));

// after
it('adds .highlight', inject(function($animate, $rootScope) {
  $rootScope.$apply('vm.active = true'); // directive queues addClass
  $animate.flush();
}));
Defensive patterns

Strategy: validation

Validate before calling

// Only flush when animation work is actually queued
inject(function($animate) {
  if ($animate.queue.length > 0) {
    $animate.flush();
  }
});

Prevention

When it happens

Trigger: Calling $animate.flush() before any $animate operation (animate/enter/leave/move/addClass/removeClass/setClass) has queued work; calling flush after a previous flush already drained the queues; the animation was disabled ($animate.enabled(false)) or the element/class change never happened, so nothing queued.

Common situations: Testing a directive whose $animate.addClass only runs when a scope condition is true, but the test never made the condition true; the code path triggering the animation sits inside a $timeout or event handler that has not run yet; passing flush(true) suppresses the throw (hideErrors), which hides both the error and any assumption mistake.

Related errors


AI-assisted analysis of angular/angular.js@d8f77817eb (2026-08-21). Data as JSON: /api/errors/30bb18970af9d79d. Report an issue: GitHub.