diff --git a/lib/src/model/transport_interface.dart b/lib/src/model/transport_interface.dart index c518d91..b9f4ad2 100644 --- a/lib/src/model/transport_interface.dart +++ b/lib/src/model/transport_interface.dart @@ -16,10 +16,10 @@ typedef SpinifyTransportBuilder = Future Function({ required SpinifyMetrics$Mutable metrics, /// Callback for reply messages - required void Function(SpinifyReply reply) onReply, + required Future Function(SpinifyReply reply) onReply, /// Callback for disconnect event - required void Function() onDisconnect, + required Future Function() onDisconnect, }); /// Spinify transport interface. diff --git a/lib/src/spinify_impl.dart b/lib/src/spinify_impl.dart index ba7ef39..8440fcb 100644 --- a/lib/src/spinify_impl.dart +++ b/lib/src/spinify_impl.dart @@ -311,7 +311,7 @@ base mixin SpinifyConnectionMixin if (state.url == url) return; final completer = _readyCompleter ??= Completer(); try { - await disconnect(); + if (state.isConnected || state.isConnecting) await disconnect(); } on Object {/* ignore */} try { _setState(SpinifyState$Connecting(url: _metrics.reconnectUrl = url)); diff --git a/lib/src/transport_fake.dart b/lib/src/transport_fake.dart index 591f0e6..3fedfd1 100644 --- a/lib/src/transport_fake.dart +++ b/lib/src/transport_fake.dart @@ -5,35 +5,38 @@ import 'dart:async'; import 'package:fixnum/fixnum.dart'; import 'model/command.dart'; -import 'model/config.dart'; import 'model/metric.dart'; import 'model/reply.dart'; import 'model/transport_interface.dart'; /// Create a fake Spinify transport. -Future $createFakeSpinifyTransport({ - /// URL for the connection - required String url, - - /// Spinify client configuration - required SpinifyConfig config, - - /// Metrics - required SpinifyMetrics$Mutable metrics, - - /// Callback for reply messages - required void Function(SpinifyReply reply) onReply, - - /// Callback for disconnect event - required void Function() onDisconnect, -}) async { - final transport = SpinifyTransportFake() - ..metrics = metrics - ..onReply = onReply - ..onDisconnect = onDisconnect; - await transport._connect(url); - return transport; -} +SpinifyTransportBuilder $createFakeSpinifyTransport([ + void Function(ISpinifyTransport transport)? out, +]) => + ({ + /// URL for the connection + required url, + + /// Spinify client configuration + required config, + + /// Metrics + required metrics, + + /// Callback for reply messages + required Future Function(SpinifyReply reply) onReply, + + /// Callback for disconnect event + required Future Function() onDisconnect, + }) async { + final transport = SpinifyTransportFake() + ..metrics = metrics + ..onReply = onReply + ..onDisconnect = onDisconnect; + await transport._connect(url); + out?.call(transport); + return transport; + }; /// Spinify fake transport class SpinifyTransportFake implements ISpinifyTransport { @@ -180,7 +183,7 @@ class SpinifyTransportFake implements ISpinifyTransport { Duration(milliseconds: _delay), () { if (!_isConnected) return; - _onReply?.call(reply(DateTime.now())); + _onReply?.call(reply(DateTime.now())).ignore(); }, ); @@ -188,18 +191,19 @@ class SpinifyTransportFake implements ISpinifyTransport { late SpinifyMetrics$Mutable metrics; /// Callback for reply messages - set onReply(void Function(SpinifyReply reply) handler) => _onReply = handler; - void Function(SpinifyReply reply)? _onReply; + set onReply(Future Function(SpinifyReply reply) handler) => + _onReply = handler; + Future Function(SpinifyReply reply)? _onReply; /// Callback for disconnect event - set onDisconnect(void Function() handler) => _onDisconnect = handler; - void Function()? _onDisconnect; + set onDisconnect(Future Function() handler) => _onDisconnect = handler; + Future Function()? _onDisconnect; @override Future disconnect([int? code, String? reason]) async { if (!_isConnected) return; await _sleep(); - _onDisconnect?.call(); + await _onDisconnect?.call(); _timer?.cancel(); _timer = null; } diff --git a/pubspec.yaml b/pubspec.yaml index 1e5c666..3c86512 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,3 +63,4 @@ dev_dependencies: benchmark_harness: ^2.2.2 lints: ^3.0.0 test: ^1.24.4 + fake_async: ^1.3.1 diff --git a/test/unit/spinify_test.dart b/test/unit/spinify_test.dart index 3021b54..b338f27 100644 --- a/test/unit/spinify_test.dart +++ b/test/unit/spinify_test.dart @@ -1,11 +1,15 @@ +import 'package:fake_async/fake_async.dart'; import 'package:spinify/spinify.dart'; import 'package:test/test.dart'; void main() { group('Spinify', () { - Spinify createFakeClient() => Spinify( + Spinify createFakeClient([ + void Function(ISpinifyTransport transport)? out, + ]) => + Spinify( config: SpinifyConfig( - transportBuilder: $createFakeSpinifyTransport, + transportBuilder: $createFakeSpinifyTransport(out), ), ); @@ -58,5 +62,37 @@ void main() { isA() ])); }); + + test( + 'Reconnect_after_disconnected_transport', + () => fakeAsync((async) { + ISpinifyTransport? transport; + final client = createFakeClient((t) => transport = t) + ..connect('ws://localhost:8000/connection/websocket'); + expect(client.state, isA()); + async.elapse(client.config.timeout); + expect(client.state, isA()); + expect(transport, isNotNull); + expect(transport, isA()); + transport!.disconnect(); + async.elapse(const Duration(milliseconds: 50)); + expect(client.state, isA()); + async.elapse(Duration( + milliseconds: client + .config.connectionRetryInterval.min.inMilliseconds ~/ + 2)); + expect(client.state, isA()); + async.elapse(client.config.connectionRetryInterval.max); + expect(client.state, isA()); + client.close(); + expectLater( + client.states, + emitsInOrder([ + isA(), + isA() + ])); + async.elapse(client.config.connectionRetryInterval.max); + expect(client.state, isA()); + })); }); }