Files
Termind/TFITests/MobileHostIntegrationRenderingTests.swift
T
2026-09-19 11:31:06 +08:00

102 lines
4.3 KiB
Swift

import SwiftUI
import TermindApplication
import TermindDomain
import TermindPresentation
import Testing
import UIKit
@testable import TermindIOS
@Suite(.serialized) @MainActor
struct MobileHostIntegrationRenderingTests {
@Test(arguments: ["en", "zh-Hans", "zh-Hant"])
func integrationPickerKeepsItsDraftAtPhoneWidth(language: String) async throws {
let draft = HostIntegrationDraft()
let controller = UIHostingController(
rootView: Form {
Section("Group") { Text("Production") }
HostIntegrationSection(mode: Binding(get: { draft.mode }, set: { draft.mode = $0 }))
}
.environment(\.locale, Locale(identifier: language)))
let scene = try #require(UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first)
let previous = scene.keyWindow
let window = UIWindow(windowScene: scene)
window.frame = CGRect(x: 0, y: 0, width: 375, height: 400)
window.rootViewController = controller
window.windowLevel = .alert + 1
window.makeKeyAndVisible()
defer {
window.isHidden = true
window.rootViewController = nil
previous?.makeKey()
}
try await Task.sleep(for: .milliseconds(500))
controller.view.layoutIfNeeded()
#expect(descendants(controller.view).compactMap { $0 as? UISegmentedControl }.isEmpty)
let picker = try #require(
descendants(controller.view).compactMap { $0 as? UIButton }.first { $0.showsMenuAsPrimaryAction })
let labels = ["Default", "Enable", "Disable"].map {
AppLocalization.string($0, locale: Locale(identifier: language))
}
let actions = try await menuActions(in: picker)
#expect(actions.map(\.title) == labels)
#expect(actions.map(\.state) == [.on, .off, .off])
picker.sendAction(actions[1])
#expect(draft.mode == .enabled)
controller.view.frame.size.width = 600
controller.view.layoutIfNeeded()
let enabledActions = try await menuActions(in: picker)
#expect(enabledActions.map(\.title) == labels)
#expect(enabledActions.map(\.state) == [.off, .on, .off])
picker.sendAction(enabledActions[2])
#expect(draft.mode == .disabled)
controller.view.frame.size.width = 375
controller.view.layoutIfNeeded()
let disabledActions = try await menuActions(in: picker)
#expect(disabledActions.map(\.title) == labels)
#expect(disabledActions.map(\.state) == [.off, .off, .on])
picker.sendAction(disabledActions[0])
#expect(draft.mode == .followGlobalSettings)
try await Task.sleep(for: .milliseconds(300))
let image = UIGraphicsImageRenderer(bounds: window.bounds).image { _ in
#expect(window.drawHierarchy(in: window.bounds, afterScreenUpdates: true))
}
let data = try #require(image.pngData())
#expect(data.count > 5_000)
Attachment.record(data, named: "host-integration-mobile-\(language).png")
try data.write(
to: FileManager.default.temporaryDirectory.appending(path: "host-integration-mobile-\(language).png"))
}
private func descendants(_ view: UIView) -> [UIView] {
[view] + view.subviews.flatMap(descendants)
}
private func menuActions(in button: UIButton) async throws -> [UIAction] {
button.performPrimaryAction()
try await Task.sleep(for: .milliseconds(500))
let interaction = try #require(button.contextMenuInteraction)
var actions: [UIAction] = []
interaction.updateVisibleMenu { menu in
actions = self.menuActions(menu)
return menu
}
interaction.dismissMenu()
try await Task.sleep(for: .milliseconds(300))
try #require(actions.count == 3)
return actions
}
private func menuActions(_ menu: UIMenu) -> [UIAction] {
menu.children.flatMap { element -> [UIAction] in
if let action = element as? UIAction { return [action] }
if let submenu = element as? UIMenu { return menuActions(submenu) }
return []
}
}
}
@Observable @MainActor private final class HostIntegrationDraft {
var mode = HostShellIntegrationMode.followGlobalSettings
}