runIterativeFixture function

Future<void> runIterativeFixture(
  1. MontyPlatform platform,
  2. Map<String, dynamic> fixture
)

Runs an iterative (external functions) fixture through platform.

Handles resumeValues, resumeErrors, and async/futures paths.

Implementation

Future<void> runIterativeFixture(
  MontyPlatform platform,
  Map<String, dynamic> fixture,
) async {
  final code = fixture['code'] as String;
  final extFns = (fixture['externalFunctions'] as List).cast<String>();
  final resumeValues = (fixture['resumeValues'] as List?)?.cast<Object>();
  final resumeErrors = (fixture['resumeErrors'] as List?)?.cast<String>();
  final asyncResumeMap = fixture['asyncResumeMap'] as Map<String, dynamic>?;
  final asyncErrorMap = fixture['asyncErrorMap'] as Map<String, dynamic>?;
  final scriptName = fixture['scriptName'] as String?;
  final expectError = fixture['expectError'] as bool? ?? false;

  var progress = await platform.start(
    code,
    externalFunctions: extFns,
    scriptName: scriptName,
  );

  final callIds = <int>[];

  if (asyncResumeMap != null) {
    if (platform is! MontyFutureCapable) {
      markTestSkipped('Platform does not support MontyFutureCapable');

      return;
    }
    final futurePlatform = platform as MontyFutureCapable;
    try {
      while (progress is! MontyComplete) {
        if (progress is MontyPending) {
          callIds.add(progress.callId);
          progress = await futurePlatform.resumeAsFuture();
        } else if (progress is MontyResolveFutures) {
          final pending = progress.pendingCallIds;
          final results = <int, Object?>{};
          final errors = <int, String>{};
          for (final id in pending) {
            final key = id.toString();
            if (asyncErrorMap != null && asyncErrorMap.containsKey(key)) {
              errors[id] = asyncErrorMap[key] as String;
            } else if (asyncResumeMap.containsKey(key)) {
              results[id] = asyncResumeMap[key];
            }
          }
          progress = await futurePlatform.resolveFutures(
            results,
            errors: errors.isNotEmpty ? errors : null,
          );
        } else {
          fail('Unexpected progress type: $progress');
        }
      }
    } on MontyException catch (e) {
      if (expectError) {
        final errorContains = fixture['errorContains'] as String?;
        if (errorContains != null) {
          expect(
            e.message.contains(errorContains),
            isTrue,
            reason: 'Expected error containing "$errorContains", '
                'got: "${e.message}"',
          );
        }

        return;
      }
      rethrow;
    }

    if (expectError) {
      final errorContains = fixture['errorContains'] as String?;
      final completeResult = progress.result;
      expect(
        completeResult.error,
        isNotNull,
        reason: 'Fixture #${fixture['id']}: expected error result',
      );
      if (errorContains != null) {
        final errorMessage = completeResult.error?.message ?? '';
        expect(
          errorMessage.contains(errorContains),
          isTrue,
          reason: 'Expected error containing "$errorContains", '
              'got: "$errorMessage"',
        );
      }

      return;
    }
  } else if (resumeErrors != null) {
    for (var i = 0; i < resumeErrors.length; i++) {
      expect(progress, isA<MontyPending>());
      if (i == 0) assertPendingFields(progress as MontyPending, fixture);
      callIds.add((progress as MontyPending).callId);
      progress = await platform.resumeWithError(resumeErrors[i]);
    }
  } else if (resumeValues != null) {
    for (var i = 0; i < resumeValues.length; i++) {
      expect(progress, isA<MontyPending>());
      if (i == 0) assertPendingFields(progress as MontyPending, fixture);
      callIds.add((progress as MontyPending).callId);
      progress = await platform.resume(resumeValues[i]);
    }
  }

  if (fixture['expectedDistinctCallIds'] == true && callIds.length > 1) {
    expect(
      callIds.toSet().length,
      callIds.length,
      reason: 'Fixture #${fixture['id']}: expected distinct call_ids, '
          'got: $callIds',
    );
  }

  expect(progress, isA<MontyComplete>());
  final complete = progress as MontyComplete;
  assertLadderResult(complete.result.value, fixture);
}