From 8653f1dd6b8827810d90029a17adce52837bb551 Mon Sep 17 00:00:00 2001 From: Doug Date: Wed, 15 Jun 2022 10:10:22 +0100 Subject: [PATCH] #40: Add UserSessionStoreProtocol. Only log out of the specific account. Add tests for the keychain controller. Expand test coverage. PR comments --- ElementX.xcodeproj/project.pbxproj | 86 ++++++++++-------- ElementX/Sources/AppCoordinator.swift | 16 ++-- .../Other/{ => Extensions}/Bundle.swift | 0 .../Other/{ => Extensions}/String.swift | 0 .../AuthenticationCoordinator.swift | 6 +- ...icationMetrics.swift => UIConstants.swift} | 6 +- .../SplashScreenCoordinator.swift | 6 +- .../SplashScreen/SplashScreenModels.swift | 7 +- .../SplashScreen/View/SplashScreen.swift | 3 +- .../SplashScreen/View/SplashScreenPage.swift | 4 +- .../KeychainController.swift | 12 ++- .../KeychainControllerProtocol.swift | 1 + .../UserSessionStore.swift | 14 +-- .../UserSessionStoreProtocol.swift | 42 +++++++++ ElementX/Sources/UITestsAppCoordinator.swift | 4 +- UITests/Sources/SplashScreenUITests.swift | 43 ++++++++- .../Sources/KeychainControllerTests.swift | 89 +++++++++++++++++++ 17 files changed, 261 insertions(+), 78 deletions(-) rename ElementX/Sources/Other/{ => Extensions}/Bundle.swift (100%) rename ElementX/Sources/Other/{ => Extensions}/String.swift (100%) rename ElementX/Sources/Screens/Authentication/{AuthenticationMetrics.swift => UIConstants.swift} (81%) rename ElementX/Sources/Services/{UserSession => UserSessionStore}/KeychainController.swift (76%) rename ElementX/Sources/Services/{UserSession => UserSessionStore}/KeychainControllerProtocol.swift (88%) rename ElementX/Sources/Services/{UserSession => UserSessionStore}/UserSessionStore.swift (94%) create mode 100644 ElementX/Sources/Services/UserSessionStore/UserSessionStoreProtocol.swift create mode 100644 UnitTests/Sources/KeychainControllerTests.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index f4136a49c..4d88df161 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -21,7 +21,6 @@ 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; 072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; }; 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; }; - 0C601923A872A87C775B889A /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3689E6F87850DD65DAA45428 /* KeychainControllerProtocol.swift */; }; 0E8C480700870BB34A2A360F /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; }; 0EA6537A07E2DC882AEA5962 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 187853A7E643995EE49FAD43 /* Localizable.stringsdict */; }; 0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */; }; @@ -67,6 +66,7 @@ 352C439BE0F75E101EF11FB1 /* RoomScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */; }; 35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; }; 368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; }; + 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; 3772354754450F2B54107E17 /* TemplateSimpleScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4EDB32B97910AAAFE632B2 /* TemplateSimpleScreenViewModelProtocol.swift */; }; 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; }; 39AE84C8E5F2FE9D2DC7775C /* EventBasedTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56008790A9C4479A6B31FDF4 /* EventBasedTimelineView.swift */; }; @@ -125,6 +125,7 @@ 77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */; }; 78B71D53C1FC55FB7A9B75F0 /* RoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24B0C97D2F560BCB72BE73B1 /* RoomTimelineController.swift */; }; 7963F98CDFDEAC75E072BD81 /* TextRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */; }; + 79A6E08ADE6E7C460A8A17A5 /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */; }; 7A54700193DC1F264368746A /* UserIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E077F76026C85ED96FEBB810 /* UserIndicatorPresenter.swift */; }; 7B3D3AFD511D496DED18910B /* TemplateSimpleScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C485C186CEC78443DA96BDC8 /* TemplateSimpleScreenViewModelTests.swift */; }; 7BB31E67648CF32D2AB5E502 /* RoomScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */; }; @@ -133,7 +134,6 @@ 7D1DAAA364A9A29D554BD24E /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0950733DD4BA83EEE752E259 /* PlaceholderAvatarImage.swift */; }; 7DE5EB4CB2401C672257283C /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B12969CEC0051BC750DA5068 /* WeakKeyDictionary.swift */; }; 7E1EDBA3934E6C29E5BD045B /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77DD2DA5DC8654F2A80FF1D /* Bundle.swift */; }; - 7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */; }; 7F19E97E7985F518C9018B83 /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF47564C584F614B7287F3EB /* RootRouter.swift */; }; 7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */; }; 7FA4227B2BAAA71560252866 /* UserIndicatorDismissal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D1532B5D9FB0C8461A1453 /* UserIndicatorDismissal.swift */; }; @@ -174,7 +174,6 @@ A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; A941EAD7F407F2ED6DA54A31 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA97D630B74B0616C1468CBD /* LoginScreen.swift */; }; AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9760103CF316DF68698BCFE6 /* LaunchScreen.storyboard */; }; - AE5360A13B915F1CAECDE4CC /* AuthenticationMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD4E0CB6EE4AD939E46A62 /* AuthenticationMetrics.swift */; }; B0EDAF55877DE19B67837C22 /* TemplateSimpleScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C29670CEC77346F31EE94C /* TemplateSimpleScreenModels.swift */; }; B245583C63F8F90357B87FAE /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 3853B78FB8531B83936C5DA6 /* SwiftState */; }; B3FDB1D9CF40777695DBBD1D /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A9AB74614131D6706894E0C /* AppCoordinatorStateMachine.swift */; }; @@ -209,18 +208,21 @@ DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC95CD75B688E946438165 /* Coordinator.swift */; }; DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A63815AD6A5C306453342F2 /* ImageRoomTimelineItem.swift */; }; DE4F8C4E0F1DB4832F09DE97 /* HomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */; }; - DF790EF2E4D41D1091AEB263 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317F02B15921BF5CC8486990 /* KeychainController.swift */; }; DFF7D6A6C26DDD40D00AE579 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = F012CB5EE3F2B67359F6CC52 /* target.yml */; }; E81EEC1675F2371D12A880A3 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61ADFB893DEF81E58DF3FAB9 /* MockRoomTimelineController.swift */; }; E9CEAF2C38E4E00459B811D9 /* LoginScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2082B5226B2A3A4D0798B6 /* LoginScreenModels.swift */; }; EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; }; EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; }; EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; }; + EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */; }; ED4F663C783E9A8C0E80B983 /* TemplateSimpleScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47543EB19F3DCF308751F53C /* TemplateSimpleScreenViewModel.swift */; }; EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; }; + EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; }; EF99A92701E401C4CD5ADC50 /* SplashScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE978A6118C131D7F2A04B3 /* SplashScreenModels.swift */; }; F01DB7DD607015557CD48B33 /* ViewFrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242A3BC7FAE2256930FB8527 /* ViewFrameReader.swift */; }; F03E16ED043C62FED5A07AE0 /* MatrixEntitityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */; }; + F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */; }; + F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */; }; F56261126E368C831B3DE976 /* NavigationRouterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 752DEC02D93AFF46BC13313A /* NavigationRouterType.swift */; }; F656F92A63D3DC1978D79427 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; }; F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; }; @@ -261,14 +263,13 @@ 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineProviderProtocol.swift; sourceTree = ""; }; 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementToggleStyle.swift; sourceTree = ""; }; 09747989908EC5E4AA29F844 /* MemberDetailsProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailsProviderProtocol.swift; sourceTree = ""; }; - 09DD4E0CB6EE4AD939E46A62 /* AuthenticationMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationMetrics.swift; sourceTree = ""; }; 0A191D3FDB995309C7E2DE7D /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 0AB7A0C06CB527A1095DEB33 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = da; path = da.lproj/Localizable.stringsdict; sourceTree = ""; }; 0C13A92C1E9C79F055B8133D /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = ""; }; 0CB569EAA5017B5B23970655 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Localizable.strings; sourceTree = ""; }; + 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; 0DD16CE9A66C9040B066AD60 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = ""; }; 0E7062F88E9D5F79C8A80524 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = ""; }; - 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = ""; }; 0EE9EAF0309A2A1D67D8FAF5 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sv; path = sv.lproj/Localizable.stringsdict; sourceTree = ""; }; 0F7A812F160E75B69A9181A2 /* SplashScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenCoordinator.swift; sourceTree = ""; }; 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFactory.swift; sourceTree = ""; }; @@ -308,18 +309,17 @@ 2AE83A3DD63BCFBB956FE5CB /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = ""; }; 2BEB3259B2208E5AE5BB3F65 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 2CF9FE7E0CF9F40D1509E63A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = bg; path = bg.lproj/Localizable.stringsdict; sourceTree = ""; }; - 317F02B15921BF5CC8486990 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; 31D6764D6976D235926FE5FC /* HomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModel.swift; sourceTree = ""; }; 325A2B3278875554DDEB8A9B /* SplashScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenUITests.swift; sourceTree = ""; }; 32CE6D4FF64C9A3C18619224 /* SplashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = ""; }; 33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; 35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 36322DD0D4E29D31B0945ADC /* EventBriefFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactory.swift; sourceTree = ""; }; - 3689E6F87850DD65DAA45428 /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = ""; }; 3747C96188856006F784BF49 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ko.lproj/Localizable.stringsdict; sourceTree = ""; }; 3782C506F4FF1AADF61B6212 /* tlh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tlh; path = tlh.lproj/Localizable.strings; sourceTree = ""; }; 399427358A80BA2848E698A2 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = ""; }; 39EBB6903EFD4236B8D11A42 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = ""; }; + 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = ""; }; 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = ""; }; 3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = ""; }; 3CDF9E55650D6035D6536538 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "nb-NO"; path = "nb-NO.lproj/Localizable.stringsdict"; sourceTree = ""; }; @@ -446,6 +446,7 @@ 8BF686BA36D0C2FA3C63DFDF /* ImageRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomMessage.swift; sourceTree = ""; }; 8C0AA893D6F8A2F563E01BB9 /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = in; path = in.lproj/Localizable.stringsdict; sourceTree = ""; }; 8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDetailProviderManager.swift; sourceTree = ""; }; + 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = ""; }; 8D8169443E5AC5FF71BFB3DB /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 90733775209F4D4D366A268F /* RootRouterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouterType.swift; sourceTree = ""; }; 92B61C243325DC76D3086494 /* EventBriefFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBriefFactoryProtocol.swift; sourceTree = ""; }; @@ -463,6 +464,7 @@ 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = ""; }; 9C5E81214D27A6B898FC397D /* ElementX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = ""; }; 9CE3C90E487B255B735D73C8 /* RoomScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModel.swift; sourceTree = ""; }; + 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConstants.swift; sourceTree = ""; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; A1C29670CEC77346F31EE94C /* TemplateSimpleScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreenModels.swift; sourceTree = ""; }; A1ED7E89865201EE7D53E6DA /* SeparatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeparatorRoomTimelineItem.swift; sourceTree = ""; }; @@ -510,6 +512,7 @@ BE03C54FC7AAE0FC03EC8976 /* SplashScreenPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenPage.swift; sourceTree = ""; }; BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = ""; }; BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = ""; }; + BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = ""; }; C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXAttributeScope.swift; sourceTree = ""; }; C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; C21ECC295F4DE8DAA86D62AC /* RoomSummaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProtocol.swift; sourceTree = ""; }; @@ -582,6 +585,7 @@ F9BA045DC4CA12D030ACF558 /* TemplateSimpleScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreen.swift; sourceTree = ""; }; F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = ""; }; FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = ""; }; + FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerTests.swift; sourceTree = ""; }; FE2DF459F1737A594667CC46 /* EmoteRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomMessage.swift; sourceTree = ""; }; FF4EDB32B97910AAAFE632B2 /* TemplateSimpleScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateSimpleScreenViewModelProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -660,7 +664,7 @@ 40E6246F03D1FE377BC5D963 /* Room */, 82D5AD3EAE3A5C1068A44A88 /* Session */, FCDF06BDB123505F0334B4F9 /* Timeline */, - CBBF6127C313A5412E438BC6 /* UserSession */, + 90C85A862720155C0CF63B02 /* UserSessionStore */, ); path = Services; sourceTree = ""; @@ -716,13 +720,6 @@ path = Resources; sourceTree = ""; }; - 298F75357B344DE964106404 /* Login */ = { - isa = PBXGroup; - children = ( - ); - path = Login; - sourceTree = ""; - }; 304D3532D4FFC1F0ABC0626E /* ViewFrameReader */ = { isa = PBXGroup; children = ( @@ -956,6 +953,7 @@ DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */, 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */, 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */, + FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */, C070FD43DC6BF4E50217965A /* LocalizationTests.swift */, 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */, 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */, @@ -1066,6 +1064,33 @@ path = HTMLParsing; sourceTree = ""; }; + 90C85A862720155C0CF63B02 /* UserSessionStore */ = { + isa = PBXGroup; + children = ( + 3A4427F9E0571B4E6E048A2B /* KeychainController.swift */, + 0CD51F9FDC91C231906D76C8 /* KeychainControllerProtocol.swift */, + 8C37FB986891D90BEAA93EAE /* UserSessionStore.swift */, + BF1B52D0ABBA7091A991CAFE /* UserSessionStoreProtocol.swift */, + ); + path = UserSessionStore; + sourceTree = ""; + }; + 90F48FEF84016ED42A94BA24 /* LoginScreen */ = { + isa = PBXGroup; + children = ( + ); + path = LoginScreen; + sourceTree = ""; + }; + 91AC284E285B25BB00B7ADB9 /* Extensions */ = { + isa = PBXGroup; + children = ( + D77DD2DA5DC8654F2A80FF1D /* Bundle.swift */, + 4519B90DE2F54443E5F43DA8 /* String.swift */, + ); + path = Extensions; + sourceTree = ""; + }; 9413F680ECDFB2B0DDB0DEF2 /* Packages */ = { isa = PBXGroup; children = ( @@ -1227,11 +1252,10 @@ children = ( 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */, E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */, - D77DD2DA5DC8654F2A80FF1D /* Bundle.swift */, 95CC95CD75B688E946438165 /* Coordinator.swift */, 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */, F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */, - 4519B90DE2F54443E5F43DA8 /* String.swift */, + 91AC284E285B25BB00B7ADB9 /* Extensions */, 8F9A844EB44B6AD7CA18FD96 /* HTMLParsing */, 06501F0E978B2D5C92771DC7 /* Logging */, FE50232944F9E67ADD7A2D21 /* Routers */, @@ -1251,16 +1275,6 @@ path = UITests; sourceTree = ""; }; - CBBF6127C313A5412E438BC6 /* UserSession */ = { - isa = PBXGroup; - children = ( - 317F02B15921BF5CC8486990 /* KeychainController.swift */, - 3689E6F87850DD65DAA45428 /* KeychainControllerProtocol.swift */, - 0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */, - ); - path = UserSession; - sourceTree = ""; - }; E59565F441830B19DBAE567C /* Screens */ = { isa = PBXGroup; children = ( @@ -1305,8 +1319,8 @@ isa = PBXGroup; children = ( D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */, - 09DD4E0CB6EE4AD939E46A62 /* AuthenticationMetrics.swift */, - 298F75357B344DE964106404 /* Login */, + 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */, + 90F48FEF84016ED42A94BA24 /* LoginScreen */, ); path = Authentication; sourceTree = ""; @@ -1650,6 +1664,7 @@ 9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */, F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */, 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */, + EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */, 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */, 149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */, 7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */, @@ -1678,7 +1693,6 @@ 3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */, A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */, EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */, - AE5360A13B915F1CAECDE4CC /* AuthenticationMetrics.swift in Sources */, CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */, 38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */, B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */, @@ -1719,8 +1733,8 @@ DDB80FD2753FEAAE43CC2AAE /* ImageRoomTimelineItem.swift in Sources */, D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */, A5C8F013ED9FB8AA6FEE18A7 /* InfoPlist.swift in Sources */, - DF790EF2E4D41D1091AEB263 /* KeychainController.swift in Sources */, - 0C601923A872A87C775B889A /* KeychainControllerProtocol.swift in Sources */, + F4C3FEDB1B3A05376A1723A3 /* KeychainController.swift in Sources */, + F2DD8661B5C0BA2BB526FA6C /* KeychainControllerProtocol.swift in Sources */, 9C9E48A627C7C166084E3F5B /* LabelledActivityIndicatorView.swift in Sources */, D826154612415D2A3BB6EBF3 /* ListTableViewAdapter.swift in Sources */, A941EAD7F407F2ED6DA54A31 /* LoginScreen.swift in Sources */, @@ -1813,6 +1827,7 @@ 500CB65ED116B81DA52FDAEE /* TimelineView.swift in Sources */, 4669804D0369FBED4E8625D1 /* ToastViewPresenter.swift in Sources */, 9CB5129C83F75921E5E28028 /* ToastViewState.swift in Sources */, + 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */, 0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */, 03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */, 17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */, @@ -1826,7 +1841,8 @@ 80E04BE80A89A78FBB4863BB /* UserIndicatorViewPresentable.swift in Sources */, 8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */, 978BB24F2A5D31EE59EEC249 /* UserSessionProtocol.swift in Sources */, - 7E91BAC17963ED41208F489B /* UserSessionStore.swift in Sources */, + 79A6E08ADE6E7C460A8A17A5 /* UserSessionStore.swift in Sources */, + EBD6C79705B3DDB2F7E5F554 /* UserSessionStoreProtocol.swift in Sources */, F01DB7DD607015557CD48B33 /* ViewFrameReader.swift in Sources */, 01F4A40C1EDCEC8DC4EC9CFA /* WeakDictionary.swift in Sources */, 77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */, diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index bec1b3d15..4f057c1dc 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -18,9 +18,9 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { private let navigationRouter: NavigationRouter - private let userSessionStore: UserSessionStore + private let userSessionStore: UserSessionStoreProtocol - private var userSession: UserSession! + private var userSession: UserSessionProtocol! private let memberDetailProviderManager: MemberDetailProviderManager @@ -88,7 +88,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { stateMachine.processEvent(.attemptedSignIn) } - func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSession) { + func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didLoginWithSession userSession: UserSessionProtocol) { self.userSession = userSession remove(childCoordinator: authenticationCoordinator) stateMachine.processEvent(.succeededSigningIn) @@ -100,14 +100,14 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { // MARK: - Private - // swiftlint:disable cyclomatic_complexity + // swiftlint:disable cyclomatic_complexity function_body_length private func setupStateMachine() { stateMachine.addTransitionHandler { [weak self] context in guard let self = self else { return } switch (context.fromState, context.event, context.toState) { case (.initial, .startWithAuthentication, .signedOut): - self.showAuthentication() + self.startAuthentication() case (.signedOut, .attemptedSignIn, .signingIn): self.showLoadingIndicator() case (.signingIn, .failedSigningIn, .signedOut): @@ -151,7 +151,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { fatalError("Failed transition with context: \(context)") } } - // swiftlint:enable cyclomatic_complexity + // swiftlint:enable cyclomatic_complexity function_body_length private func restoreUserSession() { Task { @@ -166,7 +166,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { } } - private func showAuthentication() { + private func startAuthentication() { let coordinator = AuthenticationCoordinator(userSessionStore: userSessionStore, navigationRouter: navigationRouter) coordinator.delegate = self @@ -184,7 +184,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { mainNavigationController.setViewControllers([splashViewController], animated: false) - showAuthentication() + startAuthentication() } private func presentHomeScreen() { diff --git a/ElementX/Sources/Other/Bundle.swift b/ElementX/Sources/Other/Extensions/Bundle.swift similarity index 100% rename from ElementX/Sources/Other/Bundle.swift rename to ElementX/Sources/Other/Extensions/Bundle.swift diff --git a/ElementX/Sources/Other/String.swift b/ElementX/Sources/Other/Extensions/String.swift similarity index 100% rename from ElementX/Sources/Other/String.swift rename to ElementX/Sources/Other/Extensions/String.swift diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift index 28d251385..2fc2d782c 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift @@ -19,7 +19,7 @@ protocol AuthenticationCoordinatorDelegate: AnyObject { func authenticationCoordinatorDidStartLoading(_ authenticationCoordinator: AuthenticationCoordinator) func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, - didLoginWithSession userSession: UserSession) + didLoginWithSession userSession: UserSessionProtocol) func authenticationCoordinator(_ authenticationCoordinator: AuthenticationCoordinator, didFailWithError error: AuthenticationCoordinatorError) @@ -27,7 +27,7 @@ protocol AuthenticationCoordinatorDelegate: AnyObject { class AuthenticationCoordinator: Coordinator { - private let userSessionStore: UserSessionStore + private let userSessionStore: UserSessionStoreProtocol private let navigationRouter: NavigationRouter private(set) var clientProxy: ClientProxyProtocol? @@ -35,7 +35,7 @@ class AuthenticationCoordinator: Coordinator { weak var delegate: AuthenticationCoordinatorDelegate? - init(userSessionStore: UserSessionStore, + init(userSessionStore: UserSessionStoreProtocol, navigationRouter: NavigationRouter) { self.userSessionStore = userSessionStore self.navigationRouter = navigationRouter diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationMetrics.swift b/ElementX/Sources/Screens/Authentication/UIConstants.swift similarity index 81% rename from ElementX/Sources/Screens/Authentication/AuthenticationMetrics.swift rename to ElementX/Sources/Screens/Authentication/UIConstants.swift index eb7394930..415d9c79b 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationMetrics.swift +++ b/ElementX/Sources/Screens/Authentication/UIConstants.swift @@ -16,13 +16,11 @@ import SwiftUI -/// Metrics used across the entire onboarding flow. -struct AuthenticationMetrics { +/// Standard constants used across the app's UI. +struct UIConstants { static let maxContentWidth: CGFloat = 600 static let maxContentHeight: CGFloat = 750 /// The padding used between the top of the main content and the navigation bar. static let topPaddingToNavigationBar: CGFloat = 16 - /// The width/height used for the main icon shown in most of the screens. - static let iconSize: CGFloat = 90 } diff --git a/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift b/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift index 12e32597b..269cb73a2 100644 --- a/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift +++ b/ElementX/Sources/Screens/SplashScreen/SplashScreenCoordinator.swift @@ -16,11 +16,7 @@ import SwiftUI -protocol SplashScreenCoordinatorProtocol: Coordinator, Presentable { - var callback: ((SplashScreenCoordinatorAction) -> Void)? { get set } -} - -final class SplashScreenCoordinator: SplashScreenCoordinatorProtocol { +final class SplashScreenCoordinator: Coordinator, Presentable { // MARK: - Properties diff --git a/ElementX/Sources/Screens/SplashScreen/SplashScreenModels.swift b/ElementX/Sources/Screens/SplashScreen/SplashScreenModels.swift index 30f5427c9..04642ac19 100644 --- a/ElementX/Sources/Screens/SplashScreen/SplashScreenModels.swift +++ b/ElementX/Sources/Screens/SplashScreen/SplashScreenModels.swift @@ -40,7 +40,7 @@ enum SplashScreenViewModelAction { // MARK: View -struct SplashScreenViewState: BindableState, CustomDebugStringConvertible { +struct SplashScreenViewState: BindableState { private enum Constants { static let gradientColors = [ Color(red: 0.95, green: 0.98, blue: 0.96), @@ -55,11 +55,6 @@ struct SplashScreenViewState: BindableState, CustomDebugStringConvertible { let content: [SplashScreenPageContent] var bindings: SplashScreenBindings - /// Custom debug description to reduce noise in the logs. - var debugDescription: String { - "SplashScreenViewState at page \(bindings.pageIndex)." - } - init() { // The pun doesn't translate, so we only use it for English. let locale = Locale.current diff --git a/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift b/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift index 3ba1190af..33ec6a95b 100644 --- a/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift +++ b/ElementX/Sources/Screens/SplashScreen/View/SplashScreen.swift @@ -51,6 +51,7 @@ struct SplashScreen: View { overlayHeight: overlayFrame.height + geometry.safeAreaInsets.bottom) .frame(width: geometry.size.width) .tag(-1) + .accessibilityIdentifier("hiddenPage") ForEach(0.. String? func accessTokens() -> [(username: String, accessToken: String)] + func removeAccessTokenForUsername(_ username: String) func removeAllAccessTokens() } diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift similarity index 94% rename from ElementX/Sources/Services/UserSession/UserSessionStore.swift rename to ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift index 7897a5715..df26ccf94 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSessionStore/UserSessionStore.swift @@ -18,14 +18,7 @@ import Foundation import MatrixRustSDK import Kingfisher -enum UserSessionStoreError: Error { - case missingCredentials - case failedRestoringLogin - case failedSettingUpSession -} - -@MainActor -class UserSessionStore { +class UserSessionStore: UserSessionStoreProtocol { private let keychainController: KeychainControllerProtocol @@ -70,8 +63,9 @@ class UserSessionStore { } func logout(userSession: UserSessionProtocol) { - keychainController.removeAllAccessTokens() - deleteBaseDirectory(for: userSession.clientProxy.userIdentifier) + let username = userSession.clientProxy.userIdentifier + keychainController.removeAccessTokenForUsername(username) + deleteBaseDirectory(for: username) } private func restorePreviousLogin(_ usernameTokenTuple: (username: String, accessToken: String)) async -> Result { diff --git a/ElementX/Sources/Services/UserSessionStore/UserSessionStoreProtocol.swift b/ElementX/Sources/Services/UserSessionStore/UserSessionStoreProtocol.swift new file mode 100644 index 000000000..0790f7e6a --- /dev/null +++ b/ElementX/Sources/Services/UserSessionStore/UserSessionStoreProtocol.swift @@ -0,0 +1,42 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixRustSDK + +enum UserSessionStoreError: Error { + case missingCredentials + case failedRestoringLogin + case failedSettingUpSession +} + +@MainActor +protocol UserSessionStoreProtocol { + /// Whether or not there are sessions in the store. + var hasSessions: Bool { get } + + /// Restores an existing user session. + func restoreUserSession() async -> Result + + /// Creates a user session for a new client from the SDK. + func userSession(for client: Client) async -> Result + + /// Logs out of the specified session. + func logout(userSession: UserSessionProtocol) + + /// Returns the location to store user data for a particular username. + func baseDirectoryPath(for username: String) -> String +} diff --git a/ElementX/Sources/UITestsAppCoordinator.swift b/ElementX/Sources/UITestsAppCoordinator.swift index e24b02ba4..7062cd31e 100644 --- a/ElementX/Sources/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITestsAppCoordinator.swift @@ -22,6 +22,8 @@ class UITestsAppCoordinator: Coordinator { window.tintColor = .element.accent let screens = mockScreens() + screens.forEach { $0.coordinator.start() } + let rootView = UITestsRootView(mockScreens: screens) { id in guard let screen = screens.first(where: { $0.id == id }) else { fatalError() @@ -52,5 +54,5 @@ class UITestsAppCoordinator: Coordinator { struct MockScreen: Identifiable { let id: String - let coordinator: Presentable + let coordinator: Coordinator & Presentable } diff --git a/UITests/Sources/SplashScreenUITests.swift b/UITests/Sources/SplashScreenUITests.swift index 677bf4695..044c5a9c1 100644 --- a/UITests/Sources/SplashScreenUITests.swift +++ b/UITests/Sources/SplashScreenUITests.swift @@ -16,11 +16,52 @@ import XCTest +@MainActor class SplashScreenUITests: XCTestCase { func testInitialStateComponents() { let app = Application.launch() app.goToScreenWithIdentifier("Splash Screen") - XCTAssert(app.buttons["Get started"].exists) + let getStartedButton = app.buttons["Get started"] + XCTAssertTrue(getStartedButton.exists, "The primary action button should be shown.") + } + + func testSwipingBetweenPages() async throws { + let app = Application.launch() + app.goToScreenWithIdentifier("Splash Screen") + + // Given the splash screen in its initial state. + let page1TitleText = app.staticTexts["Own your conversations."] + let page2TitleText = app.staticTexts["You're in control."] + let hiddenPageTitleText = app.staticTexts["hiddenPage"].firstMatch + + XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.") + XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.") + XCTAssertFalse(hiddenPageTitleText.isHittable, "The hidden page of the carousel should be offscreen.") + + // When swiping to the next screen. + page1TitleText.swipeLeft() + try await Task.sleep(nanoseconds: 200_000_000) // Wait for the animation. + + // Then the second screen should be shown. + XCTAssertFalse(page1TitleText.isHittable, "The title from the first page of the carousel should be offscreen.") + XCTAssertTrue(page2TitleText.isHittable, "The title from the second page of the carousel should be onscreen.") + + // When swiping back to the previous screen. + page2TitleText.swipeRight() + try await Task.sleep(nanoseconds: 200_000_000) // Wait for the animation. + + // Then the first screen should be shown again. + XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be onscreen.") + XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.") + + // When swiping back to the previous screen. + page1TitleText.swipeRight() + try await Task.sleep(nanoseconds: 200_000_000) // Wait for the animation. + + // Then the screen shouldn't change and the hidden screen should be ignored. + XCTAssertTrue(page1TitleText.isHittable, "The title from the first page of the carousel should be still be onscreen.") + XCTAssertFalse(page2TitleText.isHittable, "The title from the second page of the carousel should be offscreen.") + XCTAssertFalse(hiddenPageTitleText.isHittable, "It shouldn't be possible to swipe to the hidden page of the carousel.") } } diff --git a/UnitTests/Sources/KeychainControllerTests.swift b/UnitTests/Sources/KeychainControllerTests.swift new file mode 100644 index 000000000..5912dc050 --- /dev/null +++ b/UnitTests/Sources/KeychainControllerTests.swift @@ -0,0 +1,89 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import ElementX + +class KeychainControllerTests: XCTestCase { + var keychain: KeychainController! + + override func setUp() { + keychain = KeychainController(identifier: "\(ElementInfoPlist.cfBundleIdentifier).tests") + keychain.removeAllAccessTokens() + } + + func testAddAccessToken() { + // Given an empty keychain. + XCTAssertTrue(keychain.accessTokens().isEmpty, "The keychain should be empty to begin with.") + + // When adding an access token. + let username = "@test:example.com" + let accessToken = UUID().uuidString + keychain.setAccessToken(accessToken, forUsername: username) + + // Then the access token should be stored in the keychain. + XCTAssertEqual(keychain.accessTokenForUsername(username), accessToken, "The retrieved access token should match the value that was stored.") + } + + func testRemovingAccessToken() { + // Given a keychain with a stored access token. + let username = "@test:example.com" + let accessToken = UUID().uuidString + keychain.setAccessToken(accessToken, forUsername: username) + XCTAssertEqual(keychain.accessTokens().count, 1, "The keychain should have 1 access token.") + XCTAssertEqual(keychain.accessTokenForUsername(username), accessToken, "The initial access token should match the value that was stored.") + + // When deleting the access token. + keychain.removeAccessTokenForUsername(username) + + // Then the keychain should be empty. + XCTAssertTrue(keychain.accessTokens().isEmpty, "The keychain should be empty after deleting the token.") + XCTAssertNil(keychain.accessTokenForUsername(username), "There access token should not be returned after removal.") + } + + func testRemovingAllAccessTokens() { + // Given a keychain with 5 stored access tokens. + for index in 0..<5 { + keychain.setAccessToken(UUID().uuidString, forUsername: "@test\(index):example.com") + } + XCTAssertEqual(keychain.accessTokens().count, 5, "The keychain should have 5 access tokens.") + + // When deleting all of the access tokens. + keychain.removeAllAccessTokens() + + // Then the keychain should be empty. + XCTAssertTrue(keychain.accessTokens().isEmpty, "The keychain should be empty after deleting the token.") + } + + func testRemovingSingleAccessTokens() { + // Given a keychain with 5 stored access tokens. + for index in 0..<5 { + keychain.setAccessToken(UUID().uuidString, forUsername: "@test\(index):example.com") + } + XCTAssertEqual(keychain.accessTokens().count, 5, "The keychain should have 5 access tokens.") + + // When deleting one of the access tokens. + keychain.removeAccessTokenForUsername("@test2:example.com") + + // Then the other 4 items should remain untouched. + XCTAssertEqual(keychain.accessTokens().count, 4, "The keychain have 4 remaining access tokens.") + XCTAssertNotNil(keychain.accessTokenForUsername("@test0:example.com"), "The access token should not have been deleted.") + XCTAssertNotNil(keychain.accessTokenForUsername("@test1:example.com"), "The access token should not have been deleted.") + XCTAssertNil(keychain.accessTokenForUsername("@test2:example.com"), "The access token should have been deleted.") + XCTAssertNotNil(keychain.accessTokenForUsername("@test3:example.com"), "The access token should not have been deleted.") + XCTAssertNotNil(keychain.accessTokenForUsername("@test4:example.com"), "The access token should not have been deleted.") + } +}