diff --git a/RiotTests/UserAgentParserTests.swift b/RiotSwiftUI/Modules/UserSessions/Common/Test/Unit/UserAgentParserTests.swift similarity index 99% rename from RiotTests/UserAgentParserTests.swift rename to RiotSwiftUI/Modules/UserSessions/Common/Test/Unit/UserAgentParserTests.swift index e7704b3de..7d4766603 100644 --- a/RiotTests/UserAgentParserTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/Common/Test/Unit/UserAgentParserTests.swift @@ -14,11 +14,10 @@ // limitations under the License. // +@testable import RiotSwiftUI import XCTest -@testable import Element class UserAgentParserTests: XCTestCase { - func testAndroidUserAgents() throws { let uaStrings = [ // New User Agent Implementation @@ -182,7 +181,7 @@ class UserAgentParserTests: XCTestCase { "Element/1.9.9; iOS", "Element/1.9.7 Android", "some random string", - "Element/1.9.9; iOS ", + "Element/1.9.9; iOS " ] let userAgents = uaStrings.map { UserAgentParser.parse($0) } @@ -200,5 +199,4 @@ class UserAgentParserTests: XCTestCase { XCTAssertEqual(userAgents, expected) } - } diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift index 3ea87bed1..8f9dab072 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Coordinator/UserOtherSessionsCoordinator.swift @@ -40,7 +40,7 @@ final class UserOtherSessionsCoordinator: Coordinator, Presentable { let viewModel = UserOtherSessionsViewModel(sessionInfos: parameters.sessionInfos, filter: parameters.filter, title: parameters.title, - settingService: RiotSettings.shared) + settingsService: RiotSettings.shared) let view = UserOtherSessions(viewModel: viewModel.context) userOtherSessionsViewModel = viewModel userOtherSessionsHostingController = VectorHostingController(rootView: view) diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift index a4b3e38ce..e81bb7f05 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/MockUserOtherSessionsScreenState.swift @@ -25,6 +25,7 @@ enum MockUserOtherSessionsScreenState: MockScreenState, CaseIterable { // mock that screen. case all + case none case inactiveSessions case unverifiedSessions case verifiedSessions @@ -37,7 +38,7 @@ enum MockUserOtherSessionsScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserOtherSessionsScreenState] { // Each of the presence statuses - [.all, .inactiveSessions, .unverifiedSessions, .verifiedSessions] + [.all, .none, .inactiveSessions, .unverifiedSessions, .verifiedSessions] } /// Generate the view struct for the screen state. @@ -48,22 +49,27 @@ enum MockUserOtherSessionsScreenState: MockScreenState, CaseIterable { viewModel = UserOtherSessionsViewModel(sessionInfos: allSessions(), filter: .all, title: VectorL10n.userSessionsOverviewOtherSessionsSectionTitle, - settingService: MockUserSessionSettings()) + settingsService: MockUserSessionSettings()) + case .none: + viewModel = UserOtherSessionsViewModel(sessionInfos: [], + filter: .all, + title: VectorL10n.userSessionsOverviewOtherSessionsSectionTitle, + settingsService: MockUserSessionSettings()) case .inactiveSessions: viewModel = UserOtherSessionsViewModel(sessionInfos: inactiveSessions(), filter: .inactive, title: VectorL10n.userOtherSessionSecurityRecommendationTitle, - settingService: MockUserSessionSettings()) + settingsService: MockUserSessionSettings()) case .unverifiedSessions: viewModel = UserOtherSessionsViewModel(sessionInfos: unverifiedSessions(), filter: .unverified, title: VectorL10n.userOtherSessionSecurityRecommendationTitle, - settingService: MockUserSessionSettings()) + settingsService: MockUserSessionSettings()) case .verifiedSessions: viewModel = UserOtherSessionsViewModel(sessionInfos: verifiedSessions(), filter: .verified, title: VectorL10n.userOtherSessionSecurityRecommendationTitle, - settingService: MockUserSessionSettings()) + settingsService: MockUserSessionSettings()) } // can simulate service and viewModel actions here if needs be. diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift index 9a5702be3..71a651659 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift @@ -114,4 +114,12 @@ class UserOtherSessionsUITests: MockScreenTestCase { XCTAssertTrue(button.exists) XCTAssertFalse(buttonLearnMore.exists) } + + func test_whenNoSessionAreShown_theLayoutIsCorrect() { + app.goToScreenWithIdentifier(MockUserOtherSessionsScreenState.none.title) + let button = app.buttons["UserOtherSessions.clearFilter"] + let text = app.staticTexts["UserOtherSessions.noItemsText"] + XCTAssertTrue(button.exists) + XCTAssertTrue(text.exists) + } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift index 0d5aa1c9f..bce987575 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/Unit/UserOtherSessionsViewModelTests.swift @@ -346,7 +346,7 @@ class UserOtherSessionsViewModelTests: XCTestCase { UserOtherSessionsViewModel(sessionInfos: sessionInfos, filter: filter, title: title, - settingService: MockUserSessionSettings()) + settingsService: MockUserSessionSettings()) } private func createUserSessionInfo(sessionId: String, diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift index 741593fe3..c82532dd2 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/UserOtherSessionsViewModel.swift @@ -28,12 +28,12 @@ class UserOtherSessionsViewModel: UserOtherSessionsViewModelType, UserOtherSessi init(sessionInfos: [UserSessionInfo], filter: UserOtherSessionsFilter, title: String, - settingService: UserSessionSettingsProtocol) { + settingsService: UserSessionSettingsProtocol) { self.sessionInfos = sessionInfos defaultTitle = title let bindings = UserOtherSessionsBindings(filter: filter, isEditModeEnabled: false) let sessionItems = filter.filterSessionInfos(sessionInfos: sessionInfos, selectedSessions: selectedSessions) - self.settingsService = settingService + self.settingsService = settingsService super.init(initialViewState: UserOtherSessionsViewState(bindings: bindings, title: title, sessionItems: sessionItems, @@ -41,7 +41,7 @@ class UserOtherSessionsViewModel: UserOtherSessionsViewModelType, UserOtherSessi emptyItemsTitle: filter.userOtherSessionsViewEmptyResultsTitle, allItemsSelected: false, enableSignOutButton: false, - showLocationInfo: settingService.showIPAddressesInSessionsManager)) + showLocationInfo: settingsService.showIPAddressesInSessionsManager)) } // MARK: - Public diff --git a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift index 267008b88..e0f71675c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserOtherSessions/View/UserOtherSessions.swift @@ -73,6 +73,7 @@ struct UserOtherSessions: View { .font(theme.fonts.footnote) .foregroundColor(theme.colors.primaryContent) .padding(.bottom, 20) + .accessibilityIdentifier("UserOtherSessions.noItemsText") Button { viewModel.send(viewAction: .clearFilter) } label: { @@ -87,6 +88,7 @@ struct UserOtherSessions: View { } .background(theme.colors.background) } + .accessibilityIdentifier("UserOtherSessions.clearFilter") } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index b3efa0669..e90694fff 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -62,13 +62,13 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { name: "Android", deviceType: .mobile, verificationState: .unverified, - lastSeenIP: "3.0.0.3", + lastSeenIP: nil, lastSeenTimestamp: Date().timeIntervalSince1970 - 10, - applicationName: "Element Android", - applicationVersion: "1.0.0", + applicationName: "", + applicationVersion: "", applicationURL: nil, deviceModel: nil, - deviceOS: "Android 4.0", + deviceOS: nil, lastSeenIPLocation: nil, clientName: "Element", clientVersion: "1.0.0", diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift index ea2133dd8..fe930f4ca 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Test/UI/UserSessionDetailsUITests.swift @@ -18,18 +18,17 @@ import RiotSwiftUI import XCTest class UserSessionDetailsUITests: MockScreenTestCase { - func disabled_broken_xcode14_test_longPressDetailsCell_CopiesValueToClipboard() throws { + func test_screenWithAllTheContent() throws { app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title) - - UIPasteboard.general.string = "" - - let tables = app.tables - let sessionNameIosCell = tables.cells["Session name, iOS"] - sessionNameIosCell.press(forDuration: 0.5) - - app.buttons["Copy"].tap() - - let clipboard = try XCTUnwrap(UIPasteboard.general.string) - XCTAssertEqual(clipboard, "iOS") + + let rows = app.staticTexts.matching(identifier: "UserSessionDetailsItem.title") + XCTAssertEqual(rows.count, 6) + } + + func test_screenWithSessionSectionOnly() throws { + app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.sessionSectionOnly.title) + + let rows = app.staticTexts.matching(identifier: "UserSessionDetailsItem.title") + XCTAssertEqual(rows.count, 3) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift index 80ff02dc5..b9a015560 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/View/UserSessionDetailsItem.swift @@ -34,10 +34,12 @@ struct UserSessionDetailsItem: View { .foregroundColor(theme.colors.secondaryContent) .frame(maxWidth: .infinity, alignment: .leading) .frame(maxHeight: .infinity, alignment: .top) + .accessibility(identifier: "UserSessionDetailsItem.title") Text(viewData.value) .font(theme.fonts.subheadline) .foregroundColor(theme.colors.primaryContent) .multilineTextAlignment(.trailing) + .accessibility(identifier: "UserSessionDetailsItem.value") } .contextMenu { Button { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/UI/UserSessionNameUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/UI/UserSessionNameUITests.swift index 1603c9994..0dd53ce0a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/UI/UserSessionNameUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/UI/UserSessionNameUITests.swift @@ -21,6 +21,7 @@ class UserSessionNameUITests: MockScreenTestCase { func testUserSessionNameInitialState() { app.goToScreenWithIdentifier(MockUserSessionNameScreenState.initialName.title) + assertButtonsExists() let doneButton = app.buttons[VectorL10n.done] XCTAssertTrue(doneButton.exists) XCTAssertFalse(doneButton.isEnabled) @@ -29,6 +30,7 @@ class UserSessionNameUITests: MockScreenTestCase { func testUserSessionNameEmptyState() { app.goToScreenWithIdentifier(MockUserSessionNameScreenState.empty.title) + assertButtonsExists() let doneButton = app.buttons[VectorL10n.done] XCTAssertTrue(doneButton.exists) XCTAssertFalse(doneButton.isEnabled) @@ -37,8 +39,20 @@ class UserSessionNameUITests: MockScreenTestCase { func testUserSessionNameChangedState() { app.goToScreenWithIdentifier(MockUserSessionNameScreenState.changedName.title) + assertButtonsExists() let doneButton = app.buttons[VectorL10n.done] XCTAssertTrue(doneButton.exists) XCTAssertTrue(doneButton.isEnabled) } } + +private extension UserSessionNameUITests { + func assertButtonsExists() { + let buttons = [VectorL10n.done, VectorL10n.cancel, "LearnMore"] + + for buttonId in buttons { + let button = app.buttons[buttonId] + XCTAssertTrue(button.exists) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/Unit/UserSessionNameViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/Unit/UserSessionNameViewModelTests.swift index 5e76f4989..f0798cf3d 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/Unit/UserSessionNameViewModelTests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionName/Test/Unit/UserSessionNameViewModelTests.swift @@ -15,7 +15,6 @@ // import XCTest - @testable import RiotSwiftUI class UserSessionNameViewModelTests: XCTestCase { @@ -48,4 +47,38 @@ class UserSessionNameViewModelTests: XCTestCase { // Then the done button should be enabled. XCTAssertTrue(context.viewState.canUpdateName, "The done button should be enabled when the name has been changed.") } + + func testCancelIsCalled() { + viewModel.completion = { result in + guard case .cancel = result else { + XCTFail() + return + } + } + + viewModel.context.send(viewAction: .cancel) + } + + func testLearnMoreIsCalled() { + viewModel.completion = { result in + guard case .learnMore = result else { + XCTFail() + return + } + } + + viewModel.context.send(viewAction: .learnMore) + } + + func testUpdateNameIsCalled() { + viewModel.completion = { result in + guard case let .updateName(name) = result else { + XCTFail() + return + } + XCTAssertEqual(name, "Element Mobile: iOS") + } + + viewModel.context.send(viewAction: .done) + } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionName/View/UserSessionName.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionName/View/UserSessionName.swift index fa78292ea..8c6af4572 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionName/View/UserSessionName.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionName/View/UserSessionName.swift @@ -42,6 +42,7 @@ struct UserSessionName: View { viewModel.send(viewAction: .learnMore) } .foregroundColor(theme.colors.secondaryContent) + .accessibility(identifier: "LearnMore") } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift index 72862a37d..1340b1eb9 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift @@ -28,19 +28,12 @@ protocol UserSessionsOverviewServiceProtocol { var overviewDataPublisher: CurrentValueSubject { get } func updateOverviewData(completion: @escaping (Result) -> Void) -> Void - func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo? } extension UserSessionsOverviewServiceProtocol { /// The user's current session. var currentSession: UserSessionInfo? { overviewDataPublisher.value.currentSession } - /// Any unverified sessions on the user's account. - var unverifiedSessions: [UserSessionInfo] { overviewDataPublisher.value.unverifiedSessions } - /// Any inactive sessions on the user's account (not seen for a while). - var inactiveSessions: [UserSessionInfo] { overviewDataPublisher.value.inactiveSessions } /// Any sessions that are verified and have been seen recently. var otherSessions: [UserSessionInfo] { overviewDataPublisher.value.otherSessions } - /// Whether it is possible to link a new device via a QR code. - var linkDeviceEnabled: Bool { overviewDataPublisher.value.linkDeviceEnabled } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift index 596647f52..62d841013 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Test/UI/UserSessionsOverviewUITests.swift @@ -23,8 +23,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase { XCTAssertTrue(app.buttons["userSessionCardVerifyButton"].exists) XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists) - - verifyLinkDeviceButtonStatus(true) } func testCurrentSessionVerified() { @@ -33,7 +31,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase { XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists) app.buttons["MoreOptionsMenu"].tap() XCTAssertTrue(app.buttons["Sign out of all other sessions"].exists) - verifyLinkDeviceButtonStatus(true) } func testOnlyUnverifiedSessions() { @@ -41,8 +38,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase { XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists) XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists) - - verifyLinkDeviceButtonStatus(false) } func testOnlyInactiveSessions() { @@ -50,8 +45,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase { XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists) XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists) - - verifyLinkDeviceButtonStatus(false) } func testNoOtherSessions() { @@ -61,18 +54,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase { XCTAssertFalse(app.staticTexts["userSessionsOverviewOtherSection"].exists) app.buttons["MoreOptionsMenu"].tap() XCTAssertFalse(app.buttons["Sign out of all other sessions"].exists) - verifyLinkDeviceButtonStatus(false) - } - - func verifyLinkDeviceButtonStatus(_ enabled: Bool) { -// if enabled { -// let linkDeviceButton = app.buttons["linkDeviceButton"] -// XCTAssertTrue(linkDeviceButton.exists) -// XCTAssertTrue(linkDeviceButton.isEnabled) -// } else { -// let linkDeviceButton = app.buttons["linkDeviceButton"] -// XCTAssertFalse(linkDeviceButton.exists) -// } } func testWhenMoreThan5OtherSessionsThenViewAllButtonVisible() { diff --git a/RiotTests/UserSessionsOverviewServiceTests.swift b/RiotTests/UserSessionsOverviewServiceTests.swift index 49859bc68..86f129268 100644 --- a/RiotTests/UserSessionsOverviewServiceTests.swift +++ b/RiotTests/UserSessionsOverviewServiceTests.swift @@ -118,6 +118,12 @@ class UserSessionsOverviewServiceTests: XCTestCase { } } +private extension UserSessionsOverviewServiceProtocol { + var unverifiedSessions: [UserSessionInfo] { overviewDataPublisher.value.unverifiedSessions } + var inactiveSessions: [UserSessionInfo] { overviewDataPublisher.value.inactiveSessions } + var linkDeviceEnabled: Bool { overviewDataPublisher.value.linkDeviceEnabled } +} + private class MockUserSessionsDataProvider: UserSessionsDataProviderProtocol { enum Mode { case currentSessionUnverified diff --git a/changelog.d/pr-7065.change b/changelog.d/pr-7065.change new file mode 100644 index 000000000..d679f7ea0 --- /dev/null +++ b/changelog.d/pr-7065.change @@ -0,0 +1 @@ +Improve device manager code coverage.