Repository: adobe/react-spectrum
File: packages/@react-aria/test-utils/src/menu.ts (TypeScript)

Code before:
      act(() => {jest.runAllTimers();});

      if (option.getAttribute('href') == null && option.getAttribute('aria-haspopup') == null && menuSelectionMode === 'single' && closesOnSelect && keyboardActivation !== 'Space' && !this._isSubmenu) {
        await waitFor(() => {
          if (document.activeElement !== trigger) {
            throw new Error(`Expected the document.activeElement after selecting an option to be the menu trigger but got ${document.activeElement}`);
          } else {
            return true;
          }
        });

        if (document.contains(menu)) {
          throw new Error('Expected menu element to not be in the document after selecting an option');
        }
      }
      // else {
      //   // TODO: this is a bit unfortunate, but from a submenu point of view, we can't perform a check that waits for
      //   // focus to return to the original trigger since we don't have that information in the submenu trigger tester. Even if we did
      //   // it is a bit unpredicatable where focus might end up landing and waiting for the option/menu to disappear isn't sufficient if there are
      //   // more transitions in play. For now advance times by a certain amount of time and rely on the user to advance them even more if need be
      //   if (this._advanceTimer == null) {
      //     throw new Error('No advanceTimers provided for long press.');
      //   } else {
      //     await act(async () => await this._advanceTimer(1000));
      //   }
      // }
    } else {
      throw new Error("Attempted to select a option in the menu, but menu wasn't found.");
    }
  }

  // TODO: update this to remove needsLongPress if we wanna make the user call open first always
  /**
   * Opens the submenu. Defaults to using the interaction type set on the menu tester. The submenu trigger can be targeted via the trigger's node or the trigger's text.
   */
  async openSubmenu(opts: MenuOpenSubmenuOpts): Promise<MenuTester | null> {
    let {
      submenuTrigger,
      needsLongPress,
      interactionType = this._interactionType
    } = opts;

    let trigger = this.trigger;
    let isDisabled = trigger.hasAttribute('disabled');
    if (!trigger.getAttribute('aria-controls') && !isDisabled) {
      await this.open({needsLongPress});
    }
    if (!isDisabled) {
      let menu = this.menu;
      if (menu) {
        if (typeof submenuTrigger === 'string') {

Review comment (suggestion):
Did some poking around trying to get rid of the last `jest.runAllTimers()` here but no luck. The above is ok, but can extend test duration by quite a bit, also seemed to have some problems with hanging that I didn't quite figure out 

Code after:
        }
      }

      // This chain of waitFors is needed in place of running all timers since we don't know how long transitions may take, or what action
      // the menu option select may trigger.
      if (
        !(menuSelectionMode === 'single' && !closesOnSelect) &&
        !(menuSelectionMode === 'multiple' && (keyboardActivation === 'Space' || interactionType === 'mouse'))
      ) {
        // For RSP, clicking on a submenu option seems to briefly lose focus to the body before moving to the clicked option in the test so we need to wait
        // for focus to be coerced to somewhere else in place of running all timers.
        if (this._isSubmenu) {
          await waitFor(() => {
            if (document.activeElement === document.body) {
              throw new Error('Expected focus to move to somewhere other than the body after selecting a submenu option.');
            } else {
              return true;
            }
          });
        }

        // If user isn't trying to select multiple menu options or closeOnSelect is true then we can assume that
        // the menu will close or some action is triggered. In cases like that focus should move somewhere after the menu closes
        // but we can't really know where so just make sure it doesn't get lost to the body.
        await waitFor(() => {
          if (document.activeElement === option) {
            throw new Error('Expected focus after selecting an option to move away from the option.');
          } else {
            return true;
          }
        });

        // We'll also want to wait for focus to move away from the original submenu trigger since the entire submenu tree should
        // close. In React 16, focus actually makes it all the way to the root menu's submenu trigger so we need check the root menu
        if (this._isSubmenu) {
          await waitFor(() => {
            if (document.activeElement === this.trigger || this._rootMenu?.contains(document.activeElement)) {
              throw new Error('Expected focus after selecting an submenu option to move away from the original submenu trigger.');
            } else {
              return true;
            }
          });
        }

        // Finally wait for focus to be coerced somewhere final when the menu tree is removed from the DOM
        await waitFor(() => {
          if (document.activeElement === document.body) {
            throw new Error('Expected focus to move to somewhere other than the body after selecting a menu option.');
          } else {
            return true;
          }