Files
Termind/TFITests/MobileHardwareKeyEditorRenderingTests.swift
T

100 lines
4.5 KiB
Swift

import CryptoKit
import SwiftUI
import TermindApplication
import TermindDomain
import TermindPresentation
import TermindRemoteInfrastructure
import Testing
import UIKit
@testable import TermindIOS
@Suite(
.requiresKeyWindow,
.serialized)
@MainActor
struct MobileHardwareKeyEditorRenderingTests {
@Test(arguments: ["en", "zh-Hans"], [false, true])
func hardwareKeyEditorsUseNativeActionsAndCompactStorageRows(language: String, enclave: Bool) async throws {
let key: SSHKeyRef
if enclave {
key = SSHKeyRef(
label: "Device Key", algorithm: .ecdsa256,
publicKey: "ecdsa-sha2-nistp256 " + String(repeating: "AAAA", count: 40),
fingerprint: "SHA256:fixture", storage: .secureEnclave)
} else {
key = try WebAuthnSSH.keyReference(
SecurityKeyCredential(
credentialID: Data(repeating: 1, count: 64),
publicKeyX963: P256.Signing.PrivateKey().publicKey.x963Representation), label: "Hardware Key")
}
let credentials = CredentialLibraryModel(library: HostLibraryModel(keys: [key]))
let scene = try #require(UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first)
let previous = scene.keyWindow
let window = UIWindow(windowScene: scene)
let controller = UIHostingController(
rootView: HardwareKeyEditorHost(credentials: credentials, key: key, enclave: enclave)
.environment(\.locale, Locale(identifier: language)))
window.rootViewController = controller
window.windowLevel = .alert + 1
window.makeKeyAndVisible()
defer {
window.isHidden = true
window.rootViewController = nil
previous?.makeKey()
}
try await Task.sleep(for: .milliseconds(800))
let sheet = try #require(controller.presentedViewController)
sheet.view.layoutIfNeeded()
let navigation = try #require(descendant(UINavigationController.self, in: sheet))
let item = try #require(navigation.topViewController?.navigationItem)
let buttons =
item.leadingItemGroups.flatMap(\.barButtonItems)
+ item.trailingItemGroups.flatMap(\.barButtonItems)
+ (item.leftBarButtonItems ?? []) + (item.rightBarButtonItems ?? [])
let names = Set(buttons.compactMap { $0.accessibilityLabel ?? $0.title })
#expect(!names.isDisjoint(with: ["Save", "保存"]))
#expect(!names.isDisjoint(with: ["Cancel", "取消"]))
let collection = try #require(descendant(UICollectionView.self, in: sheet.view))
let storage = try #require(collection.cellForItem(at: IndexPath(item: 0, section: 1)))
#expect(storage.bounds.height < 100, "Storage row height: \(storage.bounds.height)")
let image = UIGraphicsImageRenderer(bounds: window.bounds).image { _ in
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
}
let data = try #require(image.pngData())
let directory = FileManager.default.temporaryDirectory.appending(path: "hardware-key-editors")
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
let device = UIDevice.current.userInterfaceIdiom == .pad ? "ipad" : "iphone"
let output = directory.appending(path: "\(device)-\(enclave ? "enclave" : "security-key")-\(language).png")
try data.write(to: output)
print("Hardware key editor snapshot: \(output.path)")
}
private func descendant<T: UIViewController>(_ type: T.Type, in controller: UIViewController) -> T? {
if let value = controller as? T { return value }
return controller.children.lazy.compactMap { descendant(type, in: $0) }.first
}
private func descendant<T: UIView>(_ type: T.Type, in view: UIView) -> T? {
if let value = view as? T { return value }
return view.subviews.lazy.compactMap { descendant(type, in: $0) }.first
}
}
private struct HardwareKeyEditorHost: View {
let credentials: CredentialLibraryModel
let key: SSHKeyRef
let enclave: Bool
@State private var presented = true
var body: some View {
Color.clear.sheet(isPresented: $presented) {
if enclave {
MobileKeyEditor(model: MobileKeyEditorModel(mode: .edit, credentials: credentials, key: key))
} else {
SecurityKeyEditor(credentials: credentials, existing: key)
}
}
}
}