blob: c87e43a86bcd7001407a1664ba595c0d2f2a713a [file] [log] [blame]
// 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/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 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', () {
expect(completer.operation.value, throwsA('error'));
expect(completer.isCompleted, isFalse);
expect(completer.operation.isCompleted, isFalse);
completer.complete(Future.error('error'));
expect(completer.isCompleted, isTrue);
expect(completer.operation.isCompleted, isTrue);
});
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');
});
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().future), throwsStateError);
});
test('with a future then successfully', () {
completer.complete(Completer().future);
expect(() => completer.complete(1), throwsStateError);
});
test('with a future twice', () {
completer.complete(Completer().future);
expect(() => completer.complete(Completer().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('when canceled', () {
test('causes the future never to fire', () async {
var completer = CancelableCompleter();
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().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();
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();
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();
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);
});
});
group('asStream()', () {
test('emits a value and then closes', () {
var completer = CancelableCompleter();
expect(completer.operation.asStream().toList(), completion(equals([1])));
completer.complete(1);
});
test('emits an error and then closes', () {
var completer = CancelableCompleter();
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;
});
CancelableOperation<String> runThen() {
originalCompleter = CancelableCompleter();
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', () {
onCancel = null;
final operation = runThen();
expect(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();
});
});
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);
});
});
});
}