From af548313ee74d5f07f1342bc15c8ce23c69e173f Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 28 Aug 2023 18:38:26 +0200 Subject: [PATCH 1/5] refactor(neon): refine visibility of some AppImplementation attributes Signed-off-by: Nikolas Rimikis --- packages/neon/neon/lib/src/blocs/apps.dart | 4 +--- .../neon/neon/lib/src/models/app_implementation.dart | 10 ++++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/neon/neon/lib/src/blocs/apps.dart b/packages/neon/neon/lib/src/blocs/apps.dart index d3727017..b0586b37 100644 --- a/packages/neon/neon/lib/src/blocs/apps.dart +++ b/packages/neon/neon/lib/src/blocs/apps.dart @@ -165,9 +165,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates unawaited(appVersions.close()); for (final app in _allAppImplementations) { - for (final bloc in app.blocs.values) { - bloc.dispose(); - } + app.dispose(); } } diff --git a/packages/neon/neon/lib/src/models/app_implementation.dart b/packages/neon/neon/lib/src/models/app_implementation.dart index fd59adc4..83aa5c47 100644 --- a/packages/neon/neon/lib/src/models/app_implementation.dart +++ b/packages/neon/neon/lib/src/models/app_implementation.dart @@ -29,12 +29,15 @@ abstract class AppImplementation String name(final BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); late final R options; + + @protected R buildOptions(final AppStorage storage); - final Map blocs = {}; + final Map _blocs = {}; - T getBloc(final Account account) => blocs[account.id] ??= buildBloc(account); + T getBloc(final Account account) => _blocs[account.id] ??= buildBloc(account); + @protected T buildBloc(final Account account); Provider get blocProvider => Provider( @@ -107,6 +110,9 @@ abstract class AppImplementation void dispose() { options.dispose(); + for (final bloc in _blocs.values) { + bloc.dispose(); + } } /// A custom theme that will be injected into the widget tree. From 564e1c35057c90de63f776adc1d8f96aeb7b683e Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 28 Aug 2023 18:38:26 +0200 Subject: [PATCH 2/5] test(neon): add tests for account.dart Signed-off-by: Nikolas Rimikis --- .../neon/neon/lib/src/models/account.dart | 20 +++-- packages/neon/neon/test/account_test.dart | 87 +++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/packages/neon/neon/lib/src/models/account.dart b/packages/neon/neon/lib/src/models/account.dart index a690483a..567b555a 100644 --- a/packages/neon/neon/lib/src/models/account.dart +++ b/packages/neon/neon/lib/src/models/account.dart @@ -66,16 +66,26 @@ class Account implements Credentials { String get id { final key = '$username@$serverURL'; - if (_idCache[key] != null) { - return _idCache[key]!; - } - return _idCache[key] = sha1.convert(utf8.encode(key)).toString(); + + return _idCache[key] ??= sha1.convert(utf8.encode(key)).toString(); } String get humanReadableID { final uri = Uri.parse(serverURL); + // Maybe also show path if it is not '/' ? - return '$username@${uri.port != 443 ? '${uri.host}:${uri.port}' : uri.host}'; + final buffer = StringBuffer() + ..write(username) + ..write('@') + ..write(uri.host); + + if (uri.hasPort) { + buffer + ..write(':') + ..write(uri.port); + } + + return buffer.toString(); } /// Completes an incomplete [Uri] using the [serverURL]. diff --git a/packages/neon/neon/test/account_test.dart b/packages/neon/neon/test/account_test.dart index a86b934e..34e360f4 100644 --- a/packages/neon/neon/test/account_test.dart +++ b/packages/neon/neon/test/account_test.dart @@ -60,4 +60,91 @@ void main() { }); } }); + + group('Account', () { + final account = Account( + serverURL: 'http://example.com', + username: 'JohnDoe', + password: 'super_secret', + ); + + test('serialization', () { + const json = { + 'serverURL': 'http://example.com', + 'username': 'JohnDoe', + 'password': 'super_secret', + 'userAgent': null, + }; + + expect(account.toJson(), equals(json)); + + expect(Account.fromJson(json), equals(account)); + }); + + test('id', () { + expect(account.id, '8bcb507a406c5ad0eed4072601bcfdd2d923e87d'); + }); + + test('humanReadableID', () { + expect(account.humanReadableID, 'JohnDoe@example.com'); + + final accountWithDefaultPort = Account( + serverURL: 'http://example.com:80', + username: 'JohnDoe', + password: 'super_secret', + ); + + expect(accountWithDefaultPort.humanReadableID, 'JohnDoe@example.com'); + + final accountWithPort = Account( + serverURL: 'http://example.com:8080', + username: 'JohnDoe', + password: 'super_secret', + ); + + expect(accountWithPort.humanReadableID, 'JohnDoe@example.com:8080'); + }); + }); + + test('AccountFind', () { + final account1 = Account( + serverURL: 'http://example.com', + username: 'JohnDoe', + password: 'super_secret', + ); + final account2 = Account( + serverURL: 'http://example.com', + username: 'JohnDoe2', + password: 'super_secret', + ); + final account3 = Account( + serverURL: 'http://example.com', + username: 'JohnDoe3', + password: 'super_secret', + ); + final account4 = Account( + serverURL: 'http://example.com', + username: 'JohnDoe4', + password: 'super_secret', + ); + final account5 = Account( + serverURL: 'http://example.com', + username: 'JohnDoe5', + password: 'super_secret', + ); + final accounts = { + account1, + account2, + account3, + account4, + account5, + }; + + expect(accounts.tryFind(null), isNull); + expect(accounts.tryFind('invalidID'), isNull); + expect(accounts.tryFind(account3.id), equals(account3)); + + expect(() => accounts.find('invalidID'), throwsA(isA())); + expect(accounts.find(account3.id), equals(account3)); + }); } From 3f8291ece325f987e6f375b3ecaeb8f24eeb20a7 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 28 Aug 2023 18:38:26 +0200 Subject: [PATCH 3/5] test(neon): test useragent Signed-off-by: Nikolas Rimikis --- packages/neon/neon/test/useragent_test.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/neon/neon/test/useragent_test.dart diff --git a/packages/neon/neon/test/useragent_test.dart b/packages/neon/neon/test/useragent_test.dart new file mode 100644 index 00000000..b4550fa2 --- /dev/null +++ b/packages/neon/neon/test/useragent_test.dart @@ -0,0 +1,17 @@ +import 'package:neon/src/utils/user_agent.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:test/test.dart'; + +void main() { + test('UserAgent', () { + final packageInfo = PackageInfo( + appName: 'appName', + packageName: 'packageName', + version: 'version', + buildNumber: 'buildNumber', + ); + buildUserAgent(packageInfo); + + expect(neonUserAgent, 'Neon version+buildNumber'); + }); +} From 111f9914e58f22f576b3bb994597b0f5e2c57e91 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 28 Aug 2023 18:38:26 +0200 Subject: [PATCH 4/5] test(neon): unit test StreamListenable Signed-off-by: Nikolas Rimikis --- .../neon/test/stream_listenable_test.dart | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 packages/neon/neon/test/stream_listenable_test.dart diff --git a/packages/neon/neon/test/stream_listenable_test.dart b/packages/neon/neon/test/stream_listenable_test.dart new file mode 100644 index 00000000..7ae1d841 --- /dev/null +++ b/packages/neon/neon/test/stream_listenable_test.dart @@ -0,0 +1,73 @@ +// ignore_for_file: unreachable_from_main + +import 'dart:async'; + +import 'package:mocktail/mocktail.dart'; +import 'package:neon/src/utils/stream_listenable.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:test/test.dart'; + +class MockCallbackFunction extends Mock { + FutureOr call(); +} + +void main() { + group('StreamListenable', () { + test('stream', () async { + final stream = BehaviorSubject(); + final callback = MockCallbackFunction(); + + StreamListenable(stream).addListener(callback.call); + + verifyNever(callback.call); + + stream.value = true; + await Future.delayed(const Duration(milliseconds: 100)); + verify(callback.call).called(1); + + stream.value = true; + await Future.delayed(const Duration(milliseconds: 100)); + verify(callback.call).called(1); + + unawaited(stream.close()); + }); + + test('multiStream', () async { + final stream = BehaviorSubject(); + final stream2 = BehaviorSubject(); + final callback = MockCallbackFunction(); + + StreamListenable.multiListenable({ + stream, + stream2, + }).addListener(callback.call); + + verifyNever(callback.call); + + stream.value = true; + await Future.delayed(const Duration(milliseconds: 100)); + verify(callback.call).called(1); + + stream2.value = 3; + await Future.delayed(const Duration(milliseconds: 100)); + verify(callback.call).called(1); + + unawaited(stream.close()); + unawaited(stream2.close()); + }); + + test('dispose', () { + final controller = StreamController(); + + final listenable = StreamListenable(controller.stream); + + expect(controller.hasListener, true); + + // ignore: cascade_invocations + listenable.dispose(); + + expect(controller.isClosed, false); + unawaited(controller.close()); + }); + }); +} From a05d2f39c47fdcccd3a53353061c42c2ad953aa5 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 28 Aug 2023 18:38:27 +0200 Subject: [PATCH 5/5] test(neon): test NeonPlatform Signed-off-by: Nikolas Rimikis --- packages/neon/neon/test/neon_platform_test.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 packages/neon/neon/test/neon_platform_test.dart diff --git a/packages/neon/neon/test/neon_platform_test.dart b/packages/neon/neon/test/neon_platform_test.dart new file mode 100644 index 00000000..06c6395d --- /dev/null +++ b/packages/neon/neon/test/neon_platform_test.dart @@ -0,0 +1,12 @@ +import 'package:neon/src/platform/platform.dart'; +import 'package:test/test.dart'; + +void main() { + test('NeonPlatform', () async { + expect(() => NeonPlatform.instance, throwsA(isA())); + + await NeonPlatform.setup(); + + expect(NeonPlatform.instance, isA()); + }); +}