// Copyright (c) 2017, 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:stream_transform/stream_transform.dart';
import 'package:test/test.dart';

void main() {
  group('Scan', () {
    test('produces intermediate values', () async {
      var source = Stream.fromIterable([1, 2, 3, 4]);
      int sum(int x, int y) => x + y;
      var result = await source.scan(0, sum).toList();

      expect(result, [1, 3, 6, 10]);
    });

    test('can create a broadcast stream', () {
      var source = StreamController<int>.broadcast();

      var transformed = source.stream.scan(null, (_, __) {});

      expect(transformed.isBroadcast, true);
    });

    test('forwards errors from source', () async {
      var source = StreamController<int>();

      int sum(int x, int y) => x + y;

      var errors = <Object>[];

      source.stream.scan(0, sum).listen(null, onError: errors.add);

      source.addError(StateError('fail'));
      await Future(() {});

      expect(errors, [isStateError]);
    });

    group('with async combine', () {
      test('returns a Stream of non-futures', () async {
        var source = Stream.fromIterable([1, 2, 3, 4]);
        Future<int> sum(int x, int y) async => x + y;
        var result = await source.scan(0, sum).toList();

        expect(result, [1, 3, 6, 10]);
      });

      test('can return a Stream of futures when specified', () async {
        var source = Stream.fromIterable([1, 2]);
        Future<int> sum(Future<int> x, int y) async => (await x) + y;
        var result =
            await source.scan<Future<int>>(Future.value(0), sum).toList();

        expect(result, [
          const TypeMatcher<Future<void>>(),
          const TypeMatcher<Future<void>>()
        ]);
        expect(await result.wait, [1, 3]);
      });

      test('does not call for subsequent values while waiting', () async {
        var source = StreamController<int>();

        var calledWith = <int>[];
        var block = Completer<void>();
        Future<int> combine(int x, int y) async {
          calledWith.add(y);
          await block.future;
          return x + y;
        }

        var results = <int>[];

        unawaited(source.stream.scan(0, combine).forEach(results.add));

        source
          ..add(1)
          ..add(2);
        await Future(() {});
        expect(calledWith, [1]);
        expect(results, isEmpty);

        block.complete();
        await Future(() {});
        expect(calledWith, [1, 2]);
        expect(results, [1, 3]);
      });

      test('forwards async errors', () async {
        var source = StreamController<int>();

        Future<int> combine(int x, int y) async => throw StateError('fail');

        var errors = <Object>[];

        source.stream.scan(0, combine).listen(null, onError: errors.add);

        source.add(1);
        await Future(() {});

        expect(errors, [isStateError]);
      });
    });
  });
}
