Various UI test fixes (#370)

* Increase integration tests time limits again as they're still ocasionally failing

* Fixed NavigationRootCoordinator name in logs

* Refactor UI tests hierarchy and introduce new userFlowScreen

* Introduce a RoomTimelineControllerFactory so that it can be mocked in the UserFlow UI tests

* Start using a mock timeline controller for the UserSession flows

* Remove the WeakDictionary dependency and replce it with a plain NSMapTable in the BackgroundTaskService

* Allow multiple UITests screenshots per screen

* Prevent the view hierarchy changing when taking screenshots

* Add UserSessionScreen UI tests

* Fix label triaging workflow project identifier as per vector-im/element-ios/pull/7150

* Fix settings screen tests

* Fix roomPlainNoAvatar and roomEncryptedWithAvatar UI tests

* Fix modal server selection screen UI tests

* Fix bug report and login screen UI tests

* Fix text typing missing characters on UI tests

* Fix sliding sync configuration on integration tests

* Stop crashing if not finding a particular room through the MockClientProxy
This commit is contained in:
Stefan Ceriu
2022-12-15 15:22:39 +02:00
committed by GitHub
parent cfd563786d
commit 5b90f37f2e
66 changed files with 454 additions and 583 deletions
@@ -20,7 +20,7 @@ import UIKit
/// /// UIKitBackgroundTaskService is a concrete implementation of BackgroundTaskServiceProtocol using a given `ApplicationProtocol` instance.
class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
private let applicationBlock: () -> ApplicationProtocol?
private var reusableTasks: WeakDictionary<String, UIKitBackgroundTask> = WeakDictionary()
private var reusableTasks = NSMapTable<NSString, UIKitBackgroundTask>(keyOptions: .strongMemory, valueOptions: .weakMemory)
private var application: ApplicationProtocol? {
applicationBlock()
@@ -50,7 +50,7 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
var result: BackgroundTaskProtocol?
if isReusable {
if let oldTask = reusableTasks[name], oldTask.isRunning {
if let oldTask = reusableTasks.object(forKey: name as NSString), oldTask.isRunning {
oldTask.reuse()
result = oldTask
} else {
@@ -59,11 +59,11 @@ class UIKitBackgroundTaskService: BackgroundTaskServiceProtocol {
application: application,
expirationHandler: { [weak self] task in
guard let self else { return }
self.reusableTasks[task.name] = nil
self.reusableTasks.removeObject(forKey: task.name as NSString)
expirationHandler?()
}) {
created = true
reusableTasks[name] = newTask
reusableTasks.setObject(newTask, forKey: name as NSString)
result = newTask
}
}
@@ -41,7 +41,12 @@ class MockClientProxy: ClientProxyProtocol {
func restartSync() { }
func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? {
nil
guard let room = roomSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }),
let displayName = room.asFilled?.name else {
return nil
}
return MockRoomProxy(displayName: displayName)
}
func loadUserDisplayName() async -> Result<String, ClientProxyError> {
@@ -55,7 +55,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
isDirect: true,
avatarURLString: nil,
lastMessage: AttributedString("Prosciutto beef ribs pancetta filet mignon kevin hamburger, chuck ham venison picanha. Beef ribs chislic turkey biltong tenderloin."),
lastMessageTimestamp: .now,
lastMessageTimestamp: .distantPast,
unreadNotificationCount: 4)),
.filled(details: RoomSummaryDetails(id: "2",
name: "Second room",
@@ -69,7 +69,7 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol {
isDirect: true,
avatarURLString: nil,
lastMessage: try? AttributedString(markdown: "**@mock:client.com**: T-bone beef ribs bacon"),
lastMessageTimestamp: .now,
lastMessageTimestamp: .distantPast,
unreadNotificationCount: 0)),
.empty(id: "3")
]
@@ -0,0 +1,28 @@
//
// 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
struct MockRoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(userId: String,
roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol {
let timelineController = MockRoomTimelineController()
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
return timelineController
}
}
@@ -32,19 +32,20 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
}
let roomId: String
let callbacks = PassthroughSubject<RoomTimelineControllerCallback, Never>()
private(set) var timelineItems = [RoomTimelineItemProtocol]()
var roomId: String {
roomProxy.id
}
init(userId: String,
roomId: String,
roomProxy: RoomProxyProtocol,
timelineProvider: RoomTimelineProviderProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol,
roomProxy: RoomProxyProtocol) {
mediaProvider: MediaProviderProtocol) {
self.userId = userId
self.roomId = roomId
self.timelineProvider = timelineProvider
self.timelineItemFactory = timelineItemFactory
self.mediaProvider = mediaProvider
@@ -0,0 +1,30 @@
//
// 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
struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(userId: String,
roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol {
RoomTimelineController(userId: userId,
roomProxy: roomProxy,
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
timelineItemFactory: timelineItemFactory,
mediaProvider: mediaProvider)
}
}
@@ -0,0 +1,25 @@
//
// 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
@MainActor
protocol RoomTimelineControllerFactoryProtocol {
func buildRoomTimelineController(userId: String,
roomProxy: RoomProxyProtocol,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol
}
@@ -26,6 +26,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
private let userSession: UserSessionProtocol
private let navigationSplitCoordinator: NavigationSplitCoordinator
private let bugReportService: BugReportServiceProtocol
private let roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol
private let emojiProvider: EmojiProviderProtocol = EmojiProvider()
private let sidebarNavigationStackCoordinator: NavigationStackCoordinator
@@ -33,11 +34,15 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
var callback: ((UserSessionFlowCoordinatorAction) -> Void)?
init(userSession: UserSessionProtocol, navigationSplitCoordinator: NavigationSplitCoordinator, bugReportService: BugReportServiceProtocol) {
init(userSession: UserSessionProtocol,
navigationSplitCoordinator: NavigationSplitCoordinator,
bugReportService: BugReportServiceProtocol,
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol) {
stateMachine = UserSessionFlowCoordinatorStateMachine()
self.userSession = userSession
self.navigationSplitCoordinator = navigationSplitCoordinator
self.bugReportService = bugReportService
self.roomTimelineControllerFactory = roomTimelineControllerFactory
sidebarNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
detailNavigationStackCoordinator = NavigationStackCoordinator(navigationSplitCoordinator: navigationSplitCoordinator)
@@ -150,13 +155,11 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
mediaProvider: userSession.mediaProvider,
roomProxy: roomProxy,
attributedStringBuilder: AttributedStringBuilder())
let timelineController = RoomTimelineController(userId: userId,
roomId: roomIdentifier,
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider,
roomProxy: roomProxy)
let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(userId: userId,
roomProxy: roomProxy,
timelineItemFactory: timelineItemFactory,
mediaProvider: userSession.mediaProvider)
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: detailNavigationStackCoordinator,
timelineController: timelineController,