From 755b28d47bd9cae2a913a74727ea595f7c92abb9 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Thu, 15 Aug 2024 23:00:57 +0200 Subject: [PATCH 1/2] refactor(neon_framework): Always use a real Account for testing Signed-off-by: provokateurin --- .../neon_framework/lib/src/testing/mocks.dart | 17 ++++++--- .../test/main_page_test.dart | 2 -- .../test/notification_test.dart | 2 -- .../talk_app/test/actor_avatar_test.dart | 4 --- .../talk_app/test/main_page_test.dart | 6 +--- .../packages/talk_app/test/message_test.dart | 35 +------------------ .../talk_app/test/reference_preview_test.dart | 4 --- .../talk_app/test/rich_object_test.dart | 13 +------ .../talk_app/test/room_avatar_test.dart | 5 --- .../talk_app/test/room_page_test.dart | 3 -- .../test/account_cache_test.dart | 4 +-- packages/neon_framework/test/dialog_test.dart | 3 -- packages/neon_framework/test/image_test.dart | 22 +++++------- .../neon_framework/test/persistence_test.dart | 2 -- .../test/request_manager_test.dart | 1 - .../test/settings_export_test.dart | 14 ++++---- .../neon_framework/test/user_avatar_test.dart | 5 --- 17 files changed, 32 insertions(+), 110 deletions(-) diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index 33546946425..779a0b97155 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; -import 'package:meta/meta.dart'; // ignore: depend_on_referenced_packages import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/blocs.dart'; @@ -23,10 +22,18 @@ import 'package:shared_preferences_platform_interface/shared_preferences_platfor // ignore: depend_on_referenced_packages import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -class MockAccount extends Mock implements Account { - @internal - @override - Uri completeUri(Uri uri) => uri; +// ignore: non_constant_identifier_names +Account MockAccount({ + String serverURL = 'https://cloud.example.com:8443/nextcloud', + String username = 'username', + String password = 'password', +}) { + return Account( + (b) => b + ..serverURL = Uri.parse(serverURL) + ..username = username + ..password = password, + ); } class MockAccountCache extends Mock implements AccountCache {} diff --git a/packages/neon_framework/packages/notifications_app/test/main_page_test.dart b/packages/neon_framework/packages/notifications_app/test/main_page_test.dart index 3c5a659ee33..5595b4f77fb 100644 --- a/packages/neon_framework/packages/notifications_app/test/main_page_test.dart +++ b/packages/neon_framework/packages/notifications_app/test/main_page_test.dart @@ -8,7 +8,6 @@ import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/utils.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:notifications_app/l10n/localizations.dart'; import 'package:notifications_app/src/blocs/notifications.dart'; import 'package:notifications_app/src/pages/main.dart'; @@ -36,7 +35,6 @@ void main() { when(() => bloc.notifications).thenAnswer((_) => BehaviorSubject.seeded(Result.success(BuiltList()))); account = MockAccount(); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); }); testWidgets('Errors', (tester) async { diff --git a/packages/neon_framework/packages/notifications_app/test/notification_test.dart b/packages/neon_framework/packages/notifications_app/test/notification_test.dart index a805de2f920..fb7724a37bd 100644 --- a/packages/neon_framework/packages/notifications_app/test/notification_test.dart +++ b/packages/neon_framework/packages/notifications_app/test/notification_test.dart @@ -4,7 +4,6 @@ import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/widgets.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/notifications.dart' as notifications; import 'package:notifications_app/l10n/localizations.dart'; import 'package:notifications_app/src/widgets/action.dart'; @@ -47,7 +46,6 @@ void main() { callback = MockCallbackFunction().call; account = MockAccount(); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); }); testWidgets('Notification', (tester) async { diff --git a/packages/neon_framework/packages/talk_app/test/actor_avatar_test.dart b/packages/neon_framework/packages/talk_app/test/actor_avatar_test.dart index b173d943267..41e026ba221 100644 --- a/packages/neon_framework/packages/talk_app/test/actor_avatar_test.dart +++ b/packages/neon_framework/packages/talk_app/test/actor_avatar_test.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/widgets.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; import 'package:talk_app/src/widgets/actor_avatar.dart'; @@ -20,8 +18,6 @@ void main() { testWidgets('$type', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); await tester.pumpWidgetWithAccessibility( TestApp( diff --git a/packages/neon_framework/packages/talk_app/test/main_page_test.dart b/packages/neon_framework/packages/talk_app/test/main_page_test.dart index 39ac9e8ce51..11df2623b0c 100644 --- a/packages/neon_framework/packages/talk_app/test/main_page_test.dart +++ b/packages/neon_framework/packages/talk_app/test/main_page_test.dart @@ -8,7 +8,6 @@ import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/utils.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:nextcloud/utils.dart'; import 'package:provider/provider.dart'; @@ -54,10 +53,7 @@ void main() { when(() => bloc.errors).thenAnswer((_) => StreamController().stream); when(() => bloc.rooms).thenAnswer((_) => BehaviorSubject.seeded(Result.success(BuiltList([room])))); - account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.username).thenReturn('test'); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); + account = MockAccount(username: 'test'); }); testWidgets('Errors', (tester) async { diff --git a/packages/neon_framework/packages/talk_app/test/message_test.dart b/packages/neon_framework/packages/talk_app/test/message_test.dart index a03a0299bd6..48895e11de4 100644 --- a/packages/neon_framework/packages/talk_app/test/message_test.dart +++ b/packages/neon_framework/packages/talk_app/test/message_test.dart @@ -9,7 +9,6 @@ import 'package:neon_framework/testing.dart'; import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; import 'package:nextcloud/core.dart' as core; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; @@ -320,8 +319,6 @@ void main() { testWidgets('Comment', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final chatMessage = MockChatMessage(); when(() => chatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -404,8 +401,6 @@ void main() { testWidgets('TalkParentMessage', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final chatMessage = MockChatMessage(); when(() => chatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -437,10 +432,7 @@ void main() { group('TalkCommentMessage', () { testWidgets('Self', (tester) async { - final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.username).thenReturn('test'); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); + final account = MockAccount(username: 'test'); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -494,9 +486,6 @@ void main() { testWidgets('Other', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.username).thenReturn('other'); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -550,8 +539,6 @@ void main() { testWidgets('Deleted', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -596,8 +583,6 @@ void main() { testWidgets('As parent', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final chatMessage = MockChatMessage(); when(() => chatMessage.timestamp).thenReturn(0); @@ -636,8 +621,6 @@ void main() { testWidgets('With parent', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -722,8 +705,6 @@ void main() { ); final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final chatMessage = MockChatMessageWithParent(); when(() => chatMessage.id).thenReturn(0); @@ -778,8 +759,6 @@ void main() { group('Separate messages', () { testWidgets('Actor', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -831,8 +810,6 @@ void main() { testWidgets('Time', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -883,8 +860,6 @@ void main() { testWidgets('System message', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.system); @@ -936,8 +911,6 @@ void main() { testWidgets('Edited', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final previousChatMessage = MockChatMessage(); when(() => previousChatMessage.messageType).thenReturn(spreed.MessageType.comment); @@ -999,9 +972,6 @@ void main() { setUp(() { account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.username).thenReturn('test'); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); chatMessage = MockChatMessage(); when(() => chatMessage.timestamp).thenReturn(0); @@ -1575,9 +1545,6 @@ void main() { .thenAnswer((_) => BehaviorSubject.seeded(Result.success(userDetails))); final account = MockAccount(); - when(() => account.id).thenReturn('id'); - when(() => account.username).thenReturn('username'); - when(() => account.client).thenReturn(NextcloudClient(Uri())); await tester.pumpWidgetWithAccessibility( TestApp( diff --git a/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart b/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart index 61c32462f98..c0459447b68 100644 --- a/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart +++ b/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart @@ -4,7 +4,6 @@ import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/widgets.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:provider/provider.dart'; import 'package:talk_app/src/widgets/reference_preview.dart'; @@ -33,7 +32,6 @@ void main() { testWidgets('Default', (tester) async { final account = MockAccount(); - when(() => account.client).thenReturn(NextcloudClient(Uri())); final openGraphObject = MockOpenGraphObject(); when(() => openGraphObject.name).thenReturn('name'); @@ -60,7 +58,6 @@ void main() { testWidgets('Opens link', (tester) async { final account = MockAccount(); - when(() => account.client).thenReturn(NextcloudClient(Uri())); final openGraphObject = MockOpenGraphObject(); when(() => openGraphObject.name).thenReturn('name'); @@ -87,7 +84,6 @@ void main() { testWidgets('With thumb', (tester) async { final account = MockAccount(); - when(() => account.client).thenReturn(NextcloudClient(Uri())); final openGraphObject = MockOpenGraphObject(); when(() => openGraphObject.thumb).thenReturn(''); diff --git a/packages/neon_framework/packages/talk_app/test/rich_object_test.dart b/packages/neon_framework/packages/talk_app/test/rich_object_test.dart index b371890cc77..0f79ea5c5b1 100644 --- a/packages/neon_framework/packages/talk_app/test/rich_object_test.dart +++ b/packages/neon_framework/packages/talk_app/test/rich_object_test.dart @@ -9,7 +9,6 @@ import 'package:neon_framework/testing.dart'; import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; @@ -30,16 +29,6 @@ void main() { setUp(() { account = MockAccount(); - when(() => account.id).thenReturn('id'); - when(() => account.username).thenReturn('username'); - when(() => account.serverURL).thenReturn(Uri.parse('http://example.com')); - when(() => account.client).thenReturn( - NextcloudClient( - Uri(), - loginName: '', - password: '', - ), - ); }); testWidgets('Deck card', (tester) async { @@ -434,7 +423,7 @@ void main() { find.byWidgetPredicate( (widget) => widget is NeonUriImage && - widget.uri.toString() == 'http://example.com/remote.php/dav/files/username/path', + widget.uri.toString() == 'https://cloud.example.com:8443/nextcloud/remote.php/dav/files/username/path', ), findsOne, ); diff --git a/packages/neon_framework/packages/talk_app/test/room_avatar_test.dart b/packages/neon_framework/packages/talk_app/test/room_avatar_test.dart index ac946df19cd..5b1c19c96d7 100644 --- a/packages/neon_framework/packages/talk_app/test/room_avatar_test.dart +++ b/packages/neon_framework/packages/talk_app/test/room_avatar_test.dart @@ -7,7 +7,6 @@ import 'package:neon_framework/testing.dart'; import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; import 'package:rxdart/subjects.dart'; @@ -22,8 +21,6 @@ void main() { testWidgets('Custom avatar', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final room = MockRoom(); when(() => room.isCustomAvatar).thenReturn(true); @@ -45,8 +42,6 @@ void main() { testWidgets('One to one', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn(''); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final userStatusBloc = MockUserStatusBloc(); when(() => userStatusBloc.statuses).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); diff --git a/packages/neon_framework/packages/talk_app/test/room_page_test.dart b/packages/neon_framework/packages/talk_app/test/room_page_test.dart index bd9122dd7c7..00d50a564bf 100644 --- a/packages/neon_framework/packages/talk_app/test/room_page_test.dart +++ b/packages/neon_framework/packages/talk_app/test/room_page_test.dart @@ -10,7 +10,6 @@ import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/utils.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; @@ -164,8 +163,6 @@ void main() { when(() => bloc.reactions).thenAnswer((_) => BehaviorSubject.seeded(BuiltMap())); final account = MockAccount(); - when(() => account.id).thenReturn('id'); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); await tester.pumpWidgetWithAccessibility( TestApp( diff --git a/packages/neon_framework/test/account_cache_test.dart b/packages/neon_framework/test/account_cache_test.dart index c82c3ce1a86..5b3ec670afd 100644 --- a/packages/neon_framework/test/account_cache_test.dart +++ b/packages/neon_framework/test/account_cache_test.dart @@ -6,8 +6,8 @@ import 'package:neon_framework/testing.dart'; void main() { final disposable0 = MockDisposable(); final disposable1 = MockDisposable(); - final account0 = MockAccount(); - final account1 = MockAccount(); + final account0 = MockAccount(username: '0'); + final account1 = MockAccount(username: '1'); group('AccountCache', () { test('map functionality', () { diff --git a/packages/neon_framework/test/dialog_test.dart b/packages/neon_framework/test/dialog_test.dart index 2c3cbe45646..eaf704ce723 100644 --- a/packages/neon_framework/test/dialog_test.dart +++ b/packages/neon_framework/test/dialog_test.dart @@ -406,7 +406,6 @@ void main() { testWidgets('Without drop_account', (tester) async { final account = MockAccount(); - when(() => account.humanReadableID).thenReturn(''); final capabilitiesBloc = MockCapabilitiesBloc(); when(() => capabilitiesBloc.capabilities).thenAnswer( @@ -433,7 +432,6 @@ void main() { group('With drop_account', () { testWidgets('Disabled', (tester) async { final account = MockAccount(); - when(() => account.humanReadableID).thenReturn(''); final capabilitiesBloc = MockCapabilitiesBloc(); when(() => capabilitiesBloc.capabilities).thenAnswer( @@ -482,7 +480,6 @@ void main() { testWidgets('Delay', (tester) async { final account = MockAccount(); - when(() => account.humanReadableID).thenReturn(''); final capabilitiesBloc = MockCapabilitiesBloc(); when(() => capabilitiesBloc.capabilities).thenAnswer( diff --git a/packages/neon_framework/test/image_test.dart b/packages/neon_framework/test/image_test.dart index 93d0328f30e..4a5f1f3627c 100644 --- a/packages/neon_framework/test/image_test.dart +++ b/packages/neon_framework/test/image_test.dart @@ -75,15 +75,12 @@ void main() { }); testWidgets('NeonApiImage', (tester) async { - final mockNextcloudClient = MockNextcloudClient(); - - final mockAccount = MockAccount(); - when(() => mockAccount.client).thenReturn(mockNextcloudClient); + final account = MockAccount(); final mockRequestManager = MockRequestManager(); when( () => mockRequestManager.wrap( - account: mockAccount, + account: account, getCacheHeaders: any(named: 'getCacheHeaders'), getRequest: any(named: 'getRequest'), converter: any(named: 'converter'), @@ -101,14 +98,14 @@ void main() { getRequest: (_) => mockRequest, etag: null, expires: null, - account: mockAccount, + account: account, ), ), ); verify( () => mockRequestManager.wrap( - account: mockAccount, + account: account, getCacheHeaders: any(named: 'getCacheHeaders'), getRequest: any(named: 'getRequest'), converter: any(named: 'converter'), @@ -119,16 +116,13 @@ void main() { }); testWidgets('NeonUriImage', (tester) async { - final mockNextcloudClient = MockNextcloudClient(); - - final mockAccount = MockAccount(); - when(() => mockAccount.client).thenReturn(mockNextcloudClient); + final account = MockAccount(); final mockRequestManager = MockRequestManager(); when( () => mockRequestManager.wrap( - account: mockAccount, + account: account, getCacheHeaders: any(named: 'getCacheHeaders'), getRequest: any(named: 'getRequest'), converter: any(named: 'converter'), @@ -143,14 +137,14 @@ void main() { TestApp( child: NeonUriImage( uri: uri, - account: mockAccount, + account: account, ), ), ); verify( () => mockRequestManager.wrap( - account: mockAccount, + account: account, getCacheHeaders: any(named: 'getCacheHeaders'), getRequest: any(named: 'getRequest'), converter: any(named: 'converter'), diff --git a/packages/neon_framework/test/persistence_test.dart b/packages/neon_framework/test/persistence_test.dart index 75e9acc7af1..7f5108ac976 100644 --- a/packages/neon_framework/test/persistence_test.dart +++ b/packages/neon_framework/test/persistence_test.dart @@ -5,7 +5,6 @@ import 'package:cookie_store/cookie_store.dart'; import 'package:cookie_store_conformance_tests/cookie_store_conformance_tests.dart' as cookie_jar_conformance; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; -import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/storage/request_cache.dart'; import 'package:neon_framework/src/storage/sqlite_cookie_persistence.dart'; import 'package:neon_framework/src/storage/sqlite_persistence.dart'; @@ -17,7 +16,6 @@ void main() { group('Persistences', () { group('RequestCache', () { final account = MockAccount(); - when(() => account.id).thenReturn('clientID'); final cache = DefaultRequestCache(); diff --git a/packages/neon_framework/test/request_manager_test.dart b/packages/neon_framework/test/request_manager_test.dart index 10a218de014..a775c85cf89 100644 --- a/packages/neon_framework/test/request_manager_test.dart +++ b/packages/neon_framework/test/request_manager_test.dart @@ -19,7 +19,6 @@ String base64String(String value) => base64.encode(utf8.encode(value)); void main() { final account = MockAccount(); - when(() => account.id).thenReturn('clientID'); late MockNeonStorage storage; setUpAll(() { diff --git a/packages/neon_framework/test/settings_export_test.dart b/packages/neon_framework/test/settings_export_test.dart index 6876eff086a..69cc54e76b4 100644 --- a/packages/neon_framework/test/settings_export_test.dart +++ b/packages/neon_framework/test/settings_export_test.dart @@ -40,21 +40,21 @@ void main() { final bloc = MockAccountsBloc(); final exporter = AccountsBlocExporter(bloc); - const accountValue = MapEntry('accountID', 'value'); - const accountExport = { - 'accounts': {'accountID': 'value'}, + final account = MockAccount(); + + final accountValue = MapEntry(account.id, 'value'); + final accountExport = { + 'accounts': {account.id: 'value'}, }; when(() => bloc.accounts).thenAnswer((_) => BehaviorSubject.seeded(BuiltList())); var export = exporter.export(); expect(Map.fromEntries([export]), {'accounts': {}}); - final fakeAccount = MockAccount(); final fakeOptions = MockAccountOptions(); - when(() => bloc.accounts).thenAnswer((_) => BehaviorSubject.seeded(BuiltList([fakeAccount]))); - when(() => bloc.getOptionsFor(fakeAccount)).thenReturn(fakeOptions); + when(() => bloc.accounts).thenAnswer((_) => BehaviorSubject.seeded(BuiltList([account]))); + when(() => bloc.getOptionsFor(account)).thenReturn(fakeOptions); when(fakeOptions.export).thenReturn(accountValue); - when(() => fakeAccount.id).thenReturn('accountID'); export = exporter.export(); expect(Map.fromEntries([export]), accountExport); diff --git a/packages/neon_framework/test/user_avatar_test.dart b/packages/neon_framework/test/user_avatar_test.dart index c5730a56a78..c56b0271cee 100644 --- a/packages/neon_framework/test/user_avatar_test.dart +++ b/packages/neon_framework/test/user_avatar_test.dart @@ -6,7 +6,6 @@ import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/src/widgets/user_avatar.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/widgets.dart'; -import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/user_status.dart' as user_status; import 'package:rxdart/rxdart.dart'; @@ -18,9 +17,6 @@ void main() { for (final (withStatus, matcher) in [(false, findsNothing), (true, findsOne)]) { testWidgets('${withStatus ? 'With' : 'Without'} status', (tester) async { final account = MockAccount(); - when(() => account.id).thenReturn('test'); - when(() => account.username).thenReturn('test'); - when(() => account.client).thenReturn(NextcloudClient(Uri.parse(''))); final userStatusBloc = MockUserStatusBloc(); when(() => userStatusBloc.statuses).thenAnswer( @@ -48,7 +44,6 @@ void main() { ), ), ); - await tester.pumpAndSettle(); expect(find.byType(NeonUserStatusIndicator), matcher); }); From fe381fb21870478a5a3b499266df8ab166f84808 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Thu, 15 Aug 2024 15:46:32 +0200 Subject: [PATCH 2/2] refactor(neon_framework): Remove url launcher hack Signed-off-by: provokateurin --- .../lib/src/models/account.dart | 5 - .../lib/src/pages/account_settings.dart | 9 +- .../lib/src/pages/login_flow.dart | 9 +- .../lib/src/pages/settings.dart | 17 +- packages/neon_framework/lib/src/router.dart | 75 +------ packages/neon_framework/lib/src/router.g.dart | 24 --- .../lib/src/utils/launch_url.dart | 10 + .../lib/src/widgets/app_bar.dart | 7 +- .../lib/src/widgets/dialog.dart | 6 +- packages/neon_framework/lib/utils.dart | 1 + .../dashboard_app/lib/src/widgets/widget.dart | 5 +- .../lib/src/widgets/widget_button.dart | 5 +- .../lib/src/widgets/widget_item.dart | 3 +- .../packages/dashboard_app/pubspec.yaml | 1 + .../dashboard_app/test/widget_test.dart | 60 +++--- .../news_app/lib/src/pages/article.dart | 13 +- .../lib/src/widgets/articles_view.dart | 6 +- .../packages/news_app/pubspec.yaml | 1 - .../notes_app/lib/src/pages/note.dart | 7 +- .../packages/notes_app/pubspec.yaml | 1 - .../lib/src/widgets/action.dart | 7 +- .../lib/src/widgets/notification.dart | 5 +- .../packages/notifications_app/pubspec.yaml | 1 + .../notifications_app/test/action_test.dart | 28 ++- .../test/notification_test.dart | 15 +- .../talk_app/lib/src/widgets/message.dart | 7 +- .../lib/src/widgets/reference_preview.dart | 3 +- .../src/widgets/rich_object/deck_card.dart | 7 +- .../lib/src/widgets/rich_object/fallback.dart | 5 +- .../lib/src/widgets/rich_object/file.dart | 7 +- .../packages/talk_app/pubspec.yaml | 1 + .../talk_app/test/reference_preview_test.dart | 23 ++- .../talk_app/test/rich_object_test.dart | 34 ++-- .../neon_framework/test/account_test.dart | 7 - packages/neon_framework/test/router_test.dart | 187 ------------------ 35 files changed, 165 insertions(+), 437 deletions(-) create mode 100644 packages/neon_framework/lib/src/utils/launch_url.dart diff --git a/packages/neon_framework/lib/src/models/account.dart b/packages/neon_framework/lib/src/models/account.dart index 88542c715c0..919c37c8ad3 100644 --- a/packages/neon_framework/lib/src/models/account.dart +++ b/packages/neon_framework/lib/src/models/account.dart @@ -142,11 +142,6 @@ abstract class Account implements Credentials, Findable, Built Uri.parse(uri.toString().replaceFirst(serverURL.toString(), '')); } /// QRcode Login credentials. diff --git a/packages/neon_framework/lib/src/pages/account_settings.dart b/packages/neon_framework/lib/src/pages/account_settings.dart index 67ab9fccaa6..efa621cfd44 100644 --- a/packages/neon_framework/lib/src/pages/account_settings.dart +++ b/packages/neon_framework/lib/src/pages/account_settings.dart @@ -14,7 +14,6 @@ import 'package:neon_framework/src/theme/dialog.dart'; import 'package:neon_framework/src/widgets/dialog.dart'; import 'package:neon_framework/src/widgets/error.dart'; import 'package:neon_framework/utils.dart'; -import 'package:url_launcher/url_launcher.dart'; /// Account settings page. /// @@ -54,11 +53,9 @@ class AccountSettingsPage extends StatelessWidget { case null: break; case AccountDeletion.remote: - await launchUrl( - account.serverURL.replace( - path: '${account.serverURL.path}/index.php/settings/user/drop_account', - ), - ); + if (context.mounted) { + await launchUrl(NeonProvider.of(context), '/index.php/settings/user/drop_account'); + } case AccountDeletion.local: final isActive = bloc.activeAccount.valueOrNull == account; diff --git a/packages/neon_framework/lib/src/pages/login_flow.dart b/packages/neon_framework/lib/src/pages/login_flow.dart index 6d520a30147..0b2e25a5bea 100644 --- a/packages/neon_framework/lib/src/pages/login_flow.dart +++ b/packages/neon_framework/lib/src/pages/login_flow.dart @@ -2,14 +2,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; -import 'package:neon_framework/l10n/localizations.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/blocs/login_flow.dart'; import 'package:neon_framework/src/router.dart'; import 'package:neon_framework/src/widgets/error.dart'; import 'package:neon_framework/src/widgets/linear_progress_indicator.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/core.dart' as core; -import 'package:url_launcher/url_launcher_string.dart'; @internal class LoginFlowPage extends StatefulWidget { @@ -39,10 +39,7 @@ class _LoginFlowPageState extends State { initSubscription = bloc.init.listen((result) async { if (result.hasData) { - await launchUrlString( - result.requireData.login, - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), result.requireData.login); } }); diff --git a/packages/neon_framework/lib/src/pages/settings.dart b/packages/neon_framework/lib/src/pages/settings.dart index c44c7bea0d1..81d5c06f0cd 100644 --- a/packages/neon_framework/lib/src/pages/settings.dart +++ b/packages/neon_framework/lib/src/pages/settings.dart @@ -5,9 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:neon_framework/l10n/localizations.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; -import 'package:neon_framework/src/models/app_implementation.dart'; import 'package:neon_framework/src/platform/platform.dart'; import 'package:neon_framework/src/router.dart'; import 'package:neon_framework/src/settings/utils/settings_export_helper.dart'; @@ -21,15 +20,13 @@ import 'package:neon_framework/src/settings/widgets/text_settings_tile.dart'; import 'package:neon_framework/src/theme/branding.dart'; import 'package:neon_framework/src/theme/dialog.dart'; import 'package:neon_framework/src/theme/icons.dart'; -import 'package:neon_framework/src/utils/adaptive.dart'; import 'package:neon_framework/src/utils/global_options.dart'; -import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/widgets/dialog.dart'; import 'package:neon_framework/src/widgets/error.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/utils.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:timezone/timezone.dart' as tz; -import 'package:url_launcher/url_launcher_string.dart'; final _log = Logger('SettingsPage'); @@ -196,10 +193,7 @@ class SettingsPage extends StatelessWidget { ), title: Text(NeonLocalizations.of(context).sourceCode), onTap: () async { - await launchUrlString( - branding.sourceCodeURL!, - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), branding.sourceCodeURL!); }, ), if (branding.issueTrackerURL != null) @@ -210,10 +204,7 @@ class SettingsPage extends StatelessWidget { ), title: Text(NeonLocalizations.of(context).issueTracker), onTap: () async { - await launchUrlString( - branding.issueTrackerURL!, - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), branding.issueTrackerURL!); }, ), CustomSettingsTile( diff --git a/packages/neon_framework/lib/src/router.dart b/packages/neon_framework/lib/src/router.dart index 1309b3f5b97..a95f72f1e04 100644 --- a/packages/neon_framework/lib/src/router.dart +++ b/packages/neon_framework/lib/src/router.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:meta/meta.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; -import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/app_implementation.dart'; import 'package:neon_framework/src/pages/account_settings.dart'; @@ -23,7 +22,6 @@ import 'package:neon_framework/src/pages/route_not_found.dart'; import 'package:neon_framework/src/pages/settings.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/provider.dart'; -import 'package:url_launcher/url_launcher.dart'; part 'router.g.dart'; @@ -36,49 +34,16 @@ GoRouter buildAppRouter({ debugLogDiagnostics: kDebugMode, navigatorKey: navigatorKey, initialLocation: const HomeRoute().location, - onException: onException, + errorBuilder: (context, state) => RouteNotFoundPage( + uri: state.uri, + ), redirect: redirect, routes: $appRoutes, ); -/// Handles routing exceptions thrown by the [GoRouter] of [buildAppRouter]. -@visibleForTesting -Future onException(BuildContext context, GoRouterState state, GoRouter router) async { - final accountsBloc = NeonProvider.of(context); - if (accountsBloc.hasAccounts) { - final capabilitiesBloc = NeonProvider.of(context); - final capabilities = capabilitiesBloc.capabilities.valueOrNull?.data; - final modRewriteWorking = capabilities?.capabilities.coreCapabilities?.core.modRewriteWorking ?? false; - - final account = NeonProvider.of(context); - var uri = account.completeUri(state.uri); - - if (uri != state.uri && !modRewriteWorking && !uri.path.startsWith('/index.php')) { - uri = uri.replace(path: '/index.php${uri.path}'); - } - - await launchUrl( - uri, - mode: LaunchMode.externalApplication, - ); - - return; - } - - if (!context.mounted) { - return; - } - - await router.push(RouteNotFoundRoute(uri: state.uri).location); -} - /// Handles redirects of the [GoRouter] of [buildAppRouter]. @visibleForTesting String? redirect(BuildContext context, GoRouterState state) { - if (state.uri.path.startsWith('/index.php/')) { - return state.uri.path.substring(10); - } - final loginQRcode = LoginQRcode.tryParse(state.uri.toString()); if (loginQRcode != null) { return LoginCheckServerStatusWithCredentialsRoute( @@ -88,17 +53,9 @@ String? redirect(BuildContext context, GoRouterState state) { ).location; } - final accountsBloc = NeonProvider.of(context); - if (accountsBloc.hasAccounts && state.uri.hasScheme) { - final account = NeonProvider.of(context); - final strippedUri = account.stripUri(state.uri); - if (strippedUri != state.uri) { - return strippedUri.toString(); - } - } - - // Redirect to login screen when no account is logged in + // Redirect to login screen when no account is logged in. // We only check the prefix of the current location as we also don't want to redirect on any of the other login routes. + final accountsBloc = NeonProvider.of(context); if (!accountsBloc.hasAccounts && !state.matchedLocation.startsWith(const LoginRoute().location)) { return const LoginRoute().location; } @@ -132,28 +89,6 @@ class AccountSettingsRoute extends GoRouteData { } } -/// {@template AppRoutes.RouteNotFoundRoute} -/// Route for the [RouteNotFoundPage]. -/// {@endtemplate} -@TypedGoRoute(path: '/not-found/:uri') -@immutable -class RouteNotFoundRoute extends GoRouteData { - /// {@macro AppRoutes.RouteNotFoundRoute} - const RouteNotFoundRoute({ - required this.uri, - }); - - /// The URI of the route that caused the error. - final Uri uri; - - @override - Widget build(BuildContext context, GoRouterState state) { - return RouteNotFoundPage( - uri: uri, - ); - } -} - /// {@template AppRoutes.HomeRoute} /// Route for the [HomePage]. /// {@endtemplate} diff --git a/packages/neon_framework/lib/src/router.g.dart b/packages/neon_framework/lib/src/router.g.dart index a0bb848234a..d00f2d943db 100644 --- a/packages/neon_framework/lib/src/router.g.dart +++ b/packages/neon_framework/lib/src/router.g.dart @@ -7,34 +7,10 @@ part of 'router.dart'; // ************************************************************************** List get $appRoutes => [ - $routeNotFoundRoute, $homeRoute, $loginRoute, ]; -RouteBase get $routeNotFoundRoute => GoRouteData.$route( - path: '/not-found/:uri', - factory: $RouteNotFoundRouteExtension._fromState, - ); - -extension $RouteNotFoundRouteExtension on RouteNotFoundRoute { - static RouteNotFoundRoute _fromState(GoRouterState state) => RouteNotFoundRoute( - uri: Uri.parse(state.pathParameters['uri']!), - ); - - String get location => GoRouteData.$location( - '/not-found/${Uri.encodeComponent(uri.toString())}', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - RouteBase get $homeRoute => GoRouteData.$route( path: '/', name: 'home', diff --git a/packages/neon_framework/lib/src/utils/launch_url.dart b/packages/neon_framework/lib/src/utils/launch_url.dart new file mode 100644 index 00000000000..62b97b0e310 --- /dev/null +++ b/packages/neon_framework/lib/src/utils/launch_url.dart @@ -0,0 +1,10 @@ +import 'package:neon_framework/models.dart'; +import 'package:url_launcher/url_launcher.dart' as url_launcher; + +/// Completes the [url] using the [account] if necessary and launches it in an external application. +Future launchUrl(Account account, String url) async { + return url_launcher.launchUrl( + account.completeUri(Uri.parse(url)), + mode: url_launcher.LaunchMode.externalApplication, + ); +} diff --git a/packages/neon_framework/lib/src/widgets/app_bar.dart b/packages/neon_framework/lib/src/widgets/app_bar.dart index 30d89ba1a5c..5a5f8a63fdb 100644 --- a/packages/neon_framework/lib/src/widgets/app_bar.dart +++ b/packages/neon_framework/lib/src/widgets/app_bar.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:meta/meta.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/l10n/localizations.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/src/blocs/unified_search.dart'; import 'package:neon_framework/src/utils/global_options.dart' as global_options; +import 'package:neon_framework/src/utils/launch_url.dart'; import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/widgets/account_switcher_button.dart'; import 'package:neon_framework/src/widgets/unified_search_results.dart'; @@ -106,9 +107,9 @@ class _NeonAppBarState extends State { viewBuilder: (_) => Padding( padding: const EdgeInsets.all(8), child: NeonUnifiedSearchResults( - onSelected: (entry) { - context.go(entry.resourceUrl); + onSelected: (entry) async { searchController.closeView(''); + await launchUrl(NeonProvider.of(context), entry.resourceUrl); }, ), ), diff --git a/packages/neon_framework/lib/src/widgets/dialog.dart b/packages/neon_framework/lib/src/widgets/dialog.dart index 896e339cfa7..ed996a3105c 100644 --- a/packages/neon_framework/lib/src/widgets/dialog.dart +++ b/packages/neon_framework/lib/src/widgets/dialog.dart @@ -21,7 +21,6 @@ import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/user_status.dart' as user_status; import 'package:nextcloud/utils.dart'; import 'package:timezone/timezone.dart' as tz; -import 'package:url_launcher/url_launcher_string.dart'; /// An button typically used in an [AlertDialog.adaptive]. /// @@ -700,10 +699,7 @@ class NeonUnifiedPushDialog extends StatelessWidget { isDefaultAction: true, onPressed: () async { Navigator.pop(context); - await launchUrlString( - 'https://f-droid.org/packages/$unifiedPushNextPushID', - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), 'https://f-droid.org/packages/$unifiedPushNextPushID'); }, child: Text( NeonLocalizations.of(context).nextPushSupportedInstall, diff --git a/packages/neon_framework/lib/utils.dart b/packages/neon_framework/lib/utils.dart index bf42d1cdc9e..b52590aa02b 100644 --- a/packages/neon_framework/lib/utils.dart +++ b/packages/neon_framework/lib/utils.dart @@ -5,6 +5,7 @@ export 'package:neon_framework/src/utils/dialog.dart'; export 'package:neon_framework/src/utils/exceptions.dart'; export 'package:neon_framework/src/utils/findable.dart'; export 'package:neon_framework/src/utils/hex_color.dart'; +export 'package:neon_framework/src/utils/launch_url.dart'; export 'package:neon_framework/src/utils/provider.dart'; export 'package:neon_framework/src/utils/request_manager.dart' show RequestManager, UnwrapCallback; export 'package:neon_framework/src/utils/validators.dart'; diff --git a/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget.dart b/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget.dart index b975abb9960..e5c2b22f9df 100644 --- a/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget.dart +++ b/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget.dart @@ -1,6 +1,7 @@ import 'package:dashboard_app/l10n/localizations.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; +import 'package:neon_framework/models.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/dashboard.dart' as dashboard; /// Displays a single dashboard widget and its items. @@ -31,7 +32,7 @@ class DashboardWidget extends StatelessWidget { child = Tooltip( message: DashboardLocalizations.of(context).openWidget, child: InkWell( - onTap: () => context.go(widget.widgetUrl!), + onTap: () async => launchUrl(NeonProvider.of(context), widget.widgetUrl!), borderRadius: const BorderRadius.all(Radius.circular(12)), child: child, ), diff --git a/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_button.dart b/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_button.dart index 4a96782312a..c46f824a3a2 100644 --- a/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_button.dart +++ b/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_button.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/theme.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/dashboard.dart' as dashboard; /// Button inside a dashboard widget that is used to trigger an action. @@ -16,7 +17,7 @@ class DashboardWidgetButton extends StatelessWidget { @override Widget build(BuildContext context) { - void onPressed() => context.go(button.link); + Future onPressed() async => launchUrl(NeonProvider.of(context), button.link); final label = Text(button.text); final icon = switch (button.type) { 'new' => AdaptiveIcons.add, diff --git a/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_item.dart b/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_item.dart index 7988a50f88d..f1a3401663f 100644 --- a/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_item.dart +++ b/packages/neon_framework/packages/dashboard_app/lib/src/widgets/widget_item.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; @@ -74,7 +73,7 @@ class DashboardWidgetItem extends StatelessWidget { maxLines: 1, ), leading: leading, - onTap: item.link.isNotEmpty ? () => context.go(item.link) : null, + onTap: item.link.isNotEmpty ? () async => launchUrl(NeonProvider.of(context), item.link) : null, ); } } diff --git a/packages/neon_framework/packages/dashboard_app/pubspec.yaml b/packages/neon_framework/packages/dashboard_app/pubspec.yaml index 28ac809358c..702e89d9d52 100644 --- a/packages/neon_framework/packages/dashboard_app/pubspec.yaml +++ b/packages/neon_framework/packages/dashboard_app/pubspec.yaml @@ -36,6 +36,7 @@ dev_dependencies: url: https://github.com/nextcloud/neon path: packages/neon_lints provider: ^6.1.2 + url_launcher_platform_interface: ^2.3.2 vector_graphics_compiler: ^1.1.11+1 flutter: diff --git a/packages/neon_framework/packages/dashboard_app/test/widget_test.dart b/packages/neon_framework/packages/dashboard_app/test/widget_test.dart index 01967245c5c..20faa8948e8 100644 --- a/packages/neon_framework/packages/dashboard_app/test/widget_test.dart +++ b/packages/neon_framework/packages/dashboard_app/test/widget_test.dart @@ -12,28 +12,28 @@ import 'package:neon_framework/theme.dart'; import 'package:neon_framework/widgets.dart'; import 'package:nextcloud/dashboard.dart' as dashboard; import 'package:provider/provider.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -Widget wrapWidget( - Widget child, { - MockGoRouter? router, -}) => - TestApp( +Widget wrapWidget(Widget child) => TestApp( localizationsDelegates: DashboardLocalizations.localizationsDelegates, supportedLocales: DashboardLocalizations.supportedLocales, - providers: [ - Provider.value( - value: Account( - (b) => b - ..serverURL = Uri() - ..username = 'example', - ), - ), - ], - router: router, + providers: [Provider.value(value: MockAccount())], child: child, ); void main() { + late MockUrlLauncher urlLauncher; + + setUpAll(() { + registerFallbackValue(const LaunchOptions()); + + urlLauncher = MockUrlLauncher(); + // ignore: discarded_futures + when(() => urlLauncher.launchUrl(any(), any())).thenAnswer((_) async => true); + + UrlLauncherPlatform.instance = urlLauncher; + }); + setUp(() { FakeNeonStorage.setup(); }); @@ -43,22 +43,19 @@ void main() { (b) => b ..title = 'Widget item title' ..subtitle = 'Widget item subtitle' - ..link = 'https://example.com/link' - ..iconUrl = 'https://example.com/iconUrl' - ..overlayIconUrl = 'https://example.com/overlayIconUrl' + ..link = '/link' + ..iconUrl = '/iconUrl' + ..overlayIconUrl = '/overlayIconUrl' ..sinceId = '', ); testWidgets('Everything filled', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( wrapWidget( DashboardWidgetItem( item: item, roundIcon: true, ), - router: router, ), ); @@ -87,7 +84,7 @@ void main() { await expectLater(find.byType(DashboardWidgetItem), matchesGoldenFile('goldens/widget_item.png')); await tester.tap(find.byType(DashboardWidgetItem)); - verify(() => router.go('https://example.com/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); testWidgets('Not round', (tester) async { @@ -165,23 +162,20 @@ void main() { (b) => b ..type = 'new' ..text = 'Button' - ..link = 'https://example.com/link', + ..link = '/link', ); testWidgets('Opens link', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( wrapWidget( DashboardWidgetButton( button: button, ), - router: router, ), ); await tester.tap(find.byType(DashboardWidgetButton)); - verify(() => router.go('https://example.com/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); testWidgets('New', (tester) async { @@ -250,9 +244,9 @@ void main() { (b) => b ..title = 'Widget item title' ..subtitle = 'Widget item subtitle' - ..link = 'https://example.com/link' - ..iconUrl = 'https://example.com/iconUrl' - ..overlayIconUrl = 'https://example.com/overlayIconUrl' + ..link = '/link' + ..iconUrl = '/iconUrl' + ..overlayIconUrl = '/overlayIconUrl' ..sinceId = '', ); final items = dashboard.WidgetItems( @@ -265,7 +259,7 @@ void main() { (b) => b ..type = 'new' ..text = 'Button' - ..link = 'https://example.com/link', + ..link = '/link', ); final widget = dashboard.Widget( (b) => b @@ -273,8 +267,8 @@ void main() { ..title = 'Widget title' ..order = 0 ..iconClass = '' - ..iconUrl = 'https://example.com/iconUrl' - ..widgetUrl = 'https://example.com/widgetUrl' + ..iconUrl = '/iconUrl' + ..widgetUrl = '/widgetUrl' ..itemIconsRound = true ..itemApiVersions.replace([1, 2]) ..reloadInterval = 0 diff --git a/packages/neon_framework/packages/news_app/lib/src/pages/article.dart b/packages/neon_framework/packages/news_app/lib/src/pages/article.dart index 8dde8f5fcda..a59d0d8eba9 100644 --- a/packages/neon_framework/packages/news_app/lib/src/pages/article.dart +++ b/packages/neon_framework/packages/news_app/lib/src/pages/article.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/platform.dart'; import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; @@ -11,7 +12,6 @@ import 'package:news_app/src/blocs/article.dart'; import 'package:news_app/src/blocs/articles.dart'; import 'package:news_app/src/options.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:webview_flutter/webview_flutter.dart'; @@ -186,10 +186,8 @@ class _NewsArticlePageState extends State { if (widget.url != null) ...[ IconButton( onPressed: () async { - await launchUrlString( - await _getURL(), - mode: LaunchMode.externalApplication, - ); + // ignore: use_build_context_synchronously + await launchUrl(NeonProvider.of(context), await _getURL()); }, tooltip: NewsLocalizations.of(context).articleOpenLink, icon: const Icon(Icons.open_in_new), @@ -216,10 +214,7 @@ class _NewsArticlePageState extends State { data: widget.bodyData, onLinkTap: (url, attributes, element) async { if (url != null) { - await launchUrlString( - url, - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), url); } }, ), diff --git a/packages/neon_framework/packages/news_app/lib/src/widgets/articles_view.dart b/packages/neon_framework/packages/news_app/lib/src/widgets/articles_view.dart index 72207c4b01a..6204bc84067 100644 --- a/packages/neon_framework/packages/news_app/lib/src/widgets/articles_view.dart +++ b/packages/neon_framework/packages/news_app/lib/src/widgets/articles_view.dart @@ -22,7 +22,6 @@ import 'package:news_app/src/widgets/feed_icon.dart'; import 'package:nextcloud/news.dart' as news; import 'package:nextcloud/utils.dart'; import 'package:timezone/timezone.dart' as tz; -import 'package:url_launcher/url_launcher_string.dart'; final _log = Logger('NewsArticlesView'); @@ -280,10 +279,7 @@ class _NewsArticlesViewState extends State { widget.bloc.markArticleAsRead(article); } if (article.url != null) { - await launchUrlString( - article.url!, - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), article.url!); } } }, diff --git a/packages/neon_framework/packages/news_app/pubspec.yaml b/packages/neon_framework/packages/news_app/pubspec.yaml index c1c00c526d0..2fb2ad9cdc3 100644 --- a/packages/neon_framework/packages/news_app/pubspec.yaml +++ b/packages/neon_framework/packages/news_app/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: rxdart: ^0.27.0 share_plus: ^10.0.0 timezone: ^0.9.4 - url_launcher: ^6.1.4 wakelock_plus: ^1.0.0 webview_flutter: ^4.0.0 diff --git a/packages/neon_framework/packages/notes_app/lib/src/pages/note.dart b/packages/neon_framework/packages/notes_app/lib/src/pages/note.dart index eeba26cee21..f6a226ee27b 100644 --- a/packages/neon_framework/packages/notes_app/lib/src/pages/note.dart +++ b/packages/neon_framework/packages/notes_app/lib/src/pages/note.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/theme.dart'; import 'package:neon_framework/utils.dart'; import 'package:notes_app/l10n/localizations.dart'; @@ -12,7 +13,6 @@ import 'package:notes_app/src/utils/category_color.dart'; import 'package:notes_app/src/utils/exception_handler.dart'; import 'package:notes_app/src/widgets/dialog.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:url_launcher/url_launcher_string.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; class NotesNotePage extends StatefulWidget { @@ -188,10 +188,7 @@ class _NotesNotePageState extends State { data: _contentController.text, onTapLink: (text, href, title) async { if (href != null) { - await launchUrlString( - href, - mode: LaunchMode.externalApplication, - ); + await launchUrl(NeonProvider.of(context), href); } }, ), diff --git a/packages/neon_framework/packages/notes_app/pubspec.yaml b/packages/neon_framework/packages/notes_app/pubspec.yaml index 7dd98f69af3..38e1fe16687 100644 --- a/packages/neon_framework/packages/notes_app/pubspec.yaml +++ b/packages/neon_framework/packages/notes_app/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: queue: ^3.0.0 rxdart: ^0.27.0 timezone: ^0.9.4 - url_launcher: ^6.1.4 wakelock_plus: ^1.0.0 dev_dependencies: diff --git a/packages/neon_framework/packages/notifications_app/lib/src/widgets/action.dart b/packages/neon_framework/packages/notifications_app/lib/src/widgets/action.dart index d497120bfd6..b4b9a319085 100644 --- a/packages/neon_framework/packages/notifications_app/lib/src/widgets/action.dart +++ b/packages/neon_framework/packages/notifications_app/lib/src/widgets/action.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; +import 'package:neon_framework/models.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/notifications.dart' as notifications; class NotificationsAction extends StatelessWidget { @@ -13,8 +14,8 @@ class NotificationsAction extends StatelessWidget { @override Widget build(BuildContext context) { return ElevatedButton( - onPressed: () { - context.go(action.link); + onPressed: () async { + await launchUrl(NeonProvider.of(context), action.link); }, style: ElevatedButton.styleFrom( foregroundColor: action.primary ? Theme.of(context).colorScheme.onPrimary : null, diff --git a/packages/neon_framework/packages/notifications_app/lib/src/widgets/notification.dart b/packages/neon_framework/packages/notifications_app/lib/src/widgets/notification.dart index 9929d089dd8..92a84622f39 100644 --- a/packages/neon_framework/packages/notifications_app/lib/src/widgets/notification.dart +++ b/packages/neon_framework/packages/notifications_app/lib/src/widgets/notification.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:intersperse/intersperse.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/theme.dart'; @@ -67,7 +66,9 @@ class NotificationsNotification extends StatelessWidget { size: const Size.square(largeIconSize), svgColorFilter: ColorFilter.mode(Theme.of(context).colorScheme.primary, BlendMode.srcIn), ), - onTap: notification.link.isNotEmpty ? () => context.go(notification.link) : null, + onTap: notification.link.isNotEmpty + ? () async => launchUrl(NeonProvider.of(context), notification.link) + : null, ), ); } diff --git a/packages/neon_framework/packages/notifications_app/pubspec.yaml b/packages/neon_framework/packages/notifications_app/pubspec.yaml index 78f1d87eac1..4ecc46ec0f2 100644 --- a/packages/neon_framework/packages/notifications_app/pubspec.yaml +++ b/packages/neon_framework/packages/notifications_app/pubspec.yaml @@ -40,6 +40,7 @@ dev_dependencies: url: https://github.com/nextcloud/neon path: packages/neon_lints provider: ^6.1.2 + url_launcher_platform_interface: ^2.3.2 vector_graphics_compiler: ^1.1.11+1 flutter: diff --git a/packages/neon_framework/packages/notifications_app/test/action_test.dart b/packages/neon_framework/packages/notifications_app/test/action_test.dart index 2dad8f18157..32a21231076 100644 --- a/packages/neon_framework/packages/notifications_app/test/action_test.dart +++ b/packages/neon_framework/packages/notifications_app/test/action_test.dart @@ -1,11 +1,31 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/testing.dart'; import 'package:notifications_app/src/widgets/action.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'testing.dart'; void main() { + late MockUrlLauncher urlLauncher; + late Account account; + + setUpAll(() { + registerFallbackValue(const LaunchOptions()); + + urlLauncher = MockUrlLauncher(); + // ignore: discarded_futures + when(() => urlLauncher.launchUrl(any(), any())).thenAnswer((_) async => true); + + UrlLauncherPlatform.instance = urlLauncher; + }); + + setUp(() { + account = MockAccount(); + }); + testWidgets('Primary', (tester) async { final action = MockAction(); when(() => action.label).thenReturn('label'); @@ -39,8 +59,6 @@ void main() { }); testWidgets('Opens link', (tester) async { - final router = MockGoRouter(); - final action = MockAction(); when(() => action.label).thenReturn('label'); when(() => action.primary).thenReturn(true); @@ -48,7 +66,9 @@ void main() { await tester.pumpWidgetWithAccessibility( TestApp( - router: router, + providers: [ + Provider.value(value: account), + ], child: NotificationsAction( action: action, ), @@ -57,6 +77,6 @@ void main() { await tester.tap(find.byType(NotificationsAction)); - verify(() => router.go('/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); } diff --git a/packages/neon_framework/packages/notifications_app/test/notification_test.dart b/packages/neon_framework/packages/notifications_app/test/notification_test.dart index fb7724a37bd..1881367fb70 100644 --- a/packages/neon_framework/packages/notifications_app/test/notification_test.dart +++ b/packages/neon_framework/packages/notifications_app/test/notification_test.dart @@ -10,6 +10,7 @@ import 'package:notifications_app/src/widgets/action.dart'; import 'package:notifications_app/src/widgets/notification.dart'; import 'package:provider/provider.dart'; import 'package:timezone/timezone.dart' as tz; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'testing.dart'; @@ -17,9 +18,16 @@ void main() { late notifications.Notification notification; late void Function() callback; late Account account; + late MockUrlLauncher urlLauncher; setUpAll(() { - registerFallbackValue(Uri()); + registerFallbackValue(const LaunchOptions()); + + urlLauncher = MockUrlLauncher(); + // ignore: discarded_futures + when(() => urlLauncher.launchUrl(any(), any())).thenAnswer((_) async => true); + + UrlLauncherPlatform.instance = urlLauncher; }); setUp(() { @@ -49,8 +57,6 @@ void main() { }); testWidgets('Notification', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( TestApp( localizationsDelegates: NotificationsLocalizations.localizationsDelegates, @@ -59,7 +65,6 @@ void main() { Provider>.value(value: BuiltSet()), Provider.value(value: account), ], - router: router, child: NotificationsNotification( notification: notification, onDelete: callback, @@ -75,7 +80,7 @@ void main() { await expectLater(find.byType(TestApp), matchesGoldenFile('goldens/notification.png')); await tester.tap(find.byType(NotificationsNotification)); - verify(() => router.go('/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); await tester.drag(find.byType(NotificationsNotification), const Offset(500, 0)); await tester.pumpAndSettle(); diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart index 85bffd91f52..7b5392c48a4 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/message.dart @@ -1,7 +1,6 @@ import 'package:built_collection/built_collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:intersperse/intersperse.dart'; import 'package:intl/intl.dart'; import 'package:neon_framework/blocs.dart'; @@ -244,7 +243,7 @@ class TalkMessagePreview extends StatelessWidget { references: BuiltList(), isPreview: true, style: Theme.of(context).textTheme.bodyMedium!, - onReferenceClicked: context.go, + onReferenceClicked: (url) async => launchUrl(NeonProvider.of(context), url), ), ], ), @@ -333,7 +332,7 @@ class TalkSystemMessage extends StatelessWidget { chatMessage: chatMessage, references: BuiltList(), style: Theme.of(context).textTheme.labelSmall!, - onReferenceClicked: context.go, + onReferenceClicked: (url) async => launchUrl(NeonProvider.of(context), url), ), ), ), @@ -540,7 +539,7 @@ class _TalkCommentMessageState extends State { ? labelColor : null, ), - onReferenceClicked: context.go, + onReferenceClicked: (url) async => launchUrl(NeonProvider.of(context), url), ), maxLines: widget.isParent ? 1 : null, overflow: widget.isParent ? TextOverflow.ellipsis : TextOverflow.visible, diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/reference_preview.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/reference_preview.dart index ed2279fd5cf..ec0179a2d89 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/reference_preview.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/reference_preview.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; @@ -85,7 +84,7 @@ class TalkReferencePreview extends StatelessWidget { ), ), child: InkWell( - onTap: () => context.go(url), + onTap: () async => launchUrl(NeonProvider.of(context), url), child: ConstrainedBox( constraints: const BoxConstraints( maxHeight: 100, diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/deck_card.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/deck_card.dart index a8c13cf12e7..0490e46fffd 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/deck_card.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/deck_card.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; -import 'package:go_router/go_router.dart'; +import 'package:neon_framework/models.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/spreed.dart' as spreed; /// Widget to display a Deck card from a rich object. @@ -20,8 +21,8 @@ class TalkRichObjectDeckCard extends StatelessWidget { child: Card( clipBehavior: Clip.hardEdge, child: InkWell( - onTap: () { - context.go(parameter.link!); + onTap: () async { + await launchUrl(NeonProvider.of(context), parameter.link!); }, child: ListTile( // TODO: Use the actual Deck logo diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/fallback.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/fallback.dart index 0aeac18800a..72c495bb778 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/fallback.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/fallback.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; @@ -50,10 +49,10 @@ class TalkRichObjectFallback extends StatelessWidget { parameter.name, style: textStyle, ), - onPressed: () { + onPressed: () async { final link = parameter.link; if (link != null) { - context.go(link); + await launchUrl(NeonProvider.of(context), link); } }, ); diff --git a/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/file.dart b/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/file.dart index ee0cf17a977..8ab2e1596f2 100644 --- a/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/file.dart +++ b/packages/neon_framework/packages/talk_app/lib/src/widgets/rich_object/file.dart @@ -1,7 +1,8 @@ import 'package:files_icons/files_icons.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_framework/theme.dart'; +import 'package:neon_framework/utils.dart'; import 'package:nextcloud/spreed.dart' as spreed; import 'package:talk_app/src/widgets/rich_object/file_preview.dart'; @@ -45,8 +46,8 @@ class TalkRichObjectFile extends StatelessWidget { } return InkWell( - onTap: () { - context.go(parameter.link!); + onTap: () async { + await launchUrl(NeonProvider.of(context), parameter.link!); }, child: child, ); diff --git a/packages/neon_framework/packages/talk_app/pubspec.yaml b/packages/neon_framework/packages/talk_app/pubspec.yaml index d4789152b3f..8796cf6b8b9 100644 --- a/packages/neon_framework/packages/talk_app/pubspec.yaml +++ b/packages/neon_framework/packages/talk_app/pubspec.yaml @@ -48,6 +48,7 @@ dev_dependencies: path: packages/neon_lints provider: ^6.1.2 shared_preferences: ^2.3.2 + url_launcher_platform_interface: ^2.3.2 vector_graphics_compiler: ^1.1.11+1 flutter: diff --git a/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart b/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart index c0459447b68..14f89427eca 100644 --- a/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart +++ b/packages/neon_framework/packages/talk_app/test/reference_preview_test.dart @@ -6,21 +6,29 @@ import 'package:neon_framework/testing.dart'; import 'package:neon_framework/widgets.dart'; import 'package:provider/provider.dart'; import 'package:talk_app/src/widgets/reference_preview.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'testing.dart'; void main() { + late MockUrlLauncher urlLauncher; + setUpAll(() { FakeNeonStorage.setup(); + + registerFallbackValue(const LaunchOptions()); + + urlLauncher = MockUrlLauncher(); + // ignore: discarded_futures + when(() => urlLauncher.launchUrl(any(), any())).thenAnswer((_) async => true); + + UrlLauncherPlatform.instance = urlLauncher; }); testWidgets('Loading', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( - TestApp( - router: router, - child: const TalkReferencePreview( + const TestApp( + child: TalkReferencePreview( url: '/link', openGraphObject: null, ), @@ -63,11 +71,8 @@ void main() { when(() => openGraphObject.name).thenReturn('name'); when(() => openGraphObject.link).thenReturn('/link'); - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( TestApp( - router: router, providers: [ Provider.value(value: account), ], @@ -79,7 +84,7 @@ void main() { ); await tester.tap(find.byType(TalkReferencePreview)); - verify(() => router.go('/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); testWidgets('With thumb', (tester) async { diff --git a/packages/neon_framework/packages/talk_app/test/rich_object_test.dart b/packages/neon_framework/packages/talk_app/test/rich_object_test.dart index 0f79ea5c5b1..4e491dba460 100644 --- a/packages/neon_framework/packages/talk_app/test/rich_object_test.dart +++ b/packages/neon_framework/packages/talk_app/test/rich_object_test.dart @@ -17,14 +17,22 @@ import 'package:talk_app/src/widgets/rich_object/fallback.dart'; import 'package:talk_app/src/widgets/rich_object/file.dart'; import 'package:talk_app/src/widgets/rich_object/file_preview.dart'; import 'package:talk_app/src/widgets/rich_object/mention.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { + late MockUrlLauncher urlLauncher; late Account account; setUpAll(() { FakeNeonStorage.setup(); - registerFallbackValue(Uri()); + registerFallbackValue(const LaunchOptions()); + + urlLauncher = MockUrlLauncher(); + // ignore: discarded_futures + when(() => urlLauncher.launchUrl(any(), any())).thenAnswer((_) async => true); + + UrlLauncherPlatform.instance = urlLauncher; }); setUp(() { @@ -32,11 +40,11 @@ void main() { }); testWidgets('Deck card', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( TestApp( - router: router, + providers: [ + Provider.value(value: account), + ], child: TalkRichObjectDeckCard( parameter: spreed.RichObjectParameter( (b) => b @@ -58,7 +66,7 @@ void main() { ); await tester.tap(find.byType(TalkRichObjectDeckCard)); - verify(() => router.go('/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); group('Mention', () { @@ -224,11 +232,11 @@ void main() { group('File', () { testWidgets('Opens link', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( TestApp( - router: router, + providers: [ + Provider.value(value: account), + ], child: TalkRichObjectFile( parameter: spreed.RichObjectParameter( (b) => b @@ -245,7 +253,7 @@ void main() { ); await tester.tap(find.byType(TalkRichObjectFile)); - verify(() => router.go('/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); testWidgets('With preview', (tester) async { @@ -432,11 +440,11 @@ void main() { group('Fallback', () { testWidgets('Opens link', (tester) async { - final router = MockGoRouter(); - await tester.pumpWidgetWithAccessibility( TestApp( - router: router, + providers: [ + Provider.value(value: account), + ], child: TalkRichObjectFallback( parameter: spreed.RichObjectParameter( (b) => b @@ -451,7 +459,7 @@ void main() { ); await tester.tap(find.byType(TalkRichObjectFallback)); - verify(() => router.go('/link')).called(1); + verify(() => urlLauncher.launchUrl('https://cloud.example.com:8443/link', any())).called(1); }); testWidgets('Without icon', (tester) async { diff --git a/packages/neon_framework/test/account_test.dart b/packages/neon_framework/test/account_test.dart index f3e470e4424..6b60a2e843a 100644 --- a/packages/neon_framework/test/account_test.dart +++ b/packages/neon_framework/test/account_test.dart @@ -52,13 +52,6 @@ void main() { Uri.parse('$serverURL/$testURL'), ); }); - - test('Strip', () { - expect( - account.stripUri(Uri.parse('$serverURL/$testURL')), - Uri.parse('/$testURL'), - ); - }); }); } }); diff --git a/packages/neon_framework/test/router_test.dart b/packages/neon_framework/test/router_test.dart index 9eb24707bcf..e2e715de749 100644 --- a/packages/neon_framework/test/router_test.dart +++ b/packages/neon_framework/test/router_test.dart @@ -1,73 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:neon_framework/blocs.dart'; -import 'package:neon_framework/models.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; import 'package:neon_framework/src/router.dart'; import 'package:neon_framework/testing.dart'; import 'package:neon_framework/utils.dart'; -import 'package:nextcloud/core.dart' as core; -import 'package:provider/provider.dart'; -import 'package:rxdart/rxdart.dart'; -// ignore: depend_on_referenced_packages -import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; - -core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data buildCapabilities( - core.CoreCapabilities? capabilities, -) => - core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data( - (b) => b - ..version.update( - (b) => b - ..major = 0 - ..minor = 0 - ..micro = 0 - ..string = '' - ..edition = '' - ..extendedSupport = false, - ) - ..capabilities = ( - // We need to provide at least one capability because anyOf expects at least one schema to match - commentsCapabilities: null, - coreCapabilities: capabilities, - corePublicCapabilities: null, - davCapabilities: null, - dropAccountCapabilities: null, - filesCapabilities: null, - filesSharingCapabilities: null, - filesTrashbinCapabilities: null, - filesVersionsCapabilities: null, - notesCapabilities: null, - notificationsCapabilities: null, - provisioningApiCapabilities: null, - sharebymailCapabilities: null, - spreedCapabilities: null, - spreedPublicCapabilities: null, - systemtagsCapabilities: null, - tablesCapabilities: null, - themingPublicCapabilities: null, - userStatusCapabilities: null, - weatherStatusCapabilities: null, - ), - ); void main() { group('redirect', () { - testWidgets('Strip index.php', (tester) async { - await tester.pumpWidgetWithAccessibility( - TestApp( - child: Container(), - ), - ); - final context = tester.element(find.byType(Container)); - - final state = MockGoRouterState(); - when(() => state.uri).thenReturn(Uri(path: '/index.php/test')); - - expect(redirect(context, state), '/test'); - }); - testWidgets('QR-Code login', (tester) async { await tester.pumpWidgetWithAccessibility( TestApp( @@ -82,36 +22,6 @@ void main() { expect(redirect(context, state), '/login/check/server/JohnDoe/super_secret?server-url=example.com'); }); - testWidgets('Strip account', (tester) async { - final accountsBloc = MockAccountsBloc(); - when(() => accountsBloc.hasAccounts).thenReturn(true); - - final account = Account( - (b) => b - ..serverURL = Uri.parse('http://example.com') - ..username = '', - ); - - await tester.pumpWidgetWithAccessibility( - TestApp( - providers: [ - NeonProvider.value(value: accountsBloc), - Provider.value(value: account), - ], - child: Container(), - ), - ); - final context = tester.element(find.byType(Container)); - - final state = MockGoRouterState(); - - when(() => state.uri).thenReturn(Uri.parse('http://example.com/test')); - expect(redirect(context, state), '/test'); - - when(() => state.uri).thenReturn(Uri.parse('example.com/test')); - expect(redirect(context, state), null); - }); - testWidgets('Login', (tester) async { final accountsBloc = MockAccountsBloc(); when(() => accountsBloc.hasAccounts).thenReturn(false); @@ -137,101 +47,4 @@ void main() { expect(redirect(context, state), null); }); }); - - group('onException', () { - testWidgets('Not found', (tester) async { - final accountsBloc = MockAccountsBloc(); - when(() => accountsBloc.hasAccounts).thenReturn(false); - - await tester.pumpWidgetWithAccessibility( - TestApp( - providers: [ - NeonProvider.value(value: accountsBloc), - ], - child: Container(), - ), - ); - final context = tester.element(find.byType(Container)); - - final router = MockGoRouter(); - when(() => router.push(any())).thenAnswer((_) => Future.value()); - - final state = MockGoRouterState(); - when(() => state.uri).thenReturn(Uri(path: '/test')); - - await onException(context, state, router); - verify(() => router.push('/not-found/${Uri.encodeComponent('/test')}')).called(1); - }); - - group('Complete account', () { - late MockUrlLauncher urlLauncher; - - setUpAll(() { - registerFallbackValue(const LaunchOptions()); - - urlLauncher = MockUrlLauncher(); - // ignore: discarded_futures - when(() => urlLauncher.launchUrl(any(), any())).thenAnswer((_) async => true); - - UrlLauncherPlatform.instance = urlLauncher; - }); - - for (final (path, modRewriteWorking, url) in [ - ('/test', true, 'http://example.com/test'), - ('/test', false, 'http://example.com/index.php/test'), - ('http://example.org/test', false, 'http://example.org/test'), - ]) { - testWidgets('Mod rewrite ${modRewriteWorking ? '' : 'not '}working $path', (tester) async { - final accountsBloc = MockAccountsBloc(); - when(() => accountsBloc.hasAccounts).thenReturn(true); - - final capabilitiesBloc = MockCapabilitiesBloc(); - when(() => capabilitiesBloc.capabilities).thenAnswer( - (_) => BehaviorSubject.seeded( - Result.success( - buildCapabilities( - core.CoreCapabilities( - (b) => b.core.update( - (b) => b - ..pollinterval = 0 - ..webdavRoot = '' - ..referenceApi = false - ..referenceRegex = '' - ..modRewriteWorking = modRewriteWorking, - ), - ), - ), - ), - ), - ); - - final account = Account( - (b) => b - ..serverURL = Uri.parse('http://example.com') - ..username = '', - ); - - await tester.pumpWidgetWithAccessibility( - TestApp( - providers: [ - NeonProvider.value(value: accountsBloc), - Provider.value(value: account), - NeonProvider.value(value: capabilitiesBloc), - ], - child: Container(), - ), - ); - final context = tester.element(find.byType(Container)); - - final router = MockGoRouter(); - - final state = MockGoRouterState(); - when(() => state.uri).thenReturn(Uri.parse(path)); - - await onException(context, state, router); - verify(() => urlLauncher.launchUrl(url, any())).called(1); - }); - } - }); - }); }