// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// ignore_for_file: unawaited_futures

import 'dart:async';

import 'package:async/async.dart';
import 'package:test/test.dart';

import 'utils.dart';

void main() {
  group('without being canceled', () {
    late CancelableCompleter completer;
    setUp(() {
      completer = CancelableCompleter(onCancel: expectAsync0(() {}, count: 0));
    });

    test('sends values to the future', () {
      expect(completer.operation.value, completion(equals(1)));
      expect(completer.isCompleted, isFalse);
      completer.complete(1);
      expect(completer.isCompleted, isTrue);
    });

    test('sends null values to the future', () {
      expect(completer.operation.value, completion(equals(null)));
      expect(completer.isCompleted, isFalse);
      completer.complete(null);
      expect(completer.isCompleted, isTrue);
    });

    test('sends errors to the future', () {
      expect(completer.operation.value, throwsA('error'));
      expect(completer.isCompleted, isFalse);
      completer.completeError('error');
      expect(completer.isCompleted, isTrue);
    });

    test('sends values in a future to the future', () {
      expect(completer.operation.value, completion(equals(1)));
      expect(completer.isCompleted, isFalse);
      completer.complete(Future.value(1));
      expect(completer.isCompleted, isTrue);
    });

    test('sends errors in a future to the future', () async {
      expect(completer.operation.value, throwsA('error'));
      expect(completer.isCompleted, isFalse);
      expect(completer.operation.isCompleted, isFalse);
      completer.complete(Future.error('error'));
      expect(completer.isCompleted, isTrue);
      await flushMicrotasks();
      expect(completer.operation.isCompleted, isTrue);
    });

    test('sends values from a cancelable operation to the future', () {
      expect(completer.operation.value, completion(equals(1)));
      completer
          .completeOperation(CancelableOperation.fromFuture(Future.value(1)));
    });

    test('sends values from a completed cancelable operation to the future',
        () async {
      final operation = CancelableOperation.fromFuture(Future.value(1));
      await operation.value;
      expect(completer.operation.value, completion(equals(1)));
      completer.completeOperation(operation);
    });

    test('sends errors from a cancelable operation to the future', () {
      expect(completer.operation.value, throwsA('error'));
      completer.completeOperation(
          CancelableOperation.fromFuture(Future.error('error')..ignore()));
    });

    test('sends errors from a completed cancelable operation to the future',
        () async {
      final operation =
          CancelableOperation.fromFuture(Future.error('error')..ignore());
      try {
        await operation.value;
      } on Object {
        // ignore
      }
      expect(completer.operation.value, throwsA('error'));
      completer.completeOperation(operation);
    });

    test('sends values to valueOrCancellation', () {
      expect(completer.operation.valueOrCancellation(), completion(equals(1)));
      completer.complete(1);
    });

    test('sends errors to valueOrCancellation', () {
      expect(completer.operation.valueOrCancellation(), throwsA('error'));
      completer.completeError('error');
    });

    test('chains null values through .then calls', () async {
      var operation = CancelableOperation.fromFuture(Future.value(null));
      expect(await operation.then((_) {}).value, null);
    });

    test('is not complete until the result is available', () async {
      var backingWork = Completer<void>();
      var operation = CancelableOperation.fromFuture(backingWork.future);
      expect(operation.isCompleted, isFalse);
      backingWork.complete();
      await backingWork.future;
      expect(operation.isCompleted, isTrue);
    });

    group('throws a StateError if completed', () {
      test('successfully twice', () {
        completer.complete(1);
        expect(() => completer.complete(1), throwsStateError);
      });

      test('successfully then unsuccessfully', () {
        completer.complete(1);
        expect(() => completer.completeError('error'), throwsStateError);
      });

      test('unsuccessfully twice', () {
        expect(completer.operation.value, throwsA('error'));
        completer.completeError('error');
        expect(() => completer.completeError('error'), throwsStateError);
      });

      test('successfully then with a future', () {
        completer.complete(1);
        expect(() => completer.complete(Completer<void>().future),
            throwsStateError);
      });

      test('with a future then successfully', () {
        completer.complete(Completer<void>().future);
        expect(() => completer.complete(1), throwsStateError);
      });

      test('with a future twice', () {
        completer.complete(Completer<void>().future);
        expect(() => completer.complete(Completer<void>().future),
            throwsStateError);
      });
    });

    group('CancelableOperation.fromFuture', () {
      test('forwards values', () {
        var operation = CancelableOperation.fromFuture(Future.value(1));
        expect(operation.value, completion(equals(1)));
      });

      test('forwards errors', () {
        var operation = CancelableOperation.fromFuture(Future.error('error'));
        expect(operation.value, throwsA('error'));
      });
    });

    group('CancelableOperation.fromSubscription', () {
      test('forwards a done event once it completes', () async {
        var controller = StreamController<void>();
        var operationCompleted = false;
        CancelableOperation.fromSubscription(controller.stream.listen(null))
            .then((_) {
          operationCompleted = true;
        });

        await flushMicrotasks();
        expect(operationCompleted, isFalse);

        controller.close();
        await flushMicrotasks();
        expect(operationCompleted, isTrue);
      });

      test('forwards errors', () {
        var operation = CancelableOperation.fromSubscription(
            Stream.error('error').listen(null));
        expect(operation.value, throwsA('error'));
      });
    });
  });

  group('when canceled', () {
    test('causes the future never to fire', () async {
      var completer = CancelableCompleter<void>();
      completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
      completer.operation.cancel();

      // Give the future plenty of time to fire if it's going to.
      await flushMicrotasks();
      completer.complete();
      await flushMicrotasks();
    });

    test('fires onCancel', () {
      var canceled = false;
      late CancelableCompleter completer;
      completer = CancelableCompleter(onCancel: expectAsync0(() {
        expect(completer.isCanceled, isTrue);
        canceled = true;
      }));

      expect(canceled, isFalse);
      expect(completer.isCanceled, isFalse);
      expect(completer.operation.isCanceled, isFalse);
      expect(completer.isCompleted, isFalse);
      expect(completer.operation.isCompleted, isFalse);
      completer.operation.cancel();
      expect(canceled, isTrue);
      expect(completer.isCanceled, isTrue);
      expect(completer.operation.isCanceled, isTrue);
      expect(completer.isCompleted, isFalse);
      expect(completer.operation.isCompleted, isFalse);
    });

    test('returns the onCancel future each time cancel is called', () {
      var completer = CancelableCompleter(onCancel: expectAsync0(() {
        return Future.value(1);
      }));
      expect(completer.operation.cancel(), completion(equals(1)));
      expect(completer.operation.cancel(), completion(equals(1)));
      expect(completer.operation.cancel(), completion(equals(1)));
    });

    test("returns a future even if onCancel doesn't", () {
      var completer = CancelableCompleter(onCancel: expectAsync0(() {}));
      expect(completer.operation.cancel(), completes);
    });

    test("doesn't call onCancel if the completer has completed", () {
      var completer =
          CancelableCompleter(onCancel: expectAsync0(() {}, count: 0));
      completer.complete(1);
      expect(completer.operation.value, completion(equals(1)));
      expect(completer.operation.cancel(), completes);
    });

    test(
        'does call onCancel if the completer has completed to an unfired '
        'Future', () {
      var completer = CancelableCompleter(onCancel: expectAsync0(() {}));
      completer.complete(Completer<void>().future);
      expect(completer.operation.cancel(), completes);
    });

    test(
        "doesn't call onCancel if the completer has completed to a fired "
        'Future', () async {
      var completer =
          CancelableCompleter(onCancel: expectAsync0(() {}, count: 0));
      completer.complete(Future.value(1));
      await completer.operation.value;
      expect(completer.operation.cancel(), completes);
    });

    test('can be completed once after being canceled', () async {
      var completer = CancelableCompleter<int>();
      completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
      await completer.operation.cancel();
      completer.complete(1);
      expect(() => completer.complete(1), throwsStateError);
    });

    test('fires valueOrCancellation with the given value', () {
      var completer = CancelableCompleter<int>();
      expect(completer.operation.valueOrCancellation(1), completion(equals(1)));
      completer.operation.cancel();
    });

    test('pipes an error through valueOrCancellation', () {
      var completer = CancelableCompleter(onCancel: () {
        throw 'error';
      });
      expect(completer.operation.valueOrCancellation(1), throwsA('error'));
      completer.operation.cancel();
    });

    test('valueOrCancellation waits on the onCancel future', () async {
      var innerCompleter = Completer<void>();
      var completer =
          CancelableCompleter(onCancel: () => innerCompleter.future);

      var fired = false;
      completer.operation.valueOrCancellation().then((_) {
        fired = true;
      });

      completer.operation.cancel();
      await flushMicrotasks();
      expect(fired, isFalse);

      innerCompleter.complete();
      await flushMicrotasks();
      expect(fired, isTrue);
    });

    test('CancelableOperation.fromSubscription() cancels the subscription',
        () async {
      var cancelCompleter = Completer<void>();
      var canceled = false;
      var controller = StreamController<void>(onCancel: () {
        canceled = true;
        return cancelCompleter.future;
      });
      var operation =
          CancelableOperation.fromSubscription(controller.stream.listen(null));

      await flushMicrotasks();
      expect(canceled, isFalse);

      // The `cancel()` call shouldn't complete until
      // `StreamSubscription.cancel` completes.
      var cancelCompleted = false;
      expect(
          operation.cancel().then((_) {
            cancelCompleted = true;
          }),
          completes);
      await flushMicrotasks();
      expect(canceled, isTrue);
      expect(cancelCompleted, isFalse);

      cancelCompleter.complete();
      await flushMicrotasks();
      expect(cancelCompleted, isTrue);
    });

    group('completeOperation', () {
      test('sends cancellation from a cancelable operation', () async {
        final completer = CancelableCompleter<void>();
        completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
        completer
            .completeOperation(CancelableCompleter<void>().operation..cancel());
        await completer.operation.valueOrCancellation();
        expect(completer.operation.isCanceled, true);
      });

      test('sends errors from a completed cancelable operation to the future',
          () async {
        final operation = CancelableCompleter<void>().operation..cancel();
        await operation.valueOrCancellation();
        final completer = CancelableCompleter<void>();
        completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
        completer.completeOperation(operation);
        await completer.operation.valueOrCancellation();
        expect(completer.operation.isCanceled, true);
      });

      test('propagates cancellation', () {
        final completer = CancelableCompleter<void>();
        final operation =
            CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 1))
                .operation;
        completer.completeOperation(operation);
        completer.operation.cancel();
      });

      test('propagates cancellation from already canceld completer', () async {
        final completer = CancelableCompleter<void>()..operation.cancel();
        await completer.operation.valueOrCancellation();
        final operation =
            CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 1))
                .operation;
        completer.completeOperation(operation);
      });
      test('cancel propagation can be disabled', () {
        final completer = CancelableCompleter<void>();
        final operation =
            CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 0))
                .operation;
        completer.completeOperation(operation, propagateCancel: false);
        completer.operation.cancel();
      });

      test('cancel propagation can be disabled from already canceled completed',
          () async {
        final completer = CancelableCompleter<void>()..operation.cancel();
        await completer.operation.valueOrCancellation();
        final operation =
            CancelableCompleter<void>(onCancel: expectAsync0(() {}, count: 0))
                .operation;
        completer.completeOperation(operation, propagateCancel: false);
      });
    });
  });

  group('asStream()', () {
    test('emits a value and then closes', () {
      var completer = CancelableCompleter<int>();
      expect(completer.operation.asStream().toList(), completion(equals([1])));
      completer.complete(1);
    });

    test('emits an error and then closes', () {
      var completer = CancelableCompleter<void>();
      var queue = StreamQueue(completer.operation.asStream());
      expect(queue.next, throwsA('error'));
      expect(queue.hasNext, completion(isFalse));
      completer.completeError('error');
    });

    test('cancels the completer when the subscription is canceled', () {
      var completer = CancelableCompleter(onCancel: expectAsync0(() {}));
      var sub =
          completer.operation.asStream().listen(expectAsync1((_) {}, count: 0));
      completer.operation.value.whenComplete(expectAsync0(() {}, count: 0));
      sub.cancel();
      expect(completer.isCanceled, isTrue);
    });
  });

  group('then', () {
    FutureOr<String> Function(int)? onValue;
    FutureOr<String> Function(Object, StackTrace)? onError;
    FutureOr<String> Function()? onCancel;
    late bool propagateCancel;
    late CancelableCompleter<int> originalCompleter;

    setUp(() {
      // Initialize all functions to ones that expect to not be called.
      onValue = expectAsync1((_) => 'Fake', count: 0, id: 'onValue');
      onError = expectAsync2((e, s) => 'Fake', count: 0, id: 'onError');
      onCancel = expectAsync0(() => 'Fake', count: 0, id: 'onCancel');
      propagateCancel = false;
      originalCompleter = CancelableCompleter<int>();
    });

    CancelableOperation<String> runThen() {
      return originalCompleter.operation.then(onValue!,
          onError: onError,
          onCancel: onCancel,
          propagateCancel: propagateCancel);
    }

    group('original operation completes successfully', () {
      test('onValue completes successfully', () {
        onValue = expectAsync1((v) => v.toString(), count: 1, id: 'onValue');

        expect(runThen().value, completion('1'));
        originalCompleter.complete(1);
      });

      test('onValue throws error', () {
        // expectAsync1 only works with functions that do not throw.
        onValue = (_) => throw 'error';

        expect(runThen().value, throwsA('error'));
        originalCompleter.complete(1);
      });

      test('onValue returns Future that throws error', () {
        onValue =
            expectAsync1((v) => Future.error('error'), count: 1, id: 'onValue');

        expect(runThen().value, throwsA('error'));
        originalCompleter.complete(1);
      });

      test('and returned operation is canceled with propagateCancel = false',
          () async {
        propagateCancel = false;

        runThen().cancel();

        // onValue should not be called.
        originalCompleter.complete(1);
      });
    });

    group('original operation completes with error', () {
      test('onError not set', () {
        onError = null;

        expect(runThen().value, throwsA('error'));
        originalCompleter.completeError('error');
      });

      test('onError completes successfully', () {
        onError = expectAsync2((e, s) => 'onError caught $e',
            count: 1, id: 'onError');

        expect(runThen().value, completion('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('onError throws', () {
        // expectAsync2 does not work with functions that throw.
        onError = (e, s) => throw 'onError caught $e';

        expect(runThen().value, throwsA('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('onError returns Future that throws', () {
        onError = expectAsync2((e, s) => Future.error('onError caught $e'),
            count: 1, id: 'onError');

        expect(runThen().value, throwsA('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('and returned operation is canceled with propagateCancel = false',
          () async {
        propagateCancel = false;

        runThen().cancel();

        // onError should not be called.
        originalCompleter.completeError('error');
      });
    });

    group('original operation canceled', () {
      test('onCancel not set', () async {
        onCancel = null;

        final operation = runThen();

        await expectLater(originalCompleter.operation.cancel(), completes);
        expect(operation.isCanceled, true);
      });

      test('onCancel completes successfully', () {
        onCancel = expectAsync0(() => 'canceled', count: 1, id: 'onCancel');

        expect(runThen().value, completion('canceled'));
        originalCompleter.operation.cancel();
      });

      test('onCancel throws error', () {
        // expectAsync0 only works with functions that do not throw.
        onCancel = () => throw 'error';

        expect(runThen().value, throwsA('error'));
        originalCompleter.operation.cancel();
      });

      test('onCancel returns Future that throws error', () {
        onCancel =
            expectAsync0(() => Future.error('error'), count: 1, id: 'onCancel');

        expect(runThen().value, throwsA('error'));
        originalCompleter.operation.cancel();
      });

      test('after completing with a future does not invoke `onValue`',
          () async {
        onValue = expectAsync1((_) => '', count: 0);
        onCancel = null;
        var operation = runThen();
        var workCompleter = Completer<int>();
        originalCompleter.complete(workCompleter.future);
        var cancelation = originalCompleter.operation.cancel();
        expect(originalCompleter.isCanceled, true);
        workCompleter.complete(0);
        await cancelation;
        expect(operation.isCanceled, true);
        await workCompleter.future;
      });

      test('after the value is completed invokes `onValue`', () {
        onValue = expectAsync1((_) => 'foo', count: 1);
        onCancel = expectAsync1((_) => '', count: 0);
        originalCompleter.complete(0);
        originalCompleter.operation.cancel();
        var operation = runThen();
        expect(operation.value, completion('foo'));
        expect(operation.isCanceled, false);
      });

      test('waits for chained cancellation', () async {
        var completer = CancelableCompleter<void>();
        var chainedOperation = completer.operation
            .then((_) => Future<void>.delayed(const Duration(milliseconds: 1)))
            .then((_) => Future<void>.delayed(const Duration(milliseconds: 1)));

        await completer.operation.cancel();
        expect(completer.operation.isCanceled, true);
        expect(chainedOperation.isCanceled, true);
      });
    });

    group('returned operation canceled', () {
      test('propagateCancel is true', () async {
        propagateCancel = true;

        await runThen().cancel();

        expect(originalCompleter.isCanceled, true);
      });

      test('propagateCancel is false', () async {
        propagateCancel = false;

        await runThen().cancel();

        expect(originalCompleter.isCanceled, false);
      });

      test('onValue callback not called after cancel', () async {
        var called = false;
        onValue = expectAsync1((_) {
          called = true;
          fail('onValue unreachable');
        }, count: 0);

        await runThen().cancel();
        originalCompleter.complete(0);
        await flushMicrotasks();
        expect(called, false);
      });

      test('onError callback not called after cancel', () async {
        var called = false;
        onError = expectAsync2((_, __) {
          called = true;
          fail('onError unreachable');
        }, count: 0);

        await runThen().cancel();
        originalCompleter.completeError('Error', StackTrace.empty);
        await flushMicrotasks();
        expect(called, false);
      });

      test('onCancel callback not called after cancel', () async {
        var called = false;
        onCancel = expectAsync0(() {
          called = true;
          fail('onCancel unreachable');
        }, count: 0);

        await runThen().cancel();
        await originalCompleter.operation.cancel();
        await flushMicrotasks();
        expect(called, false);
      });
    });
  });

  group('thenOperation', () {
    late void Function(int, CancelableCompleter<String>) onValue;
    void Function(Object, StackTrace, CancelableCompleter<String>)? onError;
    void Function(CancelableCompleter<String>)? onCancel;
    late bool propagateCancel;
    late CancelableCompleter<int> originalCompleter;

    setUp(() {
      // Initialize all functions to ones that expect to not be called.
      onValue = expectAsync2((value, completer) => completer.complete('$value'),
          count: 0, id: 'onValue');
      onError = null;
      onCancel = null;
      propagateCancel = false;
      originalCompleter = CancelableCompleter<int>();
    });

    CancelableOperation<String> runThenOperation() {
      return originalCompleter.operation.thenOperation(onValue,
          onError: onError,
          onCancel: onCancel,
          propagateCancel: propagateCancel);
    }

    group('original operation completes successfully', () {
      test('onValue completes successfully', () {
        onValue =
            expectAsync2((v, c) => c.complete('$v'), count: 1, id: 'onValue');

        expect(runThenOperation().value, completion('1'));
        originalCompleter.complete(1);
      });

      test('onValue throws error', () {
        // expectAsync1 only works with functions that do not throw.
        onValue = (_, __) => throw 'error';

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.complete(1);
      });

      test('onValue completes operation as error', () {
        onValue = expectAsync2(
            (_, completer) => completer.completeError('error'),
            count: 1,
            id: 'onValue');

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.complete(1);
      });

      test('onValue returns a Future that throws error', () {
        onValue = expectAsync2((_, completer) => Future.error('error'),
            count: 1, id: 'onValue');

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.complete(1);
      });

      test('and returned operation is canceled', () async {
        onValue = expectAsync2((_, __) => throw 'never called', count: 0);
        runThenOperation().cancel();
        // onValue should not be called.
        originalCompleter.complete(1);
      });
    });

    group('original operation completes with error', () {
      test('onError not set', () {
        onError = null;

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.completeError('error');
      });

      test('onError completes operation', () {
        onError = expectAsync3((e, s, c) => c.complete('onError caught $e'),
            count: 1, id: 'onError');

        expect(runThenOperation().value, completion('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('onError throws', () {
        // expectAsync3 does not work with functions that throw.
        onError = (e, s, c) => throw 'onError caught $e';

        expect(runThenOperation().value, throwsA('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('onError returns Future that throws error', () {
        onError = expectAsync3((e, s, c) => Future.error('onError caught $e'),
            count: 1, id: 'onError');

        expect(runThenOperation().value, throwsA('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('onError completes operation as an error', () {
        onError = expectAsync3(
            (e, s, c) => c.completeError('onError caught $e'),
            count: 1,
            id: 'onError');

        expect(runThenOperation().value, throwsA('onError caught error'));
        originalCompleter.completeError('error');
      });

      test('and returned operation is canceled with propagateCancel = false',
          () async {
        onError = expectAsync3((e, s, c) {}, count: 0);

        runThenOperation().cancel();

        // onError should not be called.
        originalCompleter.completeError('error');
      });
    });

    group('original operation canceled', () {
      test('onCancel not set', () async {
        onCancel = null;

        final operation = runThenOperation();

        await expectLater(originalCompleter.operation.cancel(), completes);
        expect(operation.isCanceled, true);
      });

      test('onCancel completes successfully', () {
        onCancel = expectAsync1((c) => c.complete('canceled'),
            count: 1, id: 'onCancel');

        expect(runThenOperation().value, completion('canceled'));
        originalCompleter.operation.cancel();
      });

      test('onCancel throws error', () {
        // expectAsync0 only works with functions that do not throw.
        onCancel = (_) => throw 'error';

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.operation.cancel();
      });

      test('onCancel completes operation as error', () {
        onCancel = expectAsync1((c) => c.completeError('error'),
            count: 1, id: 'onCancel');

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.operation.cancel();
      });

      test('onCancel returns Future that throws error', () {
        onCancel = expectAsync1((c) => Future.error('error'),
            count: 1, id: 'onCancel');

        expect(runThenOperation().value, throwsA('error'));
        originalCompleter.operation.cancel();
      });

      test('after completing with a future does not invoke `onValue`',
          () async {
        onValue = expectAsync2((_, __) {}, count: 0);
        onCancel = null;
        var operation = runThenOperation();
        var workCompleter = Completer<int>();
        originalCompleter.complete(workCompleter.future);
        var cancelation = originalCompleter.operation.cancel();
        expect(originalCompleter.isCanceled, true);
        workCompleter.complete(0);
        await cancelation;
        expect(operation.isCanceled, true);
        await workCompleter.future;
      });

      test('after the value is completed invokes `onValue`', () {
        onValue = expectAsync2((v, c) => c.complete('foo'), count: 1);
        onCancel = expectAsync1((_) {}, count: 0);
        originalCompleter.complete(0);
        originalCompleter.operation.cancel();
        var operation = runThenOperation();
        expect(operation.value, completion('foo'));
        expect(operation.isCanceled, false);
      });
    });

    group('returned operation canceled', () {
      test('propagateCancel is true', () async {
        propagateCancel = true;

        await runThenOperation().cancel();

        expect(originalCompleter.isCanceled, true);
      });

      test('propagateCancel is false', () async {
        propagateCancel = false;

        await runThenOperation().cancel();

        expect(originalCompleter.isCanceled, false);
      });

      test('onValue callback not called after cancel', () async {
        onValue = expectAsync2((_, c) {}, count: 0);

        await runThenOperation().cancel();
        originalCompleter.complete(0);
      });

      test('onError callback not called after cancel', () async {
        onError = expectAsync3((_, __, ___) {}, count: 0);

        await runThenOperation().cancel();
        originalCompleter.completeError('Error', StackTrace.empty);
      });

      test('onCancel callback not called after cancel', () async {
        onCancel = expectAsync1((_) {}, count: 0);

        await runThenOperation().cancel();
        await originalCompleter.operation.cancel();
      });
    });
  });

  group('race()', () {
    late bool canceled1;
    late CancelableCompleter<int> completer1;
    late bool canceled2;
    late CancelableCompleter<int> completer2;
    late bool canceled3;
    late CancelableCompleter<int> completer3;
    late CancelableOperation<int> operation;
    setUp(() {
      canceled1 = false;
      completer1 = CancelableCompleter<int>(onCancel: () {
        canceled1 = true;
      });

      canceled2 = false;
      completer2 = CancelableCompleter<int>(onCancel: () {
        canceled2 = true;
      });

      canceled3 = false;
      completer3 = CancelableCompleter<int>(onCancel: () {
        canceled3 = true;
      });

      operation = CancelableOperation.race(
          [completer1.operation, completer2.operation, completer3.operation]);
    });

    test('returns the first value to complete', () {
      completer1.complete(1);
      completer2.complete(2);
      completer3.complete(3);

      expect(operation.value, completion(equals(1)));
    });

    test('throws the first error to complete', () {
      completer1.completeError('error 1');
      completer2.completeError('error 2');
      completer3.completeError('error 3');

      expect(operation.value, throwsA('error 1'));
    });

    test('cancels any completers that haven\'t completed', () async {
      completer1.complete(1);
      await expectLater(operation.value, completion(equals(1)));
      expect(canceled1, isFalse);
      expect(canceled2, isTrue);
      expect(canceled3, isTrue);
    });

    test('cancels all completers when the operation is completed', () async {
      await operation.cancel();

      expect(canceled1, isTrue);
      expect(canceled2, isTrue);
      expect(canceled3, isTrue);
    });
  });
}
