async function passingThrough(state, doThis) {
      /** @type {!Set<string>} */
      const preLoadStates = new Set([
        'manifest-parser',
        'manifest',
        'drm-engine',
        // Excludes 'unload'.
      ]);

      /** @type {!Set<string>} */
      const postLoadStates = new Set([
        'unload',
      ]);

      // Only a subset of the possible states are actually intermediary states.
      const validState = preLoadStates.has(state) || postLoadStates.has(state);
      expect(validState).toBeTruthy();

      // We don't await the last action because it should not finish, however we
      // need to handle the failure or else Jasmine will fail with "Unhandled
      // rejection".
      if (preLoadStates.has(state)) {
        await player.attach(video);
        player.load('test:sintel').catch(() => {});
      } else {
        goog.asserts.assert(state == 'unload', 'Unrecognized testing state!');
        await player.attach(video);
        await player.load('test:sintel');
        player.unload().catch(() => {});
      }

      return new Promise((resolve, reject) => {
        let called = false;

        whenEnteringState(state, () => {
          // Make sure we don't execute more than once per promise.
          if (called) {
            return;
          }
          called = true;

          // We need to call doThis in-sync with entering the state so that it
          // can start in the same interpreter cycle. If we did not do this, the
          // player could have changed states before |doThis| was called.
          doThis().then(resolve, reject);
        });
      });
    }
Some states are intermediaries, making it impossible to "start" in them.
Instead this method calls |doThis| when we are passing through the state.
The goal of this method is to make it possible to test routing when the
current route is interrupted to go somewhere.

@param {string} state
@param {function():!Promise} doThis
@return {!Promise}