169 lines
6.0 KiB
Swift
169 lines
6.0 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
import Combine
|
|
import CoreLocation
|
|
import SwiftUI
|
|
|
|
class AccessibilityTestsAppCoordinator: AppCoordinatorProtocol {
|
|
var windowManager: any SecureWindowManagerProtocol
|
|
|
|
func handleDeepLink(_ url: URL, isExternalURL: Bool, windowType: SecondaryWindowType?) -> Bool {
|
|
fatalError("Not implemented")
|
|
}
|
|
|
|
func handleAppRoute(_ appRoute: AppRoute, windowType: SecondaryWindowType?) {
|
|
fatalError("Not implemented.")
|
|
}
|
|
|
|
func handlePotentialPhishingAttempt(url: URL, openURLAction: @escaping (URL) -> Void) -> Bool {
|
|
fatalError("Not implemented")
|
|
}
|
|
|
|
func handleUserActivity(_ userActivity: NSUserActivity) {
|
|
fatalError("Not implemented")
|
|
}
|
|
|
|
private let previewsWrapper: PreviewsWrapper
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
init(appDelegate: AppDelegate) {
|
|
windowManager = WindowManager(appDelegate: appDelegate)
|
|
// disabling View animations
|
|
UIView.setAnimationsEnabled(false)
|
|
|
|
MXLog.configure(currentTarget: "accessibility-tests")
|
|
|
|
ServiceLocator.shared.register(userIndicatorController: UserIndicatorController())
|
|
|
|
AppSettings.configureWithSuiteName("io.element.elementx.accessibilitytests")
|
|
AppSettings.resetAllSettings()
|
|
ServiceLocator.shared.register(appSettings: AppSettings())
|
|
|
|
let analyticsClient = AnalyticsClientMock()
|
|
analyticsClient.isRunning = false
|
|
ServiceLocator.shared.register(analytics: AnalyticsService(client: analyticsClient,
|
|
appSettings: ServiceLocator.shared.settings))
|
|
|
|
guard let name = ProcessInfo.accessibilityViewID,
|
|
let previewType = TestablePreviewsDictionary.dictionary[name] else {
|
|
fatalError("Unable to launch with unknown screen.")
|
|
}
|
|
previewsWrapper = .init(name: name, previews: previewType._allPreviews)
|
|
|
|
setupSignalling()
|
|
// Used to perform the request check before the tests run on CI, so it can be immediately dismissed.
|
|
CLLocationManager().requestWhenInUseAuthorization()
|
|
}
|
|
|
|
func toPresentable() -> AnyView {
|
|
AnyView(PreviewsWrapperView(wrapper: previewsWrapper))
|
|
}
|
|
|
|
private func setupSignalling() {
|
|
do {
|
|
let client = try UITestsSignalling.Client(mode: .app)
|
|
client.signals.sink { [weak self] signal in
|
|
guard let self else { return }
|
|
switch signal {
|
|
case .accessibilityAudit(let auditSignal):
|
|
switch auditSignal {
|
|
case .nextPreview:
|
|
Task { [weak self] in
|
|
guard let self else { return }
|
|
await previewsWrapper.updateCurrentIndex()
|
|
do {
|
|
guard !previewsWrapper.isDone else {
|
|
try client.send(.accessibilityAudit(.noMorePreviews))
|
|
return
|
|
}
|
|
|
|
try client.send(.accessibilityAudit(.nextPreviewReady(name: previewsWrapper.previewName)))
|
|
} catch {
|
|
fatalError("Failed sending signal: \(signal)")
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
.store(in: &cancellables)
|
|
} catch {
|
|
fatalError("Unable to start client signalling")
|
|
}
|
|
}
|
|
}
|
|
|
|
struct PreviewsWrapperView: View {
|
|
let wrapper: PreviewsWrapper
|
|
@Environment(\.dynamicTypeSize) var dynamicTypeSize
|
|
|
|
var body: some View {
|
|
if wrapper.currentIndex >= 0, !wrapper.isDone {
|
|
wrapper.currentPreview.content
|
|
.id("\(wrapper.previewName)-\(dynamicTypeSize)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@Observable final class PreviewsWrapper {
|
|
private let name: String
|
|
private let previews: [_Preview]
|
|
private(set) var currentIndex = -1
|
|
var currentPreview: _Preview {
|
|
previews[currentIndex]
|
|
}
|
|
|
|
private(set) var isDone = false
|
|
|
|
var previewName: String {
|
|
"\(name)-\(currentPreview.displayName ?? String(currentIndex))"
|
|
}
|
|
|
|
init(name: String, previews: [_Preview]) {
|
|
self.name = name
|
|
self.previews = previews
|
|
}
|
|
|
|
@MainActor
|
|
func updateCurrentIndex() async {
|
|
let newIndex = currentIndex + 1
|
|
guard newIndex < previews.count else {
|
|
isDone = true
|
|
return
|
|
}
|
|
let newPreview = previews[newIndex]
|
|
var fulfillmentSource: SnapshotFulfillmentPreferenceKey.Source?
|
|
let preferenceReadingView = newPreview.content.onPreferenceChange(SnapshotFulfillmentPreferenceKey.self) { fulfillmentSource = $0?.source }
|
|
|
|
// Render an image of the view in order to trigger the preference updates to occur.
|
|
let imageRenderer = ImageRenderer(content: preferenceReadingView)
|
|
_ = imageRenderer.uiImage
|
|
|
|
switch fulfillmentSource {
|
|
case .publisher(let publisher):
|
|
_ = await publisher
|
|
// Not sure whye byt some publisher seem to not properly comunicate their completion,
|
|
// so we added a timeout. Since we are going to migrate from publishers to stream,
|
|
// this is a temporary solution
|
|
.timeout(.seconds(1), scheduler: DispatchQueue.main)
|
|
.values.first { $0 == true }
|
|
case .sequence(let sequence):
|
|
_ = await sequence.first { $0 == true }
|
|
case .none:
|
|
break
|
|
}
|
|
|
|
currentIndex = newIndex
|
|
}
|
|
}
|