// 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.

import 'dart:async';

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

import 'utils.dart';

void main() {
  late FutureGroup futureGroup;
  setUp(() {
    futureGroup = FutureGroup();
  });

  group('with no futures', () {
    test('never completes if nothing happens', () async {
      var completed = false;
      futureGroup.future.then((_) => completed = true);

      await flushMicrotasks();
      expect(completed, isFalse);
    });

    test("completes once it's closed", () {
      expect(futureGroup.future, completion(isEmpty));
      expect(futureGroup.isClosed, isFalse);
      futureGroup.close();
      expect(futureGroup.isClosed, isTrue);
    });
  });

  group('with a future that already completed', () {
    test('never completes if nothing happens', () async {
      futureGroup.add(Future.value());
      await flushMicrotasks();

      var completed = false;
      futureGroup.future.then((_) => completed = true);

      await flushMicrotasks();
      expect(completed, isFalse);
    });

    test("completes once it's closed", () async {
      futureGroup.add(Future.value());
      await flushMicrotasks();

      expect(futureGroup.future, completes);
      expect(futureGroup.isClosed, isFalse);
      futureGroup.close();
      expect(futureGroup.isClosed, isTrue);
    });

    test("completes to that future's value", () {
      futureGroup.add(Future.value(1));
      futureGroup.close();
      expect(futureGroup.future, completion(equals([1])));
    });

    test("completes to that future's error, even if it's not closed", () {
      futureGroup.add(Future.error('error'));
      expect(futureGroup.future, throwsA('error'));
    });
  });

  test('completes once all contained futures complete', () async {
    var completer1 = Completer<void>();
    var completer2 = Completer<void>();
    var completer3 = Completer<void>();

    futureGroup.add(completer1.future);
    futureGroup.add(completer2.future);
    futureGroup.add(completer3.future);
    futureGroup.close();

    var completed = false;
    futureGroup.future.then((_) => completed = true);

    completer1.complete();
    await flushMicrotasks();
    expect(completed, isFalse);

    completer2.complete();
    await flushMicrotasks();
    expect(completed, isFalse);

    completer3.complete();
    await flushMicrotasks();
    expect(completed, isTrue);
  });

  test('completes to the values of the futures in order of addition', () {
    var completer1 = Completer<int>();
    var completer2 = Completer<int>();
    var completer3 = Completer<int>();

    futureGroup.add(completer1.future);
    futureGroup.add(completer2.future);
    futureGroup.add(completer3.future);
    futureGroup.close();

    // Complete the completers in reverse order to prove that that doesn't
    // affect the result order.
    completer3.complete(3);
    completer2.complete(2);
    completer1.complete(1);
    expect(futureGroup.future, completion(equals([1, 2, 3])));
  });

  test("completes to the first error to be emitted, even if it's not closed",
      () {
    var completer1 = Completer<void>();
    var completer2 = Completer<void>();
    var completer3 = Completer<void>();

    futureGroup.add(completer1.future);
    futureGroup.add(completer2.future);
    futureGroup.add(completer3.future);

    completer2.completeError('error 2');
    completer1.completeError('error 1');
    expect(futureGroup.future, throwsA('error 2'));
  });

  group('onIdle:', () {
    test('emits an event when the last pending future completes', () async {
      var idle = false;
      futureGroup.onIdle.listen((_) => idle = true);

      var completer1 = Completer<void>();
      var completer2 = Completer<void>();
      var completer3 = Completer<void>();

      futureGroup.add(completer1.future);
      futureGroup.add(completer2.future);
      futureGroup.add(completer3.future);

      await flushMicrotasks();
      expect(idle, isFalse);
      expect(futureGroup.isIdle, isFalse);

      completer1.complete();
      await flushMicrotasks();
      expect(idle, isFalse);
      expect(futureGroup.isIdle, isFalse);

      completer2.complete();
      await flushMicrotasks();
      expect(idle, isFalse);
      expect(futureGroup.isIdle, isFalse);

      completer3.complete();
      await flushMicrotasks();
      expect(idle, isTrue);
      expect(futureGroup.isIdle, isTrue);
    });

    test('emits an event each time it becomes idle', () async {
      var idle = false;
      futureGroup.onIdle.listen((_) => idle = true);

      var completer = Completer<void>();
      futureGroup.add(completer.future);

      completer.complete();
      await flushMicrotasks();
      expect(idle, isTrue);
      expect(futureGroup.isIdle, isTrue);

      idle = false;
      completer = Completer<void>();
      futureGroup.add(completer.future);

      await flushMicrotasks();
      expect(idle, isFalse);
      expect(futureGroup.isIdle, isFalse);

      completer.complete();
      await flushMicrotasks();
      expect(idle, isTrue);
      expect(futureGroup.isIdle, isTrue);
    });

    test('emits an event when the group closes', () async {
      // It's important that the order of events here stays consistent over
      // time, since code may rely on it in subtle ways.
      var idle = false;
      var onIdleDone = false;
      var futureFired = false;

      futureGroup.onIdle.listen(expectAsync1((_) {
        expect(futureFired, isFalse);
        idle = true;
      }), onDone: expectAsync0(() {
        expect(idle, isTrue);
        expect(futureFired, isFalse);
        onIdleDone = true;
      }));

      futureGroup.future.then(expectAsync1((_) {
        expect(idle, isTrue);
        expect(onIdleDone, isTrue);
        futureFired = true;
      }));

      var completer = Completer<void>();
      futureGroup.add(completer.future);
      futureGroup.close();

      await flushMicrotasks();
      expect(idle, isFalse);
      expect(futureGroup.isIdle, isFalse);

      completer.complete();
      await flushMicrotasks();
      expect(idle, isTrue);
      expect(futureGroup.isIdle, isTrue);
      expect(futureFired, isTrue);
    });
  });
}
