mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Improve user session overview tests
* Add sessions overview UI tests * Expose static methods from the UserSession name and lastActivity formatters; cleaned up the UserSessionsOverview a bit * Add UserSessionsOverviewViewModel unit tests * Add UserSessionsOverviewService unit tests
This commit is contained in:
parent
f4da3e2448
commit
52c4ff65db
21 changed files with 650 additions and 143 deletions
|
@ -24,6 +24,9 @@ schemes:
|
|||
disableMainThreadChecker: true
|
||||
targets:
|
||||
- RiotTests
|
||||
gatherCoverageData: true
|
||||
coverageTargets:
|
||||
- Riot
|
||||
|
||||
targets:
|
||||
Riot:
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
import Foundation
|
||||
|
||||
/// Enables to build last activity date string
|
||||
class UserSessionLastActivityFormatter {
|
||||
enum UserSessionLastActivityFormatter {
|
||||
private static var lastActivityDateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.locale = Locale.current
|
||||
|
@ -28,9 +28,9 @@ class UserSessionLastActivityFormatter {
|
|||
}()
|
||||
|
||||
/// Session last activity string
|
||||
func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String {
|
||||
static func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String {
|
||||
let date = Date(timeIntervalSince1970: lastActivityTimestamp)
|
||||
|
||||
return UserSessionLastActivityFormatter.lastActivityDateFormatter.string(from: date)
|
||||
return Self.lastActivityDateFormatter.string(from: date)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
import Foundation
|
||||
|
||||
/// Enables to build user session name
|
||||
class UserSessionNameFormatter {
|
||||
enum UserSessionNameFormatter {
|
||||
/// Session name with client name and session display name
|
||||
func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String {
|
||||
static func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String {
|
||||
let sessionName: String
|
||||
|
||||
let clientName = deviceType.name
|
||||
|
|
|
@ -109,12 +109,14 @@ struct UserSessionCardView: View {
|
|||
.buttonStyle(PrimaryActionButtonStyle())
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, 3)
|
||||
.accessibilityIdentifier("userSessionCardVerifyButton")
|
||||
}
|
||||
|
||||
if viewData.isCurrentSessionDisplayMode {
|
||||
Text(VectorL10n.userSessionViewDetails)
|
||||
.font(theme.fonts.body)
|
||||
.foregroundColor(theme.colors.accent)
|
||||
.accessibilityIdentifier("userSessionCardViewDetails")
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
|
|
|
@ -18,9 +18,6 @@ import Foundation
|
|||
|
||||
/// View data for UserSessionCardView
|
||||
struct UserSessionCardViewData {
|
||||
private static let userSessionNameFormatter = UserSessionNameFormatter()
|
||||
private static let lastActivityDateFormatter = UserSessionLastActivityFormatter()
|
||||
|
||||
var id: String {
|
||||
sessionId
|
||||
}
|
||||
|
@ -48,13 +45,13 @@ struct UserSessionCardViewData {
|
|||
lastSeenIP: String?,
|
||||
isCurrentSessionDisplayMode: Bool = false) {
|
||||
self.sessionId = sessionId
|
||||
sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
|
||||
sessionName = UserSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
|
||||
self.isVerified = isVerified
|
||||
|
||||
var lastActivityDateString: String?
|
||||
|
||||
if let lastActivityTimestamp = lastActivityTimestamp {
|
||||
lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityTimestamp)
|
||||
lastActivityDateString = UserSessionLastActivityFormatter.lastActivityDateString(from: lastActivityTimestamp)
|
||||
}
|
||||
|
||||
self.lastActivityDateString = lastActivityDateString
|
||||
|
|
|
@ -37,7 +37,8 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
|
|||
init(parameters: UserSessionsOverviewCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
service = UserSessionsOverviewService(mxSession: parameters.session)
|
||||
let dataProvider = UserSessionsDataProvider(session: parameters.session)
|
||||
service = UserSessionsOverviewService(dataProvider: dataProvider)
|
||||
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
|
||||
hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context))
|
||||
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingViewController)
|
||||
|
|
|
@ -20,31 +20,39 @@ import SwiftUI
|
|||
/// Using an enum for the screen allows you define the different state cases with
|
||||
/// the relevant associated data for each case.
|
||||
enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable {
|
||||
case verifiedSession
|
||||
case currentSessionUnverified
|
||||
case currentSessionVerified
|
||||
case onlyUnverifiedSessions
|
||||
case onlyInactiveSessions
|
||||
case noOtherSessions
|
||||
|
||||
/// The associated screen
|
||||
var screenType: Any.Type {
|
||||
UserSessionsOverview.self
|
||||
}
|
||||
|
||||
/// A list of screen state definitions
|
||||
static var allCases: [MockUserSessionsOverviewScreenState] {
|
||||
// Each of the presence statuses
|
||||
[.verifiedSession]
|
||||
}
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
var screenView: ([Any], AnyView) {
|
||||
let service = MockUserSessionsOverviewService()
|
||||
var service: UserSessionsOverviewServiceProtocol?
|
||||
switch self {
|
||||
case .verifiedSession:
|
||||
break
|
||||
case .currentSessionUnverified:
|
||||
service = MockUserSessionsOverviewService(mode: .currentSessionUnverified)
|
||||
case .currentSessionVerified:
|
||||
service = MockUserSessionsOverviewService(mode: .currentSessionVerified)
|
||||
case .onlyUnverifiedSessions:
|
||||
service = MockUserSessionsOverviewService(mode: .onlyUnverifiedSessions)
|
||||
case .onlyInactiveSessions:
|
||||
service = MockUserSessionsOverviewService(mode: .onlyInactiveSessions)
|
||||
case .noOtherSessions:
|
||||
service = MockUserSessionsOverviewService(mode: .noOtherSessions)
|
||||
}
|
||||
|
||||
guard let service = service else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
|
||||
|
||||
// can simulate service and viewModel actions here if needs be.
|
||||
|
||||
return (
|
||||
[service, viewModel],
|
||||
AnyView(UserSessionsOverview(viewModel: viewModel.context)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// 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 MatrixSDK
|
||||
|
||||
class UserSessionsDataProvider: UserSessionsDataProviderProtocol {
|
||||
private let session: MXSession
|
||||
|
||||
init(session: MXSession) {
|
||||
self.session = session
|
||||
}
|
||||
|
||||
var myDeviceId: String {
|
||||
session.myDeviceId
|
||||
}
|
||||
|
||||
var myUserId: String? {
|
||||
session.myUserId
|
||||
}
|
||||
|
||||
var activeAccounts: [MXKAccount] {
|
||||
MXKAccountManager.shared().activeAccounts
|
||||
}
|
||||
|
||||
func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void) {
|
||||
session.matrixRestClient.devices(completion: completion)
|
||||
}
|
||||
|
||||
func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo {
|
||||
session.crypto.device(withDeviceId: deviceId, ofUser: userId)
|
||||
}
|
||||
|
||||
func accountData(for eventType: String) -> [AnyHashable : Any]? {
|
||||
session.accountData.accountData(forEventType: eventType)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// 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 MatrixSDK
|
||||
|
||||
protocol UserSessionsDataProviderProtocol {
|
||||
var myDeviceId: String { get }
|
||||
|
||||
var myUserId: String? { get }
|
||||
|
||||
var activeAccounts: [MXKAccount] { get }
|
||||
|
||||
func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void)
|
||||
|
||||
func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo
|
||||
|
||||
func accountData(for eventType: String) -> [AnyHashable: Any]?
|
||||
}
|
|
@ -21,12 +21,12 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
/// Delay after which session is considered inactive, 90 days
|
||||
private static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400
|
||||
|
||||
private let mxSession: MXSession
|
||||
private let dataProvider: UserSessionsDataProviderProtocol
|
||||
|
||||
private(set) var overviewData: UserSessionsOverviewData
|
||||
|
||||
init(mxSession: MXSession) {
|
||||
self.mxSession = mxSession
|
||||
init(dataProvider: UserSessionsDataProviderProtocol) {
|
||||
self.dataProvider = dataProvider
|
||||
|
||||
overviewData = UserSessionsOverviewData(currentSession: nil,
|
||||
unverifiedSessions: [],
|
||||
|
@ -39,7 +39,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
// MARK: - Public
|
||||
|
||||
func updateOverviewData(completion: @escaping (Result<UserSessionsOverviewData, Error>) -> Void) {
|
||||
mxSession.matrixRestClient.devices { response in
|
||||
dataProvider.devices { response in
|
||||
switch response {
|
||||
case .success(let devices):
|
||||
self.overviewData = self.sessionsOverviewData(from: devices)
|
||||
|
@ -61,16 +61,18 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
// MARK: - Private
|
||||
|
||||
private func setupInitialOverviewData() {
|
||||
let currentSessionInfo = currentSessionInfo()
|
||||
guard let currentSessionInfo = getCurrentSessionInfo() else {
|
||||
return
|
||||
}
|
||||
|
||||
overviewData = UserSessionsOverviewData(currentSession: currentSessionInfo,
|
||||
unverifiedSessions: [],
|
||||
inactiveSessions: [],
|
||||
unverifiedSessions: currentSessionInfo.isVerified ? [] : [currentSessionInfo],
|
||||
inactiveSessions: currentSessionInfo.isActive ? [] : [currentSessionInfo],
|
||||
otherSessions: [])
|
||||
}
|
||||
|
||||
private func currentSessionInfo() -> UserSessionInfo? {
|
||||
guard let mainAccount = MXKAccountManager.shared().activeAccounts.first,
|
||||
private func getCurrentSessionInfo() -> UserSessionInfo? {
|
||||
guard let mainAccount = dataProvider.activeAccounts.first,
|
||||
let device = mainAccount.device else {
|
||||
return nil
|
||||
}
|
||||
|
@ -80,7 +82,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
private func sessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData {
|
||||
let allSessions = devices
|
||||
.sorted { $0.lastSeenTs > $1.lastSeenTs }
|
||||
.map { sessionInfo(from: $0, isCurrentSession: $0.deviceId == mxSession.myDeviceId) }
|
||||
.map { sessionInfo(from: $0, isCurrentSession: $0.deviceId == dataProvider.myDeviceId) }
|
||||
|
||||
return UserSessionsOverviewData(currentSession: allSessions.filter(\.isCurrent).first,
|
||||
unverifiedSessions: allSessions.filter { !$0.isVerified },
|
||||
|
@ -92,7 +94,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
let isSessionVerified = deviceInfo(for: device.deviceId)?.trustLevel.isVerified ?? false
|
||||
|
||||
let eventType = kMXAccountDataTypeClientInformation + "." + device.deviceId
|
||||
let appData = mxSession.accountData.accountData(forEventType: eventType)
|
||||
let appData = dataProvider.accountData(for: eventType)
|
||||
var userAgent: UserAgent?
|
||||
var isSessionActive = true
|
||||
|
||||
|
@ -114,11 +116,11 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
}
|
||||
|
||||
private func deviceInfo(for deviceId: String) -> MXDeviceInfo? {
|
||||
guard let userId = mxSession.myUserId else {
|
||||
guard let userId = dataProvider.myUserId else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId)
|
||||
return dataProvider.device(withDeviceId: deviceId, ofUser: userId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,28 +17,70 @@
|
|||
import Foundation
|
||||
|
||||
class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
||||
let overviewData: UserSessionsOverviewData
|
||||
enum Mode {
|
||||
case currentSessionUnverified
|
||||
case currentSessionVerified
|
||||
case onlyUnverifiedSessions
|
||||
case onlyInactiveSessions
|
||||
case noOtherSessions
|
||||
}
|
||||
|
||||
private let mode: Mode
|
||||
|
||||
var overviewData: UserSessionsOverviewData
|
||||
|
||||
init(mode: Mode = .currentSessionUnverified) {
|
||||
self.mode = mode
|
||||
|
||||
overviewData = UserSessionsOverviewData(currentSession: nil,
|
||||
unverifiedSessions: [],
|
||||
inactiveSessions: [],
|
||||
otherSessions: [])
|
||||
}
|
||||
|
||||
func updateOverviewData(completion: @escaping (Result<UserSessionsOverviewData, Error>) -> Void) {
|
||||
let unverifiedSessions = buildSessions(verified: false, active: true)
|
||||
let inactiveSessions = buildSessions(verified: true, active: false)
|
||||
|
||||
switch mode {
|
||||
case .noOtherSessions:
|
||||
overviewData = UserSessionsOverviewData(currentSession: currentSession,
|
||||
unverifiedSessions: [],
|
||||
inactiveSessions: [],
|
||||
otherSessions: [])
|
||||
case .onlyUnverifiedSessions:
|
||||
overviewData = UserSessionsOverviewData(currentSession: currentSession,
|
||||
unverifiedSessions: unverifiedSessions + [currentSession],
|
||||
inactiveSessions: [],
|
||||
otherSessions: unverifiedSessions)
|
||||
case .onlyInactiveSessions:
|
||||
overviewData = UserSessionsOverviewData(currentSession: currentSession,
|
||||
unverifiedSessions: [],
|
||||
inactiveSessions: inactiveSessions,
|
||||
otherSessions: inactiveSessions)
|
||||
default:
|
||||
let otherSessions = unverifiedSessions + inactiveSessions + buildSessions(verified: true, active: true)
|
||||
|
||||
overviewData = UserSessionsOverviewData(currentSession: currentSession,
|
||||
unverifiedSessions: unverifiedSessions,
|
||||
inactiveSessions: inactiveSessions,
|
||||
otherSessions: otherSessions)
|
||||
}
|
||||
|
||||
completion(.success(overviewData))
|
||||
}
|
||||
|
||||
func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo? {
|
||||
nil
|
||||
overviewData.otherSessions.first { $0.id == sessionId }
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
init() {
|
||||
overviewData = UserSessionsOverviewData(currentSession: Self.allSessions.filter(\.isCurrent).first,
|
||||
unverifiedSessions: Self.allSessions.filter { !$0.isVerified },
|
||||
inactiveSessions: Self.allSessions.filter { !$0.isActive },
|
||||
otherSessions: Self.allSessions.filter { !$0.isCurrent })
|
||||
}
|
||||
|
||||
static let allSessions = [
|
||||
private var currentSession: UserSessionInfo {
|
||||
UserSessionInfo(id: "alice",
|
||||
name: "iOS",
|
||||
deviceType: .mobile,
|
||||
isVerified: false,
|
||||
isVerified: mode == .currentSessionVerified,
|
||||
lastSeenIP: "10.0.0.10",
|
||||
lastSeenTimestamp: nil,
|
||||
applicationName: "Element iOS",
|
||||
|
@ -49,51 +91,54 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
|||
lastSeenIPLocation: nil,
|
||||
deviceName: "My iPhone",
|
||||
isActive: true,
|
||||
isCurrent: true),
|
||||
UserSessionInfo(id: "1",
|
||||
name: "macOS",
|
||||
deviceType: .desktop,
|
||||
isVerified: true,
|
||||
lastSeenIP: "1.0.0.1",
|
||||
lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000,
|
||||
applicationName: "Element MacOS",
|
||||
applicationVersion: "1.0.0",
|
||||
applicationURL: nil,
|
||||
deviceModel: nil,
|
||||
deviceOS: "macOS 12.5.1",
|
||||
lastSeenIPLocation: nil,
|
||||
deviceName: "My Mac",
|
||||
isActive: false,
|
||||
isCurrent: false),
|
||||
UserSessionInfo(id: "2",
|
||||
name: "Firefox on Windows",
|
||||
deviceType: .web,
|
||||
isVerified: true,
|
||||
lastSeenIP: "2.0.0.2",
|
||||
lastSeenTimestamp: Date().timeIntervalSince1970 - 100,
|
||||
applicationName: "Element Web",
|
||||
applicationVersion: "1.0.0",
|
||||
applicationURL: nil,
|
||||
deviceModel: nil,
|
||||
deviceOS: "Windows 10",
|
||||
lastSeenIPLocation: nil,
|
||||
deviceName: "My Windows",
|
||||
isActive: true,
|
||||
isCurrent: false),
|
||||
UserSessionInfo(id: "3",
|
||||
name: "Android",
|
||||
deviceType: .mobile,
|
||||
isVerified: false,
|
||||
lastSeenIP: "3.0.0.3",
|
||||
lastSeenTimestamp: Date().timeIntervalSince1970 - 10,
|
||||
applicationName: "Element Android",
|
||||
applicationVersion: "1.0.0",
|
||||
applicationURL: nil,
|
||||
deviceModel: nil,
|
||||
deviceOS: "Android 4.0",
|
||||
lastSeenIPLocation: nil,
|
||||
deviceName: "My Phone",
|
||||
isActive: true,
|
||||
isCurrent: false)
|
||||
]
|
||||
isCurrent: true)
|
||||
}
|
||||
|
||||
private func buildSessions(verified: Bool, active: Bool) -> [UserSessionInfo] {
|
||||
[UserSessionInfo(id: "1 verified: \(verified) active: \(active)",
|
||||
name: "macOS verified: \(verified) active: \(active)",
|
||||
deviceType: .desktop,
|
||||
isVerified: verified,
|
||||
lastSeenIP: "1.0.0.1",
|
||||
lastSeenTimestamp: Date().timeIntervalSince1970 - 130_000,
|
||||
applicationName: "Element MacOS",
|
||||
applicationVersion: "1.0.0",
|
||||
applicationURL: nil,
|
||||
deviceModel: nil,
|
||||
deviceOS: "macOS 12.5.1",
|
||||
lastSeenIPLocation: nil,
|
||||
deviceName: "My Mac",
|
||||
isActive: active,
|
||||
isCurrent: false),
|
||||
UserSessionInfo(id: "2 verified: \(verified) active: \(active)",
|
||||
name: "Firefox on Windows verified: \(verified) active: \(active)",
|
||||
deviceType: .web,
|
||||
isVerified: verified,
|
||||
lastSeenIP: "2.0.0.2",
|
||||
lastSeenTimestamp: Date().timeIntervalSince1970 - 100,
|
||||
applicationName: "Element Web",
|
||||
applicationVersion: "1.0.0",
|
||||
applicationURL: nil,
|
||||
deviceModel: nil,
|
||||
deviceOS: "Windows 10",
|
||||
lastSeenIPLocation: nil,
|
||||
deviceName: "My Windows",
|
||||
isActive: active,
|
||||
isCurrent: false),
|
||||
UserSessionInfo(id: "3 verified: \(verified) active: \(active)",
|
||||
name: "Android verified: \(verified) active: \(active)",
|
||||
deviceType: .mobile,
|
||||
isVerified: verified,
|
||||
lastSeenIP: "3.0.0.3",
|
||||
lastSeenTimestamp: Date().timeIntervalSince1970 - 10,
|
||||
applicationName: "Element Android",
|
||||
applicationVersion: "1.0.0",
|
||||
applicationURL: nil,
|
||||
deviceModel: nil,
|
||||
deviceOS: "Android 4.0",
|
||||
lastSeenIPLocation: nil,
|
||||
deviceName: "My Phone",
|
||||
isActive: active,
|
||||
isCurrent: false)]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,5 +18,38 @@ import RiotSwiftUI
|
|||
import XCTest
|
||||
|
||||
class UserSessionsOverviewUITests: MockScreenTestCase {
|
||||
// TODO:
|
||||
func testCurrentSessionUnverified() {
|
||||
app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.currentSessionUnverified.title)
|
||||
|
||||
XCTAssertTrue(app.buttons["userSessionCardVerifyButton"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists)
|
||||
}
|
||||
|
||||
func testCurrentSessionVerified() {
|
||||
app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.currentSessionVerified.title)
|
||||
|
||||
XCTAssertFalse(app.buttons["userSessionCardVerifyButton"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists)
|
||||
}
|
||||
|
||||
func testOnlyUnverifiedSessions() {
|
||||
app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.onlyUnverifiedSessions.title)
|
||||
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists)
|
||||
}
|
||||
|
||||
func testOnlyInactiveSessions() {
|
||||
app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.onlyInactiveSessions.title)
|
||||
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists)
|
||||
}
|
||||
|
||||
func testNoOtherSessions() {
|
||||
app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.noOtherSessions.title)
|
||||
|
||||
XCTAssertFalse(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists)
|
||||
XCTAssertFalse(app.staticTexts["userSessionsOverviewOtherSection"].exists)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,71 @@ import XCTest
|
|||
@testable import RiotSwiftUI
|
||||
|
||||
class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
var service: MockUserSessionsOverviewService!
|
||||
var viewModel: UserSessionsOverviewViewModelProtocol!
|
||||
var context: UserSessionsOverviewViewModelType.Context!
|
||||
func testInitialStateEmpty() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService())
|
||||
|
||||
XCTAssertNil(viewModel.state.currentSessionViewData)
|
||||
XCTAssertTrue(viewModel.state.unverifiedSessionsViewData.isEmpty)
|
||||
XCTAssertTrue(viewModel.state.inactiveSessionsViewData.isEmpty)
|
||||
XCTAssertTrue(viewModel.state.otherSessionsViewData.isEmpty)
|
||||
}
|
||||
|
||||
override func setUpWithError() throws {
|
||||
service = MockUserSessionsOverviewService()
|
||||
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
|
||||
context = viewModel.context
|
||||
func testLoadOnDidAppear() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService())
|
||||
viewModel.process(viewAction: .viewAppeared)
|
||||
|
||||
XCTAssertNotNil(viewModel.state.currentSessionViewData)
|
||||
XCTAssertFalse(viewModel.state.unverifiedSessionsViewData.isEmpty)
|
||||
XCTAssertFalse(viewModel.state.inactiveSessionsViewData.isEmpty)
|
||||
XCTAssertFalse(viewModel.state.otherSessionsViewData.isEmpty)
|
||||
}
|
||||
|
||||
func testSimpleActionProcessing() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService())
|
||||
|
||||
var result: UserSessionsOverviewViewModelResult?
|
||||
viewModel.completion = { action in
|
||||
result = action
|
||||
}
|
||||
|
||||
viewModel.process(viewAction: .verifyCurrentSession)
|
||||
XCTAssertEqual(result, .verifyCurrentSession)
|
||||
|
||||
viewModel.process(viewAction: .viewAllUnverifiedSessions)
|
||||
XCTAssertEqual(result, .showAllUnverifiedSessions)
|
||||
|
||||
viewModel.process(viewAction: .viewAllInactiveSessions)
|
||||
XCTAssertEqual(result, .showAllInactiveSessions)
|
||||
|
||||
viewModel.process(viewAction: .viewAllOtherSessions)
|
||||
XCTAssertEqual(result, .showAllOtherSessions)
|
||||
}
|
||||
|
||||
func testShowSessionDetails() {
|
||||
let service = MockUserSessionsOverviewService()
|
||||
service.updateOverviewData { _ in }
|
||||
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
|
||||
|
||||
var result: UserSessionsOverviewViewModelResult?
|
||||
viewModel.completion = { action in
|
||||
result = action
|
||||
}
|
||||
|
||||
guard let currentSession = service.overviewData.currentSession else {
|
||||
XCTFail("The current session should be valid at this point")
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.process(viewAction: .viewCurrentSessionDetails)
|
||||
XCTAssertEqual(result, .showCurrentSessionOverview(session: currentSession))
|
||||
|
||||
guard let randomSession = service.overviewData.otherSessions.randomElement() else {
|
||||
XCTFail("There should be other sessions")
|
||||
return
|
||||
}
|
||||
|
||||
viewModel.process(viewAction: .tapUserSession(randomSession.id))
|
||||
XCTAssertEqual(result, .showUserSessionOverview(session: randomSession))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ enum UserSessionsOverviewCoordinatorResult {
|
|||
|
||||
// MARK: View model
|
||||
|
||||
enum UserSessionsOverviewViewModelResult {
|
||||
enum UserSessionsOverviewViewModelResult: Equatable {
|
||||
case showAllUnverifiedSessions
|
||||
case showAllInactiveSessions
|
||||
case verifyCurrentSession
|
||||
|
|
|
@ -18,9 +18,6 @@ import Foundation
|
|||
|
||||
/// View data for UserSessionListItem
|
||||
struct UserSessionListItemViewData: Identifiable {
|
||||
private static let userSessionNameFormatter = UserSessionNameFormatter()
|
||||
private static let lastActivityDateFormatter = UserSessionLastActivityFormatter()
|
||||
|
||||
var id: String {
|
||||
sessionId
|
||||
}
|
||||
|
@ -39,7 +36,7 @@ struct UserSessionListItemViewData: Identifiable {
|
|||
isVerified: Bool,
|
||||
lastActivityDate: TimeInterval?) {
|
||||
self.sessionId = sessionId
|
||||
sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
|
||||
sessionName = UserSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
|
||||
sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate)
|
||||
deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified)
|
||||
}
|
||||
|
@ -54,7 +51,7 @@ struct UserSessionListItemViewData: Identifiable {
|
|||
var lastActivityDateString: String?
|
||||
|
||||
if let lastActivityDate = lastActivityDate {
|
||||
lastActivityDateString = Self.lastActivityDateFormatter.lastActivityDateString(from: lastActivityDate)
|
||||
lastActivityDateString = UserSessionLastActivityFormatter.lastActivityDateString(from: lastActivityDate)
|
||||
}
|
||||
|
||||
if let lastActivityDateString = lastActivityDateString, lastActivityDateString.isEmpty == false {
|
||||
|
|
|
@ -23,11 +23,13 @@ struct UserSessionsOverview: View {
|
|||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
securityRecommendationsSection
|
||||
if hasSecurityRecommendations {
|
||||
securityRecommendationsSection
|
||||
}
|
||||
|
||||
currentSessionsSection
|
||||
|
||||
if viewModel.viewState.otherSessionsViewData.isEmpty == false {
|
||||
if !viewModel.viewState.otherSessionsViewData.isEmpty {
|
||||
otherSessionsSection
|
||||
}
|
||||
}
|
||||
|
@ -40,41 +42,39 @@ struct UserSessionsOverview: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var securityRecommendationsSection: some View {
|
||||
if hasSecurityRecommendations {
|
||||
SwiftUI.Section {
|
||||
if !viewModel.viewState.unverifiedSessionsViewData.isEmpty {
|
||||
SecurityRecommendationCard(style: .unverified,
|
||||
sessionCount: viewModel.viewState.unverifiedSessionsViewData.count) {
|
||||
viewModel.send(viewAction: .viewAllUnverifiedSessions)
|
||||
}
|
||||
SwiftUI.Section {
|
||||
if !viewModel.viewState.unverifiedSessionsViewData.isEmpty {
|
||||
SecurityRecommendationCard(style: .unverified,
|
||||
sessionCount: viewModel.viewState.unverifiedSessionsViewData.count) {
|
||||
viewModel.send(viewAction: .viewAllUnverifiedSessions)
|
||||
}
|
||||
|
||||
if !viewModel.viewState.inactiveSessionsViewData.isEmpty {
|
||||
SecurityRecommendationCard(style: .inactive,
|
||||
sessionCount: viewModel.viewState.inactiveSessionsViewData.count) {
|
||||
viewModel.send(viewAction: .viewAllInactiveSessions)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
VStack(alignment: .leading) {
|
||||
Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionTitle)
|
||||
.textCase(.uppercase)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.bottom, 8.0)
|
||||
|
||||
Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionInfo)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.bottom, 12.0)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 24)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
if !viewModel.viewState.inactiveSessionsViewData.isEmpty {
|
||||
SecurityRecommendationCard(style: .inactive,
|
||||
sessionCount: viewModel.viewState.inactiveSessionsViewData.count) {
|
||||
viewModel.send(viewAction: .viewAllInactiveSessions)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
VStack(alignment: .leading) {
|
||||
Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionTitle)
|
||||
.textCase(.uppercase)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.bottom, 8.0)
|
||||
|
||||
Text(VectorL10n.userSessionsOverviewSecurityRecommendationsSectionInfo)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.bottom, 12.0)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 24)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.accessibilityIdentifier("userSessionsOverviewSecurityRecommendationsSection")
|
||||
}
|
||||
|
||||
var hasSecurityRecommendations: Bool {
|
||||
|
@ -102,10 +102,9 @@ struct UserSessionsOverview: View {
|
|||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var otherSessionsSection: some View {
|
||||
SwiftUI.Section {
|
||||
// Device list
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(viewModel.viewState.otherSessionsViewData) { viewData in
|
||||
UserSessionListItem(viewData: viewData, onBackgroundTap: { sessionId in
|
||||
|
@ -131,6 +130,7 @@ struct UserSessionsOverview: View {
|
|||
.padding(.horizontal, 16.0)
|
||||
.padding(.top, 24.0)
|
||||
}
|
||||
.accessibilityIdentifier("userSessionsOverviewOtherSection")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ schemes:
|
|||
disableMainThreadChecker: true
|
||||
targets:
|
||||
- RiotSwiftUnitTests
|
||||
gatherCoverageData: true
|
||||
coverageTargets:
|
||||
- RiotSwiftUI
|
||||
|
||||
targets:
|
||||
RiotSwiftUI:
|
||||
|
|
|
@ -24,6 +24,9 @@ schemes:
|
|||
disableMainThreadChecker: true
|
||||
targets:
|
||||
- RiotSwiftUITests
|
||||
gatherCoverageData: true
|
||||
coverageTargets:
|
||||
- RiotSwiftUI
|
||||
|
||||
targets:
|
||||
RiotSwiftUITests:
|
||||
|
|
|
@ -24,6 +24,9 @@ schemes:
|
|||
disableMainThreadChecker: true
|
||||
targets:
|
||||
- RiotSwiftUnitTests
|
||||
gatherCoverageData: true
|
||||
coverageTargets:
|
||||
- RiotSwiftUI
|
||||
|
||||
targets:
|
||||
RiotSwiftUnitTests:
|
||||
|
|
267
RiotTests/UserSessionsOverviewServiceTests.swift
Normal file
267
RiotTests/UserSessionsOverviewServiceTests.swift
Normal file
|
@ -0,0 +1,267 @@
|
|||
//
|
||||
// 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 XCTest
|
||||
@testable import Element
|
||||
@testable import MatrixSDK
|
||||
|
||||
private let currentDeviceId = "deviceId"
|
||||
private let unverifiedDeviceId = "unverifiedDeviceId"
|
||||
private let currentUserId = "userId"
|
||||
|
||||
class UserSessionsOverviewServiceTests: XCTestCase {
|
||||
func testInitialSessionUnverified() {
|
||||
let dataProvider = MockUserSessionsDataProvider(mode: .currentSessionUnverified)
|
||||
let service = UserSessionsOverviewService(dataProvider: dataProvider)
|
||||
|
||||
XCTAssertNotNil(service.overviewData.currentSession)
|
||||
XCTAssertFalse(service.overviewData.currentSession?.isVerified ?? false)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false)
|
||||
XCTAssertFalse(service.overviewData.unverifiedSessions.isEmpty)
|
||||
XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty)
|
||||
|
||||
XCTAssertEqual(service.sessionForIdentifier(currentDeviceId), service.overviewData.currentSession)
|
||||
}
|
||||
|
||||
func testInitialSessionVerified() {
|
||||
let dataProvider = MockUserSessionsDataProvider(mode: .currentSessionVerified)
|
||||
let service = UserSessionsOverviewService(dataProvider: dataProvider)
|
||||
|
||||
XCTAssertNotNil(service.overviewData.currentSession)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false)
|
||||
XCTAssertTrue(service.overviewData.unverifiedSessions.isEmpty)
|
||||
XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty)
|
||||
}
|
||||
|
||||
func testWithAllSessionsVerified() {
|
||||
let service = setupServiceWithMode(.allOtherSessionsValid)
|
||||
|
||||
XCTAssertNotNil(service.overviewData.currentSession)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false)
|
||||
|
||||
XCTAssertTrue(service.overviewData.unverifiedSessions.isEmpty)
|
||||
XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty)
|
||||
XCTAssertFalse(service.overviewData.otherSessions.isEmpty)
|
||||
}
|
||||
|
||||
func testWithSomeUnverifiedSessions() {
|
||||
let service = setupServiceWithMode(.someUnverifiedSessions)
|
||||
|
||||
XCTAssertNotNil(service.overviewData.currentSession)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false)
|
||||
|
||||
XCTAssertFalse(service.overviewData.unverifiedSessions.isEmpty)
|
||||
XCTAssertTrue(service.overviewData.inactiveSessions.isEmpty)
|
||||
XCTAssertFalse(service.overviewData.otherSessions.isEmpty)
|
||||
}
|
||||
|
||||
func testWithSomeInactiveSessions() {
|
||||
let service = setupServiceWithMode(.someInactiveSessions)
|
||||
|
||||
XCTAssertNotNil(service.overviewData.currentSession)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false)
|
||||
|
||||
XCTAssertTrue(service.overviewData.unverifiedSessions.isEmpty)
|
||||
XCTAssertFalse(service.overviewData.inactiveSessions.isEmpty)
|
||||
XCTAssertFalse(service.overviewData.otherSessions.isEmpty)
|
||||
}
|
||||
|
||||
func testWithSomeUnverifiedAndInactiveSessions() {
|
||||
let service = setupServiceWithMode(.someUnverifiedAndInactiveSessions)
|
||||
|
||||
XCTAssertNotNil(service.overviewData.currentSession)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isVerified ?? false)
|
||||
XCTAssertTrue(service.overviewData.currentSession?.isActive ?? false)
|
||||
|
||||
XCTAssertFalse(service.overviewData.unverifiedSessions.isEmpty)
|
||||
XCTAssertFalse(service.overviewData.inactiveSessions.isEmpty)
|
||||
XCTAssertFalse(service.overviewData.otherSessions.isEmpty)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupServiceWithMode(_ mode: MockUserSessionsDataProvider.Mode) -> UserSessionsOverviewServiceProtocol {
|
||||
let dataProvider = MockUserSessionsDataProvider(mode: mode)
|
||||
let service = UserSessionsOverviewService(dataProvider: dataProvider)
|
||||
|
||||
let expectation = expectation(description: "Wait for service update")
|
||||
service.updateOverviewData { _ in
|
||||
expectation.fulfill()
|
||||
}
|
||||
|
||||
waitForExpectations(timeout: 1.0)
|
||||
|
||||
return service
|
||||
}
|
||||
}
|
||||
|
||||
private class MockUserSessionsDataProvider: UserSessionsDataProviderProtocol {
|
||||
enum Mode {
|
||||
case currentSessionUnverified
|
||||
case currentSessionVerified
|
||||
case allOtherSessionsValid
|
||||
case someUnverifiedSessions
|
||||
case someInactiveSessions
|
||||
case someUnverifiedAndInactiveSessions
|
||||
}
|
||||
|
||||
private let mode: Mode
|
||||
|
||||
var myDeviceId = currentDeviceId
|
||||
|
||||
var myUserId: String? = currentUserId
|
||||
|
||||
var activeAccounts: [MXKAccount] {
|
||||
[MockAccount()]
|
||||
}
|
||||
|
||||
init(mode: Mode) {
|
||||
self.mode = mode
|
||||
}
|
||||
|
||||
func devices(completion: @escaping (MXResponse<[MXDevice]>) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
switch self.mode {
|
||||
case .currentSessionUnverified:
|
||||
return
|
||||
case .currentSessionVerified:
|
||||
return
|
||||
case .allOtherSessionsValid:
|
||||
completion(.success(self.verifiedSessions))
|
||||
case .someUnverifiedSessions:
|
||||
completion(.success(self.verifiedSessions + self.unverifiedSessions))
|
||||
case .someInactiveSessions:
|
||||
completion(.success(self.verifiedSessions + self.inactiveSessions))
|
||||
case .someUnverifiedAndInactiveSessions:
|
||||
completion(.success(self.verifiedSessions + self.unverifiedSessions + self.inactiveSessions))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func device(withDeviceId deviceId: String, ofUser userId: String) -> MXDeviceInfo {
|
||||
guard deviceId == currentDeviceId else {
|
||||
return MockDeviceInfo(verified: deviceId != unverifiedDeviceId)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .currentSessionUnverified:
|
||||
return MockDeviceInfo(verified: false)
|
||||
default:
|
||||
return MockDeviceInfo(verified: true)
|
||||
}
|
||||
}
|
||||
|
||||
func accountData(for eventType: String) -> [AnyHashable : Any]? {
|
||||
[:]
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
var verifiedSessions: [MXDevice] {
|
||||
[MockDevice(identifier: currentDeviceId, sessionActive: true),
|
||||
MockDevice(identifier: UUID().uuidString, sessionActive: true)]
|
||||
}
|
||||
|
||||
var unverifiedSessions: [MXDevice] {
|
||||
[MockDevice(identifier: unverifiedDeviceId, sessionActive: true)]
|
||||
}
|
||||
|
||||
var inactiveSessions: [MXDevice] {
|
||||
[MockDevice(identifier: UUID().uuidString, sessionActive: false)]
|
||||
}
|
||||
}
|
||||
|
||||
private class MockAccount: MXKAccount {
|
||||
override var device: MXDevice? {
|
||||
MockDevice(identifier: currentDeviceId, sessionActive: true)
|
||||
}
|
||||
}
|
||||
|
||||
private class MockDevice: MXDevice {
|
||||
private let identifier: String
|
||||
private let sessionActive: Bool
|
||||
|
||||
init(identifier: String, sessionActive: Bool) {
|
||||
self.identifier = identifier
|
||||
self.sessionActive = sessionActive
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override var deviceId: String {
|
||||
get {
|
||||
identifier
|
||||
}
|
||||
set {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override var lastSeenTs: UInt64 {
|
||||
get {
|
||||
if sessionActive {
|
||||
return UInt64(Date().timeIntervalSince1970 * 1000)
|
||||
} else {
|
||||
let ninetyDays: Double = 90 * 86400
|
||||
return UInt64((Date().timeIntervalSince1970 - ninetyDays) * 1000)
|
||||
}
|
||||
}
|
||||
set {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MockDeviceInfo: MXDeviceInfo {
|
||||
private let verified: Bool
|
||||
|
||||
init(verified: Bool) {
|
||||
self.verified = verified
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override var trustLevel: MXDeviceTrustLevel! {
|
||||
MockDeviceTrustLevel(verified: verified)
|
||||
}
|
||||
}
|
||||
|
||||
private class MockDeviceTrustLevel: MXDeviceTrustLevel {
|
||||
private let verified: Bool
|
||||
|
||||
init(verified: Bool) {
|
||||
self.verified = verified
|
||||
super.init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
override var isVerified: Bool {
|
||||
verified
|
||||
}
|
||||
}
|
|
@ -24,6 +24,9 @@ schemes:
|
|||
disableMainThreadChecker: true
|
||||
targets:
|
||||
- RiotTests
|
||||
gatherCoverageData: true
|
||||
coverageTargets:
|
||||
- Riot
|
||||
|
||||
targets:
|
||||
RiotTests:
|
||||
|
|
Loading…
Reference in a new issue