waitAllUnordered method

Future<TestInfoList<St>> waitAllUnordered(
  1. List<Type> actionTypes, {
  2. int timeoutInSeconds = defaultTimeout,
  3. List<Type>? ignore,
})

The same as waitAllUnorderedGetLast, but instead of returning just the last info, it returns a list with the end info for each action.

If you pass action types to ignore, they will be ignored (the test won't fail when encountering them, and won't collect testInfo for them). This method will remember all ignored actions and wait for them to finish, so that they don't "leak" to the next wait. An action type cannot exist in both actionTypes and ignore lists.

If ignore is null, it will use the global ignore provided in the StoreTester constructor, if any. If ignore is an empty list, it will disable that global ignore.

Implementation

Future<TestInfoList<St>> waitAllUnordered(
  List<Type> actionTypes, {
  int timeoutInSeconds = defaultTimeout,
  List<Type>? ignore,
}) async {
  assert(actionTypes.isNotEmpty);
  ignore ??= _ignore;

  // Actions which are expected can't also be ignored.
  var intersection = ignore.toSet().intersection(actionTypes.toSet());
  if (intersection.isNotEmpty)
    throw StoreException("Actions $intersection "
        "should not be expected and ignored.");

  TestInfoList<St> infoList = TestInfoList<St>();
  List<Type> actionsIni = List.from(actionTypes);
  List<Type> actionsEnd = List.from(actionTypes);

  TestInfo<St>? testInfo;

  // Saves ignored actions INI.
  // Note: This relies on Actions not overriding operator ==.
  List<ReduxAction?> ignoredActions = [];

  while (actionsIni.isNotEmpty || actionsEnd.isNotEmpty) {
    try {
      testInfo = await _next(timeoutInSeconds: timeoutInSeconds);

      while (ignore.contains(testInfo!.type)) {
        //
        // Saves ignored actions.
        if (ignore.contains(testInfo.type)) {
          if (testInfo.isINI)
            ignoredActions.add(testInfo.action);
          else
            ignoredActions.remove(testInfo.action);
        }

        testInfo = await (_next(timeoutInSeconds: timeoutInSeconds));
      }
    } on StoreExceptionTimeout catch (error) {
      error.addDetail("These actions were not dispatched: "
          "$actionsIni INI.");
      error.addDetail("These actions haven't finished: "
          "$actionsEnd END.");
      rethrow;
    }

    var action = testInfo.type;

    if (testInfo.isINI) {
      if (!actionsIni.remove(action))
        throw StoreException("Unexpected action was dispatched: "
            "$action INI.");
    } else {
      if (!actionsEnd.remove(action))
        throw StoreException("Unexpected action was dispatched: "
            "$action END.");

      // Only save the END states.
      infoList._add(testInfo);
    }
  }

  // Wait for all ignored actions to finish, so that they don't "leak" to the next wait.
  while (ignoredActions.isNotEmpty) {
    testInfo = await _next();

    var wasIgnored = ignoredActions.remove(testInfo.action);

    if (!wasIgnored && ignore.contains(testInfo.type)) {
      if (testInfo.isINI)
        ignoredActions.add(testInfo.action);
      else
        ignoredActions.remove(testInfo.action);
      continue;
    }

    if (!testInfo.isEND || !wasIgnored)
      throw StoreException("Got this unexpected action: "
          "${testInfo.type} ${testInfo.ini ? "INI" : "END"}.");
  }

  lastInfo = infoList.last;

  return infoList;
}