mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
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:
@@ -219,7 +219,8 @@ class AppCoordinator: AppCoordinatorProtocol {
|
||||
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: SplashScreenCoordinator())
|
||||
let userSessionFlowCoordinator = UserSessionFlowCoordinator(userSession: userSession,
|
||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
||||
bugReportService: bugReportService)
|
||||
bugReportService: bugReportService,
|
||||
roomTimelineControllerFactory: RoomTimelineControllerFactory())
|
||||
|
||||
userSessionFlowCoordinator.callback = { [weak self] action in
|
||||
switch action {
|
||||
|
||||
@@ -57,9 +57,9 @@ class NavigationRootCoordinator: ObservableObject, CoordinatorProtocol, CustomSt
|
||||
|
||||
var description: String {
|
||||
if let rootCoordinator = rootModule?.coordinator {
|
||||
return "SingleScreenCoordinator(\(rootCoordinator)"
|
||||
return "NavigationRootCoordinator(\(rootCoordinator)"
|
||||
} else {
|
||||
return "SingleScreenCoordinator(Empty)"
|
||||
return "NavigationRootCoordinator(Empty)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
extension XCUIElement {
|
||||
func clearAndTypeText(_ text: String) {
|
||||
let maxAttemptCount = 10
|
||||
var attemptCount = 0
|
||||
|
||||
repeat {
|
||||
tap()
|
||||
|
||||
guard let currentValue = value as? String else {
|
||||
XCTFail("Tried to clear and type text into a non string value")
|
||||
return
|
||||
}
|
||||
|
||||
let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: currentValue.count)
|
||||
|
||||
typeText(deleteString)
|
||||
typeText(text)
|
||||
|
||||
if !exists { // Break if the element in question doesn't exist anymore
|
||||
break
|
||||
}
|
||||
|
||||
guard let newValue = value as? String else {
|
||||
XCTFail("Tried to clear and type text into a non string value")
|
||||
return
|
||||
}
|
||||
|
||||
if newValue == String(repeating: "•", count: text.count) { // Secure entry text field
|
||||
break
|
||||
}
|
||||
|
||||
if newValue == text.trimmingCharacters(in: .whitespacesAndNewlines) {
|
||||
break
|
||||
}
|
||||
|
||||
attemptCount += 1
|
||||
if attemptCount > maxAttemptCount {
|
||||
XCTFail("Failed clearAndTypeText after \(maxAttemptCount) attempts.")
|
||||
return
|
||||
}
|
||||
|
||||
} while true
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ struct HomeScreenRoomCell: View {
|
||||
context.send(viewAction: .loadRoomData(roomIdentifier: room.id))
|
||||
}
|
||||
}
|
||||
.accessibilityIdentifier("roomName:\(room.name)")
|
||||
}
|
||||
|
||||
var lastMessageFont: Font {
|
||||
|
||||
@@ -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")
|
||||
]
|
||||
|
||||
+28
@@ -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
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -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
|
||||
+30
@@ -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)
|
||||
}
|
||||
}
|
||||
+25
@@ -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,
|
||||
|
||||
@@ -36,6 +36,7 @@ enum UITestScreenIdentifier: String {
|
||||
case roomSmallTimelineIncomingAndSmallPagination
|
||||
case roomSmallTimelineLargePagination
|
||||
case sessionVerification
|
||||
case userSessionScreen
|
||||
}
|
||||
|
||||
extension UITestScreenIdentifier: CustomStringConvertible {
|
||||
|
||||
@@ -18,58 +18,64 @@ import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class UITestsAppCoordinator: AppCoordinatorProtocol {
|
||||
private var currentRootCoordinator: CoordinatorProtocol?
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
let notificationManager: NotificationManagerProtocol? = nil
|
||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||
private var mockScreens: [MockScreen] = []
|
||||
var notificationManager: NotificationManagerProtocol?
|
||||
|
||||
init() {
|
||||
UIView.setAnimationsEnabled(false)
|
||||
navigationStackCoordinator = NavigationStackCoordinator()
|
||||
navigationRootCoordinator = NavigationRootCoordinator()
|
||||
mockScreens = UITestScreenIdentifier.allCases.map { MockScreen(id: $0, navigationRootCoordinator: navigationRootCoordinator) }
|
||||
|
||||
ServiceLocator.shared.register(userNotificationController: MockUserNotificationController())
|
||||
}
|
||||
|
||||
func start() {
|
||||
let screens = mockScreens()
|
||||
let rootCoordinator = UITestsRootCoordinator(mockScreens: screens) { id in
|
||||
guard let screen = screens.first(where: { $0.id == id }) else {
|
||||
let rootCoordinator = UITestsRootCoordinator(mockScreens: mockScreens) { id in
|
||||
guard let screen = self.mockScreens.first(where: { $0.id == id }) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Store the initial coordinator so that it stays alive if drops it
|
||||
// For example when replacing the root in the authentication flows
|
||||
self.currentRootCoordinator = screen.coordinator
|
||||
|
||||
self.navigationStackCoordinator.setRootCoordinator(screen.coordinator)
|
||||
self.navigationRootCoordinator.setRootCoordinator(screen.coordinator)
|
||||
}
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(rootCoordinator)
|
||||
navigationRootCoordinator.setRootCoordinator(rootCoordinator)
|
||||
|
||||
Bundle.elementFallbackLanguage = "en"
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
navigationStackCoordinator.toPresentable()
|
||||
}
|
||||
|
||||
private func mockScreens() -> [MockScreen] {
|
||||
UITestScreenIdentifier.allCases.map { MockScreen(id: $0, navigationStackCoordinator: navigationStackCoordinator) }
|
||||
func toPresentable() -> AnyView {
|
||||
navigationRootCoordinator.toPresentable()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class MockScreen: Identifiable {
|
||||
let id: UITestScreenIdentifier
|
||||
let navigationStackCoordinator: NavigationStackCoordinator
|
||||
|
||||
private let navigationRootCoordinator: NavigationRootCoordinator
|
||||
private var retainedState = [Any]()
|
||||
|
||||
init(id: UITestScreenIdentifier, navigationRootCoordinator: NavigationRootCoordinator) {
|
||||
self.id = id
|
||||
self.navigationRootCoordinator = navigationRootCoordinator
|
||||
}
|
||||
|
||||
lazy var coordinator: CoordinatorProtocol = {
|
||||
switch id {
|
||||
case .login:
|
||||
return LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
|
||||
navigationStackCoordinator: navigationStackCoordinator))
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = LoginCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
|
||||
navigationStackCoordinator: navigationStackCoordinator))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .serverSelection:
|
||||
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
isModallyPresented: true))
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
isModallyPresented: true))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .serverSelectionNonModal:
|
||||
return ServerSelectionCoordinator(parameters: .init(authenticationService: MockAuthenticationServiceProxy(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
@@ -78,8 +84,12 @@ class MockScreen: Identifiable {
|
||||
return AnalyticsPromptCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"),
|
||||
mediaProvider: MockMediaProvider())))
|
||||
case .authenticationFlow:
|
||||
return AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(),
|
||||
navigationStackCoordinator: navigationStackCoordinator)
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = AuthenticationCoordinator(authenticationService: MockAuthenticationServiceProxy(),
|
||||
navigationStackCoordinator: navigationStackCoordinator)
|
||||
retainedState.append(coordinator)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .softLogout:
|
||||
let credentials = SoftLogoutCredentials(userId: "@mock:matrix.org",
|
||||
homeserverName: "matrix.org",
|
||||
@@ -93,47 +103,66 @@ class MockScreen: Identifiable {
|
||||
case .simpleUpgrade:
|
||||
return TemplateCoordinator(parameters: .init(promptType: .upgrade))
|
||||
case .home:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let session = MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:matrix.org"),
|
||||
mediaProvider: MockMediaProvider())
|
||||
return HomeScreenCoordinator(parameters: .init(userSession: session,
|
||||
attributedStringBuilder: AttributedStringBuilder(),
|
||||
bugReportService: MockBugReportService(),
|
||||
navigationStackCoordinator: navigationStackCoordinator))
|
||||
let coordinator = HomeScreenCoordinator(parameters: .init(userSession: session,
|
||||
attributedStringBuilder: AttributedStringBuilder(),
|
||||
bugReportService: MockBugReportService(),
|
||||
navigationStackCoordinator: navigationStackCoordinator))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .settings:
|
||||
return SettingsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"),
|
||||
mediaProvider: MockMediaProvider()),
|
||||
bugReportService: MockBugReportService()))
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = SettingsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
userSession: MockUserSession(clientProxy: MockClientProxy(userIdentifier: "@mock:client.com"),
|
||||
mediaProvider: MockMediaProvider()),
|
||||
bugReportService: MockBugReportService()))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .bugReport:
|
||||
return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
screenshot: nil,
|
||||
isModallyPresented: false))
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
screenshot: nil,
|
||||
isModallyPresented: true))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .bugReportWithScreenshot:
|
||||
return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
screenshot: Asset.Images.appLogo.image,
|
||||
isModallyPresented: false))
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(),
|
||||
userNotificationController: MockUserNotificationController(),
|
||||
screenshot: Asset.Images.appLogo.image,
|
||||
isModallyPresented: false))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .onboarding:
|
||||
return OnboardingCoordinator()
|
||||
case .roomPlainNoAvatar:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomName: "Some room name",
|
||||
roomAvatarUrl: nil,
|
||||
emojiProvider: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomEncryptedWithAvatar:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomName: "Some room name",
|
||||
roomAvatarUrl: "mock_url",
|
||||
emojiProvider: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomSmallTimeline:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
@@ -142,8 +171,12 @@ class MockScreen: Identifiable {
|
||||
roomName: "New room",
|
||||
roomAvatarUrl: "mock_url",
|
||||
emojiProvider: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomSmallTimelineIncomingAndSmallPagination:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.singleMessageChunk]
|
||||
@@ -155,8 +188,13 @@ class MockScreen: Identifiable {
|
||||
roomName: "Small timeline",
|
||||
roomAvatarUrl: "mock_url",
|
||||
emojiProvider: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .roomSmallTimelineLargePagination:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
@@ -166,15 +204,28 @@ class MockScreen: Identifiable {
|
||||
roomName: "Small timeline, paginating",
|
||||
roomAvatarUrl: "mock_url",
|
||||
emojiProvider: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .sessionVerification:
|
||||
let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy())
|
||||
return SessionVerificationCoordinator(parameters: parameters)
|
||||
case .userSessionScreen:
|
||||
let navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: SplashScreenCoordinator())
|
||||
|
||||
let clientProxy = MockClientProxy(userIdentifier: "@mock:client.com", roomSummaryProvider: MockRoomSummaryProvider(state: .loaded))
|
||||
|
||||
let coordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()),
|
||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
||||
bugReportService: MockBugReportService(),
|
||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory())
|
||||
|
||||
coordinator.start()
|
||||
|
||||
retainedState.append(coordinator)
|
||||
|
||||
return navigationSplitCoordinator
|
||||
}
|
||||
}()
|
||||
|
||||
init(id: UITestScreenIdentifier, navigationStackCoordinator: NavigationStackCoordinator) {
|
||||
self.id = id
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ struct UITestsRootCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
.accessibilityIdentifier(coordinator.id.rawValue)
|
||||
}
|
||||
.padding(.top, 50) // Add some top padding so the iPad split screen button isn't tapped by mistake
|
||||
.listStyle(.plain)
|
||||
.navigationTitle("Screens")
|
||||
.navigationViewStyle(.stack)
|
||||
}
|
||||
}
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Nicholas Cross
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1 +0,0 @@
|
||||
Files copied over from [nicholascross/WeakDictionary](https://github.com/nicholascross/WeakDictionary) because of a lack SPM support.
|
||||
@@ -1,116 +0,0 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2016 Nicholas Cross
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakDictionary<Key: Hashable, Value: AnyObject> {
|
||||
private var storage: [Key: WeakDictionaryReference<Value>]
|
||||
|
||||
public init() {
|
||||
self.init(storage: [Key: WeakDictionaryReference<Value>]())
|
||||
}
|
||||
|
||||
public init(dictionary: [Key: Value]) {
|
||||
var newStorage = [Key: WeakDictionaryReference<Value>]()
|
||||
dictionary.forEach { key, value in newStorage[key] = WeakDictionaryReference<Value>(value: value) }
|
||||
self.init(storage: newStorage)
|
||||
}
|
||||
|
||||
private init(storage: [Key: WeakDictionaryReference<Value>]) {
|
||||
self.storage = storage
|
||||
}
|
||||
|
||||
public mutating func reap() {
|
||||
storage = weakDictionary().storage
|
||||
}
|
||||
|
||||
public func weakDictionary() -> WeakDictionary<Key, Value> {
|
||||
self[startIndex..<endIndex]
|
||||
}
|
||||
|
||||
public func dictionary() -> [Key: Value] {
|
||||
var newStorage = [Key: Value]()
|
||||
|
||||
storage.forEach { key, value in
|
||||
if let retainedValue = value.value {
|
||||
newStorage[key] = retainedValue
|
||||
}
|
||||
}
|
||||
|
||||
return newStorage
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakDictionary: Collection {
|
||||
public typealias Index = DictionaryIndex<Key, WeakDictionaryReference<Value>>
|
||||
|
||||
public var startIndex: Index {
|
||||
storage.startIndex
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
storage.endIndex
|
||||
}
|
||||
|
||||
public func index(after index: Index) -> Index {
|
||||
storage.index(after: index)
|
||||
}
|
||||
|
||||
public subscript(position: Index) -> (Key, WeakDictionaryReference<Value>) {
|
||||
return storage[position]
|
||||
}
|
||||
|
||||
public subscript(key: Key) -> Value? {
|
||||
get {
|
||||
guard let valueRef = storage[key] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return valueRef.value
|
||||
}
|
||||
|
||||
set {
|
||||
guard let value = newValue else {
|
||||
storage[key] = nil
|
||||
return
|
||||
}
|
||||
|
||||
storage[key] = WeakDictionaryReference<Value>(value: value)
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(bounds: Range<Index>) -> WeakDictionary<Key, Value> {
|
||||
let subStorage = storage[bounds.lowerBound..<bounds.upperBound]
|
||||
var newStorage = [Key: WeakDictionaryReference<Value>]()
|
||||
|
||||
subStorage.filter { _, value in value.value != nil }
|
||||
.forEach { key, value in newStorage[key] = value }
|
||||
|
||||
return WeakDictionary<Key, Value>(storage: newStorage)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Dictionary where Value: AnyObject {
|
||||
func weakDictionary() -> WeakDictionary<Key, Value> {
|
||||
WeakDictionary<Key, Value>(dictionary: self)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2016 Nicholas Cross
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakDictionaryKey<Key: AnyObject & Hashable, Value: AnyObject>: Hashable {
|
||||
private weak var baseKey: Key?
|
||||
private let hash: Int
|
||||
private var retainedValue: Value?
|
||||
private let nilKeyHash = UUID().hashValue
|
||||
|
||||
public init(key: Key, value: Value? = nil) {
|
||||
baseKey = key
|
||||
retainedValue = value
|
||||
hash = key.hashValue
|
||||
}
|
||||
|
||||
public static func == (lhs: WeakDictionaryKey, rhs: WeakDictionaryKey) -> Bool {
|
||||
(lhs.baseKey != nil && rhs.baseKey != nil && lhs.baseKey == rhs.baseKey)
|
||||
|| lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
if baseKey == nil {
|
||||
hasher.combine(nilKeyHash)
|
||||
} else {
|
||||
hasher.combine(baseKey)
|
||||
}
|
||||
}
|
||||
|
||||
public var key: Key? {
|
||||
baseKey
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2016 Nicholas Cross
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakDictionaryReference<Value: AnyObject> {
|
||||
private weak var referencedValue: Value?
|
||||
|
||||
init(value: Value) {
|
||||
referencedValue = value
|
||||
}
|
||||
|
||||
public var value: Value? {
|
||||
referencedValue
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2016 Nicholas Cross
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct WeakKeyDictionary<Key: AnyObject & Hashable, Value: AnyObject> {
|
||||
private var storage: WeakDictionary<WeakDictionaryKey<Key, Value>, Value>
|
||||
private let valuesRetainedByKey: Bool
|
||||
|
||||
public init(valuesRetainedByKey: Bool = false) {
|
||||
self.init(
|
||||
storage: WeakDictionary<WeakDictionaryKey<Key, Value>, Value>(),
|
||||
valuesRetainedByKey: valuesRetainedByKey
|
||||
)
|
||||
}
|
||||
|
||||
public init(dictionary: [Key: Value], valuesRetainedByKey: Bool = false) {
|
||||
var newStorage = WeakDictionary<WeakDictionaryKey<Key, Value>, Value>()
|
||||
|
||||
dictionary.forEach { key, value in
|
||||
var keyRef: WeakDictionaryKey<Key, Value>!
|
||||
|
||||
if valuesRetainedByKey {
|
||||
keyRef = WeakDictionaryKey<Key, Value>(key: key, value: value)
|
||||
} else {
|
||||
keyRef = WeakDictionaryKey<Key, Value>(key: key)
|
||||
}
|
||||
|
||||
newStorage[keyRef] = value
|
||||
}
|
||||
|
||||
self.init(storage: newStorage, valuesRetainedByKey: valuesRetainedByKey)
|
||||
}
|
||||
|
||||
private init(storage: WeakDictionary<WeakDictionaryKey<Key, Value>, Value>, valuesRetainedByKey: Bool = false) {
|
||||
self.storage = storage
|
||||
self.valuesRetainedByKey = valuesRetainedByKey
|
||||
}
|
||||
|
||||
public mutating func reap() {
|
||||
storage = weakKeyDictionary().storage
|
||||
}
|
||||
|
||||
public func weakDictionary() -> WeakDictionary<Key, Value> {
|
||||
dictionary().weakDictionary()
|
||||
}
|
||||
|
||||
public func weakKeyDictionary() -> WeakKeyDictionary<Key, Value> {
|
||||
self[startIndex..<endIndex]
|
||||
}
|
||||
|
||||
public func dictionary() -> [Key: Value] {
|
||||
var newStorage = [Key: Value]()
|
||||
|
||||
storage.forEach { key, value in
|
||||
if let retainedKey = key.key, let retainedValue = value.value {
|
||||
newStorage[retainedKey] = retainedValue
|
||||
}
|
||||
}
|
||||
|
||||
return newStorage
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakKeyDictionary: Collection {
|
||||
public typealias Index = DictionaryIndex<WeakDictionaryKey<Key, Value>, WeakDictionaryReference<Value>>
|
||||
|
||||
public var startIndex: Index {
|
||||
storage.startIndex
|
||||
}
|
||||
|
||||
public var endIndex: Index {
|
||||
storage.endIndex
|
||||
}
|
||||
|
||||
public func index(after index: Index) -> Index {
|
||||
storage.index(after: index)
|
||||
}
|
||||
|
||||
public subscript(position: Index) -> (WeakDictionaryKey<Key, Value>, WeakDictionaryReference<Value>) {
|
||||
return storage[position]
|
||||
}
|
||||
|
||||
public subscript(key: Key) -> Value? {
|
||||
get {
|
||||
storage[WeakDictionaryKey<Key, Value>(key: key)]
|
||||
}
|
||||
|
||||
set {
|
||||
let retainedValue = valuesRetainedByKey ? newValue : nil
|
||||
let weakKey = WeakDictionaryKey<Key, Value>(key: key, value: retainedValue)
|
||||
storage[weakKey] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(bounds: Range<Index>) -> WeakKeyDictionary<Key, Value> {
|
||||
let subStorage = storage[bounds.lowerBound..<bounds.upperBound]
|
||||
var newStorage = WeakDictionary<WeakDictionaryKey<Key, Value>, Value>()
|
||||
|
||||
subStorage.filter { key, value in key.key != nil && value.value != nil }
|
||||
.forEach { key, value in newStorage[key] = value.value }
|
||||
|
||||
return WeakKeyDictionary<Key, Value>(storage: newStorage)
|
||||
}
|
||||
}
|
||||
|
||||
public extension WeakDictionary where Key: AnyObject {
|
||||
func weakKeyDictionary(valuesRetainedByKey: Bool = false) -> WeakKeyDictionary<Key, Value> {
|
||||
WeakKeyDictionary<Key, Value>(dictionary: dictionary(), valuesRetainedByKey: valuesRetainedByKey)
|
||||
}
|
||||
}
|
||||
|
||||
public extension Dictionary where Key: AnyObject, Value: AnyObject {
|
||||
func weakKeyDictionary(valuesRetainedByKey: Bool = false) -> WeakKeyDictionary<Key, Value> {
|
||||
WeakKeyDictionary<Key, Value>(dictionary: self, valuesRetainedByKey: valuesRetainedByKey)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user