- SessionController wraps RoyalVNCKit.VNCConnection via nonisolated delegate adapter that bridges callbacks to @MainActor; Keychain-resolved passwords; reconnect with jittered exponential backoff; NWPathMonitor adaptive-quality hook; framebuffer rendered to CALayer.contents from didUpdateFramebuffer. - Touch + trackpad input modes with floating soft cursor overlay; hardware keyboard via pressesBegan/Ended → X11 keysyms; UIPointerInteraction with hidden cursor for indirect pointers; pinch-to-zoom; Apple Pencil as direct touch; two-finger pan / indirect scroll wheel events. - Bidirectional clipboard sync (per-connection opt-in); multi-monitor screen picker with input remapping; screenshot capture → share sheet; on-disconnect reconnect/close prompt; view-only and curtain-mode persisted. - iPad multi-window via WindowGroup(for: UUID.self) + context-menu open; CloudKit-backed ModelContainer with local fallback; PrivacyInfo.xcprivacy. 10 VNCCore tests + 4 VNCUI tests pass; iPhone and iPad simulator builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.2 KiB
Swift
92 lines
3.2 KiB
Swift
import Testing
|
|
@testable import VNCCore
|
|
import Foundation
|
|
import CoreGraphics
|
|
|
|
@Suite struct SessionStateTests {
|
|
@Test func idleEqualsIdle() {
|
|
#expect(SessionState.idle == .idle)
|
|
}
|
|
|
|
@Test func connectedWithDifferentSizesDiffer() {
|
|
let a = SessionState.connected(framebufferSize: FramebufferSize(width: 1920, height: 1080))
|
|
let b = SessionState.connected(framebufferSize: FramebufferSize(width: 1280, height: 800))
|
|
#expect(a != b)
|
|
}
|
|
|
|
@Test func disconnectReasonsDiffer() {
|
|
#expect(DisconnectReason.userRequested != .authenticationFailed)
|
|
}
|
|
}
|
|
|
|
@Suite struct ReconnectPolicyTests {
|
|
@Test func userRequestedNeverReconnects() {
|
|
let policy = ReconnectPolicy.default
|
|
#expect(!policy.shouldReconnect(for: .userRequested))
|
|
#expect(!policy.shouldReconnect(for: .authenticationFailed))
|
|
}
|
|
|
|
@Test func networkFailuresReconnect() {
|
|
let policy = ReconnectPolicy.default
|
|
#expect(policy.shouldReconnect(for: .networkError("oops")))
|
|
#expect(policy.shouldReconnect(for: .remoteClosed))
|
|
}
|
|
|
|
@Test func policyDelaysGrowAndCap() {
|
|
let policy = ReconnectPolicy(maxAttempts: 5,
|
|
baseDelaySeconds: 1,
|
|
maxDelaySeconds: 8,
|
|
jitterFraction: 0)
|
|
#expect(policy.delay(for: 1) == 1)
|
|
#expect(policy.delay(for: 2) == 2)
|
|
#expect(policy.delay(for: 3) == 4)
|
|
#expect(policy.delay(for: 4) == 8)
|
|
#expect(policy.delay(for: 5) == 8)
|
|
#expect(policy.delay(for: 6) == nil)
|
|
}
|
|
|
|
@Test func zeroAttemptsDisablesReconnect() {
|
|
let policy = ReconnectPolicy.none
|
|
#expect(!policy.shouldReconnect(for: .networkError("x")))
|
|
}
|
|
}
|
|
|
|
@Suite struct RemoteScreenTests {
|
|
@Test func screensAreHashable() {
|
|
let a = RemoteScreen(id: 1, frame: CGRect(x: 0, y: 0, width: 1920, height: 1080))
|
|
let b = RemoteScreen(id: 1, frame: CGRect(x: 0, y: 0, width: 1920, height: 1080))
|
|
let c = RemoteScreen(id: 2, frame: CGRect(x: 0, y: 0, width: 1920, height: 1080))
|
|
#expect(a == b)
|
|
#expect(a != c)
|
|
#expect(Set([a, b, c]).count == 2)
|
|
}
|
|
}
|
|
|
|
@Suite struct PasswordProviderTests {
|
|
private final class StubKeychain: KeychainServicing, @unchecked Sendable {
|
|
var stored: [String: String] = [:]
|
|
func storePassword(_ password: String, account: String) throws {
|
|
stored[account] = password
|
|
}
|
|
func loadPassword(account: String) throws -> String? {
|
|
stored[account]
|
|
}
|
|
func deletePassword(account: String) throws {
|
|
stored[account] = nil
|
|
}
|
|
}
|
|
|
|
@Test func keychainBackedProviderReturnsStored() {
|
|
let keychain = StubKeychain()
|
|
try? keychain.storePassword("hunter2", account: "abc")
|
|
let provider = DefaultPasswordProvider(keychain: keychain)
|
|
#expect(provider.password(for: "abc") == "hunter2")
|
|
#expect(provider.password(for: "missing") == nil)
|
|
}
|
|
|
|
@Test func staticProviderAlwaysReturnsSame() {
|
|
let provider = StaticPasswordProvider(password: "fixed")
|
|
#expect(provider.password(for: "anything") == "fixed")
|
|
}
|
|
}
|