296 lines
8.6 KiB
Dart
296 lines
8.6 KiB
Dart
|
import 'dart:async';
|
||
|
|
||
|
import 'package:angel3_reactivex/angel3_reactivex.dart';
|
||
|
import 'package:test/test.dart';
|
||
|
|
||
|
class MockStream<T> extends Stream<T> {
|
||
|
final Stream<T> stream;
|
||
|
var listenCount = 0;
|
||
|
|
||
|
MockStream(this.stream);
|
||
|
|
||
|
@override
|
||
|
StreamSubscription<T> listen(void Function(T event)? onData,
|
||
|
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
||
|
++listenCount;
|
||
|
return stream.listen(
|
||
|
onData,
|
||
|
onError: onError,
|
||
|
onDone: onDone,
|
||
|
cancelOnError: cancelOnError,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void main() {
|
||
|
group('BehaviorConnectableStream', () {
|
||
|
test('should not emit before connecting', () {
|
||
|
final stream = MockStream<int>(Stream.fromIterable(const [1, 2, 3]));
|
||
|
final connectableStream = ValueConnectableStream(stream);
|
||
|
|
||
|
expect(stream.listenCount, 0);
|
||
|
connectableStream.connect();
|
||
|
expect(stream.listenCount, 1);
|
||
|
});
|
||
|
|
||
|
test('should begin emitting items after connection', () {
|
||
|
var count = 0;
|
||
|
const items = [1, 2, 3];
|
||
|
final stream = ValueConnectableStream(Stream.fromIterable(items));
|
||
|
|
||
|
stream.connect();
|
||
|
|
||
|
expect(stream, emitsInOrder(items));
|
||
|
stream.listen(expectAsync1((i) {
|
||
|
expect(stream.value, items[count]);
|
||
|
count++;
|
||
|
}, count: items.length));
|
||
|
});
|
||
|
|
||
|
test('stops emitting after the connection is cancelled', () async {
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3]).publishValue();
|
||
|
|
||
|
stream.connect().cancel(); // ignore: unawaited_futures
|
||
|
|
||
|
expect(stream, neverEmits(anything));
|
||
|
});
|
||
|
|
||
|
test('stops emitting after the last subscriber unsubscribes', () async {
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
|
||
|
|
||
|
stream.listen(null).cancel(); // ignore: unawaited_futures
|
||
|
|
||
|
expect(stream, neverEmits(anything));
|
||
|
});
|
||
|
|
||
|
test('keeps emitting with an active subscription', () async {
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
|
||
|
|
||
|
stream.listen(null);
|
||
|
stream.listen(null).cancel(); // ignore: unawaited_futures
|
||
|
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
});
|
||
|
|
||
|
test('multicasts a single-subscription stream', () async {
|
||
|
final stream = ValueConnectableStream(
|
||
|
Stream.fromIterable(const [1, 2, 3]),
|
||
|
).autoConnect();
|
||
|
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
});
|
||
|
|
||
|
test('replays the latest item', () async {
|
||
|
final stream = ValueConnectableStream(
|
||
|
Stream.fromIterable(const [1, 2, 3]),
|
||
|
).autoConnect();
|
||
|
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
|
||
|
await Future<void>.delayed(Duration(milliseconds: 200));
|
||
|
|
||
|
expect(stream, emits(3));
|
||
|
});
|
||
|
|
||
|
test('replays the seeded item', () async {
|
||
|
final stream =
|
||
|
ValueConnectableStream.seeded(StreamController<int>().stream, 3)
|
||
|
.autoConnect();
|
||
|
|
||
|
expect(stream, emitsInOrder(const <int>[3]));
|
||
|
expect(stream, emitsInOrder(const <int>[3]));
|
||
|
expect(stream, emitsInOrder(const <int>[3]));
|
||
|
|
||
|
await Future<void>.delayed(Duration(milliseconds: 200));
|
||
|
|
||
|
expect(stream, emits(3));
|
||
|
});
|
||
|
|
||
|
test('replays the seeded null item', () async {
|
||
|
final stream =
|
||
|
ValueConnectableStream.seeded(StreamController<int>().stream, null)
|
||
|
.autoConnect();
|
||
|
|
||
|
expect(stream, emitsInOrder(const <int?>[null]));
|
||
|
expect(stream, emitsInOrder(const <int?>[null]));
|
||
|
expect(stream, emitsInOrder(const <int?>[null]));
|
||
|
|
||
|
await Future<void>.delayed(Duration(milliseconds: 200));
|
||
|
|
||
|
expect(stream, emits(null));
|
||
|
});
|
||
|
|
||
|
test('can multicast streams', () async {
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
|
||
|
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
expect(stream, emitsInOrder(const <int>[1, 2, 3]));
|
||
|
});
|
||
|
|
||
|
test('transform Stream with initial value', () async {
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3]).shareValueSeeded(0);
|
||
|
|
||
|
expect(stream.value, 0);
|
||
|
expect(stream, emitsInOrder(const <int>[0, 1, 2, 3]));
|
||
|
});
|
||
|
|
||
|
test('provides access to the latest value', () async {
|
||
|
const items = [1, 2, 3];
|
||
|
var count = 0;
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3]).shareValue();
|
||
|
|
||
|
stream.listen(expectAsync1((data) {
|
||
|
expect(data, items[count]);
|
||
|
count++;
|
||
|
if (count == items.length) {
|
||
|
expect(stream.value, 3);
|
||
|
}
|
||
|
}, count: items.length));
|
||
|
});
|
||
|
|
||
|
test('provides access to the latest error', () async {
|
||
|
final source = StreamController<int>();
|
||
|
final stream = ValueConnectableStream(source.stream).autoConnect();
|
||
|
|
||
|
source.sink.add(1);
|
||
|
source.sink.add(2);
|
||
|
source.sink.add(3);
|
||
|
source.sink.addError(Exception('error'));
|
||
|
|
||
|
stream.listen(
|
||
|
null,
|
||
|
onError: expectAsync1((Object error) {
|
||
|
expect(stream.valueOrNull, 3);
|
||
|
expect(stream.value, 3);
|
||
|
expect(stream.hasValue, isTrue);
|
||
|
|
||
|
expect(stream.errorOrNull, error);
|
||
|
expect(stream.error, error);
|
||
|
expect(stream.hasError, isTrue);
|
||
|
}),
|
||
|
);
|
||
|
});
|
||
|
|
||
|
test('provide a function to autoconnect that stops listening', () async {
|
||
|
final stream = Stream.fromIterable(const [1, 2, 3])
|
||
|
.publishValue()
|
||
|
.autoConnect(connection: (subscription) => subscription.cancel());
|
||
|
|
||
|
expect(await stream.isEmpty, true);
|
||
|
});
|
||
|
|
||
|
test('refCount cancels source subscription when no listeners remain',
|
||
|
() async {
|
||
|
{
|
||
|
var isCanceled = false;
|
||
|
|
||
|
final controller =
|
||
|
StreamController<void>(onCancel: () => isCanceled = true);
|
||
|
final stream = controller.stream.shareValue();
|
||
|
|
||
|
StreamSubscription<void> subscription;
|
||
|
subscription = stream.listen(null);
|
||
|
|
||
|
await subscription.cancel();
|
||
|
expect(isCanceled, true);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
var isCanceled = false;
|
||
|
|
||
|
final controller =
|
||
|
StreamController<void>(onCancel: () => isCanceled = true);
|
||
|
final stream = controller.stream.shareValueSeeded(null);
|
||
|
|
||
|
StreamSubscription<void> subscription;
|
||
|
subscription = stream.listen(null);
|
||
|
|
||
|
await subscription.cancel();
|
||
|
expect(isCanceled, true);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
test('can close shareValue() stream', () async {
|
||
|
{
|
||
|
final isCanceled = Completer<void>();
|
||
|
|
||
|
final controller = StreamController<bool>();
|
||
|
controller.stream
|
||
|
.shareValue()
|
||
|
.doOnCancel(() => isCanceled.complete())
|
||
|
.listen(null);
|
||
|
|
||
|
controller.add(true);
|
||
|
await Future<void>.delayed(Duration.zero);
|
||
|
await controller.close();
|
||
|
|
||
|
await expectLater(isCanceled.future, completes);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
final isCanceled = Completer<void>();
|
||
|
|
||
|
final controller = StreamController<bool>();
|
||
|
controller.stream
|
||
|
.shareValueSeeded(false)
|
||
|
.doOnCancel(() => isCanceled.complete())
|
||
|
.listen(null);
|
||
|
|
||
|
controller.add(true);
|
||
|
await Future<void>.delayed(Duration.zero);
|
||
|
await controller.close();
|
||
|
|
||
|
await expectLater(isCanceled.future, completes);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
test(
|
||
|
'throws StateError when mixing autoConnect, connect and refCount together',
|
||
|
() {
|
||
|
ValueConnectableStream<int> stream() => Stream.value(1).publishValue();
|
||
|
|
||
|
expect(
|
||
|
() => stream()
|
||
|
..autoConnect()
|
||
|
..connect(),
|
||
|
throwsStateError,
|
||
|
);
|
||
|
expect(
|
||
|
() => stream()
|
||
|
..autoConnect()
|
||
|
..refCount(),
|
||
|
throwsStateError,
|
||
|
);
|
||
|
expect(
|
||
|
() => stream()
|
||
|
..connect()
|
||
|
..refCount(),
|
||
|
throwsStateError,
|
||
|
);
|
||
|
});
|
||
|
|
||
|
test('calling autoConnect() multiple times returns the same value', () {
|
||
|
final s = Stream.value(1).publishValueSeeded(1);
|
||
|
expect(s.autoConnect(), same(s.autoConnect()));
|
||
|
expect(s.autoConnect(), same(s.autoConnect()));
|
||
|
});
|
||
|
|
||
|
test('calling connect() multiple times returns the same value', () {
|
||
|
final s = Stream.value(1).publishValueSeeded(1);
|
||
|
expect(s.connect(), same(s.connect()));
|
||
|
expect(s.connect(), same(s.connect()));
|
||
|
});
|
||
|
|
||
|
test('calling refCount() multiple times returns the same value', () {
|
||
|
final s = Stream.value(1).publishValueSeeded(1);
|
||
|
expect(s.refCount(), same(s.refCount()));
|
||
|
expect(s.refCount(), same(s.refCount()));
|
||
|
});
|
||
|
});
|
||
|
}
|