diff --git a/Sources/App/AppState.swift b/Sources/App/AppState.swift index c27eacb..746ac5b 100644 --- a/Sources/App/AppState.swift +++ b/Sources/App/AppState.swift @@ -11,6 +11,19 @@ final class AppState: ObservableObject { @Published var lastForegroundedAt: Date = Date() private init() { + // Test-only overrides + let args = ProcessInfo.processInfo.arguments + if args.contains("--reset-state") { + Keychain.shared.delete(key: "piremote.credential") + UserDefaults.standard.removeObject(forKey: "faceid.enabled") + } + if args.contains("--enable-faceid") { + UserDefaults.standard.set(true, forKey: "faceid.enabled") + } + if args.contains("--force-lock") { + isLocked = true + } + // Try loading persisted credential on launch credential = try? Keychain.shared.load(key: "piremote.credential") } diff --git a/Sources/App/piRemoteApp.swift b/Sources/App/piRemoteApp.swift index fc0614e..f161e95 100644 --- a/Sources/App/piRemoteApp.swift +++ b/Sources/App/piRemoteApp.swift @@ -15,6 +15,15 @@ struct piRemoteApp: App { .onAppear { notificationDelegate.setup() UIApplication.shared.registerForRemoteNotifications() + + // Test-only: auto-pair if argument present + if let pairArgIndex = ProcessInfo.processInfo.arguments.firstIndex(of: "--pair-with-url"), + pairArgIndex + 1 < ProcessInfo.processInfo.arguments.count { + let urlString = ProcessInfo.processInfo.arguments[pairArgIndex + 1] + if let url = URL(string: urlString) { + handlePairingURL(url) + } + } } .onOpenURL { url in handlePairingURL(url) diff --git a/Sources/Core/Sessions/SessionRegistry.swift b/Sources/Core/Sessions/SessionRegistry.swift index ac9583a..e0455ba 100644 --- a/Sources/Core/Sessions/SessionRegistry.swift +++ b/Sources/Core/Sessions/SessionRegistry.swift @@ -104,6 +104,16 @@ private struct SessionItem: Decodable { let id: String let name: String let state: String + + enum CodingKeys: String, CodingKey { case id, name, state } + + init(from decoder: Decoder) throws { + let c = try decoder.container(keyedBy: CodingKeys.self) + id = try c.decode(String.self, forKey: .id) + name = try c.decode(String.self, forKey: .name) + // Defensive: older sidecar versions omit `state` from POST /sessions. + state = (try? c.decode(String.self, forKey: .state)) ?? "idle" + } } // MARK: - Errors diff --git a/Sources/UI/Input/ModifierBar.swift b/Sources/UI/Input/ModifierBar.swift index d5e7c7e..ca8883a 100644 --- a/Sources/UI/Input/ModifierBar.swift +++ b/Sources/UI/Input/ModifierBar.swift @@ -187,6 +187,8 @@ private struct BarButton: View { ) } .buttonStyle(.plain) // prevent SwiftUI from wrapping in extra chrome + .accessibilityLabel(title) + .accessibilityIdentifier("modbar.\(title)") } } @@ -238,6 +240,11 @@ struct RepeatingBarButton: View { } } ) + // Make it discoverable for XCUITest (Text + gesture isn't a button) + .accessibilityElement() + .accessibilityLabel(title) + .accessibilityIdentifier("modbar.\(title)") + .accessibilityAddTraits(.isButton) } // MARK: Helpers diff --git a/Sources/UI/Sessions/SessionSwitcher.swift b/Sources/UI/Sessions/SessionSwitcher.swift index f71d5db..c21f58a 100644 --- a/Sources/UI/Sessions/SessionSwitcher.swift +++ b/Sources/UI/Sessions/SessionSwitcher.swift @@ -55,6 +55,8 @@ struct SessionSwitcher: View { } label: { Image(systemName: "plus") } + .accessibilityLabel("New Session") + .accessibilityIdentifier("sessionswitcher.new") } } .alert("New Session", isPresented: $showNewSessionAlert) { diff --git a/Sources/UI/Status/StatusBar.swift b/Sources/UI/Status/StatusBar.swift index dce4931..889d8d3 100644 --- a/Sources/UI/Status/StatusBar.swift +++ b/Sources/UI/Status/StatusBar.swift @@ -39,6 +39,8 @@ struct StatusBar: View { Image(systemName: "list.bullet") .font(.caption) } + .accessibilityLabel("Switcher") + .accessibilityIdentifier("statusbar.switcher") } if onSettings != nil { @@ -48,6 +50,8 @@ struct StatusBar: View { Image(systemName: "gear") .font(.caption) } + .accessibilityLabel("Settings") + .accessibilityIdentifier("statusbar.settings") } if onUnpair != nil { @@ -58,6 +62,8 @@ struct StatusBar: View { .font(.caption) .foregroundStyle(.red) } + .accessibilityLabel("Unpair") + .accessibilityIdentifier("statusbar.unpair") } } } diff --git a/Sources/UI/Terminal/MainTerminalView.swift b/Sources/UI/Terminal/MainTerminalView.swift index 806b3ec..0337144 100644 --- a/Sources/UI/Terminal/MainTerminalView.swift +++ b/Sources/UI/Terminal/MainTerminalView.swift @@ -21,6 +21,13 @@ struct MainTerminalView: View { @StateObject private var registry = SessionRegistry() // T-2.6 @EnvironmentObject var appState: AppState + /// True when running under XCUITest. Skips the live SwiftTerm view + WS + /// connection, which otherwise keep the app non-idle and cause every + /// XCUITest interaction to block for ~120 s. + private var isUITest: Bool { + ProcessInfo.processInfo.arguments.contains("--uitest") + } + var body: some View { VStack(spacing: 0) { // ── Status bar ────────────────────────────────────────── @@ -34,8 +41,14 @@ struct MainTerminalView: View { ) // ── Terminal ──────────────────────────────────────────── - TerminalViewRepresentable(controller: terminalVC) - .ignoresSafeArea(edges: .bottom) + if isUITest { + Color.black + .overlay(Text("UITest mode").foregroundStyle(.white).font(.caption)) + .accessibilityIdentifier("terminal.placeholder") + } else { + TerminalViewRepresentable(controller: terminalVC) + .ignoresSafeArea(edges: .bottom) + } Divider() @@ -52,6 +65,8 @@ struct MainTerminalView: View { .task { await registry.refresh(credential: credential) } // T-2.6 .onChange(of: activeSessionId) { _, newId in guard let newId else { return } + // UI-test mode: no terminal view, no WS — just update the label. + if isUITest { sessionName = newId; return } // Avoid reconnect storm if id already matches the current connection. if connection?.id == newId { return } Task { @@ -78,6 +93,15 @@ struct MainTerminalView: View { // MARK: - Bootstrap private func initialBootstrap() async { + // UI-test mode: skip the live WebSocket. SwiftTerm's constant redraw + // on incoming frames prevents XCUITest's idle wait from completing, + // making every .tap() block for ~120 s. Status bar, modifier bar, and + // both sheets still render normally. + if ProcessInfo.processInfo.arguments.contains("--uitest") { + statusText = "● uitest" + sessionName = "uitest" + return + } statusText = "Looking for sessions…" do { let sessionId = try await resolveSession() diff --git a/Sources/UI/Terminal/TerminalViewController.swift b/Sources/UI/Terminal/TerminalViewController.swift index 36a17b8..f75b2cc 100644 --- a/Sources/UI/Terminal/TerminalViewController.swift +++ b/Sources/UI/Terminal/TerminalViewController.swift @@ -70,8 +70,9 @@ public final class TerminalViewController: UIViewController { /// SwiftTerm's `feed(byteArray:)` is thread-safe; this wrapper is /// intentionally @MainActor so callers remain on the main actor. public func feed(data: Data) { + guard let tv = terminalView else { return } let bytes = [UInt8](data) - terminalView.feed(byteArray: bytes[...]) + tv.feed(byteArray: bytes[...]) } /// Feed a base64-encoded ANSI snapshot (e.g. from the sidecar's diff --git a/UITests/Helpers.swift b/UITests/Helpers.swift new file mode 100644 index 0000000..5f33a32 --- /dev/null +++ b/UITests/Helpers.swift @@ -0,0 +1,74 @@ +import Foundation +import XCTest + +@MainActor +extension XCUIApplication { + + /// Launch with a freshly-fetched pairing URL so the test starts from a + /// known paired state. Hits `/pair-qr` on the sidecar to obtain a + /// one-time-use token, then passes it to the app via launch args. + /// Fails the current test if the sidecar is unreachable. + func launchPaired(file: StaticString = #file, line: UInt = #line) { + guard let url = TestSidecar.freshPairURL() else { + XCTFail("Could not fetch fresh pair URL from sidecar — is it running?", + file: file, line: line) + return + } + launchArguments = ["--uitest", "--reset-state", "--pair-with-url", url] + launch() + } + + /// Launch with state reset so the PairingFlowView is shown. + func launchUnpaired() { + launchArguments = ["--uitest", "--reset-state"] + launch() + } + + /// Launch with a specific pairing URL (for the explicit deep-link test). + func launchWithPairing(url: String) { + launchArguments = ["--uitest", "--reset-state", "--pair-with-url", url] + launch() + } + + /// Launch directly into the lock screen (faceid enabled + forced lock). + /// Resets state first so the test isn't polluted by previous runs. + func launchLocked() { + launchArguments = ["--uitest", "--reset-state", "--enable-faceid", "--force-lock"] + launch() + } +} + +// MARK: - Sidecar helpers + +enum TestSidecar { + static let baseURL = "http://10.13.37.2:17373" + static let bearer = "GeoZytsPPGwItCHRNu8EwoZGlHH5iUAx" + + /// Synchronously fetch a fresh pairing URL from the sidecar. + static func freshPairURL() -> String? { + let url = URL(string: "\(baseURL)/pair-qr?token=\(bearer)&format=url")! + var result: String? + let sem = DispatchSemaphore(value: 0) + URLSession.shared.dataTask(with: url) { data, _, _ in + defer { sem.signal() } + guard let data = data, + let text = String(data: data, encoding: .utf8) else { return } + if let match = text.range(of: #"pi-remote://[^\s]+"#, + options: .regularExpression) { + result = String(text[match]) + } + }.resume() + _ = sem.wait(timeout: .now() + 5) + return result + } + + /// Delete a session via the sidecar REST API (best-effort, for cleanup). + static func deleteSession(_ id: String) { + var req = URLRequest(url: URL(string: "\(baseURL)/sessions/\(id)")!) + req.httpMethod = "DELETE" + req.setValue("Bearer \(bearer)", forHTTPHeaderField: "Authorization") + let sem = DispatchSemaphore(value: 0) + URLSession.shared.dataTask(with: req) { _, _, _ in sem.signal() }.resume() + _ = sem.wait(timeout: .now() + 5) + } +} diff --git a/UITests/LockScreenUITests.swift b/UITests/LockScreenUITests.swift new file mode 100644 index 0000000..ac2415b --- /dev/null +++ b/UITests/LockScreenUITests.swift @@ -0,0 +1,21 @@ +import XCTest + +@MainActor +final class LockScreenUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLockScreenAppearance() throws { + let app = XCUIApplication() + app.launchLocked() + + // Assert LockView is showing + let lockText = app.staticTexts["Locked"] + XCTAssertTrue(lockText.waitForExistence(timeout: 5), "Lock screen should be visible when forced") + + // We can't easily test Face ID success in simulator without manual menu interaction, + // so we just verify the lock screen is presented. + } +} diff --git a/UITests/ModifierBarUITests.swift b/UITests/ModifierBarUITests.swift new file mode 100644 index 0000000..6f397e6 --- /dev/null +++ b/UITests/ModifierBarUITests.swift @@ -0,0 +1,47 @@ +import XCTest + +@MainActor +final class ModifierBarUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testModifierBarButtons() throws { + let app = XCUIApplication() + app.launchPaired() + + // List of buttons to verify + // Wait until the terminal view is up and the modifier bar visible. + XCTAssertTrue(app.buttons["Ctrl"].waitForExistence(timeout: 10), + "Modifier bar should appear after pairing") + + let buttons = [ + "Ctrl", "Esc", "Tab", "←", "↑", "↓", "→", "⇧↵", "📋" + ] + + for title in buttons { + let btn = app.buttons[title] + XCTAssertTrue(btn.exists, "Modifier button '\(title)' should be present") + } + + // Test Ctrl sticky state + let ctrlBtn = app.buttons["Ctrl"] + // In SwiftUI, the visual state (background color) isn't directly accessible via XCUITest + // unless we add accessibilityValue or similar. + // However, we can verify that tapping it doesn't crash and the app remains responsive. + ctrlBtn.tap() + + // Tap a few other buttons + app.buttons["Esc"].tap() + app.buttons["↑"].tap() + app.buttons["📋"].tap() + + // Verify PasteSheet appears (navigationTitle is "Paste") + let pasteSheetTitle = app.staticTexts["Paste"] + XCTAssertTrue(pasteSheetTitle.waitForExistence(timeout: 5), "Paste sheet should appear after tapping clipboard button") + + // Dismiss PasteSheet + app.buttons["Cancel"].tap() + } +} diff --git a/UITests/PairingUITests.swift b/UITests/PairingUITests.swift new file mode 100644 index 0000000..da8a9da --- /dev/null +++ b/UITests/PairingUITests.swift @@ -0,0 +1,31 @@ +import XCTest + +@MainActor +final class PairingUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testLaunchUnpairedShowsPairingFlow() throws { + let app = XCUIApplication() + app.launchUnpaired() + + // Assert PairingFlowView is showing + let scanButton = app.buttons["Tap to Scan"] + XCTAssertTrue(scanButton.waitForExistence(timeout: 5), "PairingFlowView should be visible on unpaired launch") + } + + func testAutoPairingViaLaunchArgument() throws { + guard let pairingURL = TestSidecar.freshPairURL() else { + throw XCTSkip("Could not fetch fresh pair URL - sidecar may be down") + } + + let app = XCUIApplication() + app.launchWithPairing(url: pairingURL) + + let switcherButton = app.buttons["Switcher"] + XCTAssertTrue(switcherButton.waitForExistence(timeout: 15), + "App should auto-pair and show MainTerminalView") + } +} diff --git a/UITests/SessionSwitcherUITests.swift b/UITests/SessionSwitcherUITests.swift new file mode 100644 index 0000000..5508ba0 --- /dev/null +++ b/UITests/SessionSwitcherUITests.swift @@ -0,0 +1,72 @@ +import XCTest + +@MainActor +final class SessionSwitcherUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testSessionSwitcherFlow() throws { + let app = XCUIApplication() + app.launchPaired() + + // 1. Open Switcher + let switcherButton = app.buttons["Switcher"] + XCTAssertTrue(switcherButton.waitForExistence(timeout: 10)) + switcherButton.tap() + + // 2. Assert Navigation Title + let navTitle = app.staticTexts["Sessions"] + XCTAssertTrue(navTitle.waitForExistence(timeout: 5), "Session switcher sheet should have title 'Sessions'") + + // 3. Assert existing sessions + let mainSession = app.staticTexts["main"] + let workSession = app.staticTexts["work"] + let logsSession = app.staticTexts["logs"] + + XCTAssertTrue(mainSession.exists, "Session 'main' should be present") + XCTAssertTrue(workSession.exists, "Session 'work' should be present") + XCTAssertTrue(logsSession.exists, "Session 'logs' should be present") + + // 4. Select 'main' and verify dismissal + mainSession.tap() + XCTAssertFalse(navTitle.waitForExistence(timeout: 5), "Sheet should dismiss after selecting a session") + + // 5. Create new session + switcherButton.tap() + let addButton = app.buttons["New Session"] + XCTAssertTrue(addButton.waitForExistence(timeout: 5)) + addButton.tap() + + let nameField = app.textFields["Session name"] + XCTAssertTrue(nameField.waitForExistence(timeout: 5)) + nameField.tap() + let uniqueName = "uitest-\(Int(Date().timeIntervalSince1970))" + nameField.typeText(uniqueName) + + let createButton = app.buttons["Create"] + XCTAssertTrue(createButton.exists) + createButton.tap() + + // 6. Verify new session appears (refresh runs async after spawn; + // allow extra time for the sidecar round-trip + list rerender). + let newSession = app.staticTexts[uniqueName] + if !newSession.waitForExistence(timeout: 15) { + // Capture diagnostic info to file before failing + let screenshot = XCUIScreen.main.screenshot() + let att = XCTAttachment(screenshot: screenshot) + att.lifetime = .keepAlways + att.name = "session-switcher-failure" + add(att) + let hierarchy = XCTAttachment(string: app.debugDescription) + hierarchy.lifetime = .keepAlways + hierarchy.name = "hierarchy" + add(hierarchy) + XCTFail("New session '\(uniqueName)' should appear in the list") + } + + // Cleanup: delete via sidecar API + TestSidecar.deleteSession(uniqueName) + } +} diff --git a/UITests/SettingsUITests.swift b/UITests/SettingsUITests.swift new file mode 100644 index 0000000..78a980d --- /dev/null +++ b/UITests/SettingsUITests.swift @@ -0,0 +1,55 @@ +import XCTest + +@MainActor +final class SettingsUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testSettingsView() throws { + let app = XCUIApplication() + app.launchPaired() + + // 1. Open Settings + let settingsButton = app.buttons["Settings"] + XCTAssertTrue(settingsButton.waitForExistence(timeout: 10)) + settingsButton.tap() + + // 2. Assert Navigation Title + let navTitle = app.staticTexts["Settings"] + XCTAssertTrue(navTitle.waitForExistence(timeout: 5), "Settings sheet should have title 'Settings'") + + // 3. Assert section content (SwiftUI renders Section headers as + // uppercase "SECURITY" etc. but visibility is fragile across SDKs. + // Asserting on the section's contents is more robust.) + XCTAssertTrue(app.switches["Require Face ID"].waitForExistence(timeout: 5), + "Security → Face ID toggle should exist") + XCTAssertTrue(app.buttons["Unpair"].exists, + "Danger → Unpair button should exist") + + // 4. Toggle Face ID. SwiftUI's Toggle inside a Form is famously + // sticky for XCUI's .tap() — a coordinate hit on the switch itself + // is more reliable. + let faceIDToggle = app.switches["Require Face ID"] + XCTAssertTrue(faceIDToggle.exists, "Require Face ID toggle should exist") + let initialState = faceIDToggle.value as? String + faceIDToggle.coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5)).tap() + // Give SwiftUI a moment to update + persist into UserDefaults. + let flipped = NSPredicate(format: "value != %@", initialState ?? "") + expectation(for: flipped, evaluatedWith: faceIDToggle, handler: nil) + waitForExpectations(timeout: 3) + + // 5. Verify Sidecar Info + // Host:Port is in a LabeledContent. In XCUITest, LabeledContent often appears as two staticTexts. + // We look for the value that matches the pattern. + let hostText = app.staticTexts.matching(NSPredicate(format: "label CONTAINS '10.13.37.2:17373'")).firstMatch + XCTAssertTrue(hostText.exists, "Sidecar host:port should be displayed correctly") + + // 6. Dismiss + let doneButton = app.buttons["Done"] + XCTAssertTrue(doneButton.exists) + doneButton.tap() + XCTAssertFalse(navTitle.waitForExistence(timeout: 5), "Settings sheet should dismiss") + } +} diff --git a/UITests/StatusBarUITests.swift b/UITests/StatusBarUITests.swift new file mode 100644 index 0000000..226f289 --- /dev/null +++ b/UITests/StatusBarUITests.swift @@ -0,0 +1,22 @@ +import XCTest + +@MainActor +final class StatusBarUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + func testStatusBarButtonsPresence() throws { + let app = XCUIApplication() + app.launchPaired() + + let switcherButton = app.buttons["Switcher"] + let settingsButton = app.buttons["Settings"] + let unpairButton = app.buttons["Unpair"] + + XCTAssertTrue(switcherButton.waitForExistence(timeout: 10), "Switcher button should be present") + XCTAssertTrue(settingsButton.waitForExistence(timeout: 5), "Settings button should be present") + XCTAssertTrue(unpairButton.waitForExistence(timeout: 5), "Unpair button should be present") + } +} diff --git a/piRemote.xcodeproj/project.pbxproj b/piRemote.xcodeproj/project.pbxproj index d1b77b0..f9e562b 100644 --- a/piRemote.xcodeproj/project.pbxproj +++ b/piRemote.xcodeproj/project.pbxproj @@ -7,9 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + 0572AF0EFE02ABD12CD5C584 /* SmokeUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE5E1A0BE69AF1FEF73E373F /* SmokeUITests.swift */; }; 05CD861F694B84577A4B5A27 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9BAF4FBE6CC23FDD9B40040 /* PairingTests.swift */; }; 09AC16350B4E83B71B05A9D5 /* ResumeCursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7961BE126AFEEE4B7AA6621 /* ResumeCursor.swift */; }; + 132F60783EC43AE420FA7CD5 /* LockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94525E2A804BF64F12806D4E /* LockView.swift */; }; 16095F16FAB72320676A729D /* ResumeCursorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7607FF3804A2602B1C6A05D4 /* ResumeCursorTests.swift */; }; + 17F996CE1F5CF3FF12E5C1AB /* LockScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF52ADD110456E368EAE217C /* LockScreenUITests.swift */; }; 19E584DD72E8F6DE3AF4E77F /* MainTerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D4E4BB86FBFBD80287048C1 /* MainTerminalView.swift */; }; 1F353AB548615ECD7D241EF7 /* SessionConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F95D26CD899B18D07AB0B2 /* SessionConnection.swift */; }; 2AA3AC859917D32C1444FC5B /* FrameCodec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15B6B497329B98A4508D963B /* FrameCodec.swift */; }; @@ -18,10 +21,12 @@ 30E07FF586EABBBB8C70AE60 /* piRemoteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73D747BC787A24B4E225B142 /* piRemoteApp.swift */; }; 3486C15393498F5306C8F43B /* ScrollbackCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22658EED98A0B3C2183AACDD /* ScrollbackCacheTests.swift */; }; 3AC56484E8AC068C10F31324 /* REVIEW_NOTES.md in Resources */ = {isa = PBXBuildFile; fileRef = 6DE4A325EEA53870390B89D9 /* REVIEW_NOTES.md */; }; + 4334301CFE20E6B66BDE7D19 /* SessionSwitcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8C77FC9D24BB5F8DA96386C /* SessionSwitcher.swift */; }; 4877B4085C529C640FBBE6AB /* ThemeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC48E39D19238178A180B30C /* ThemeStore.swift */; }; 56096DB64F700FC00C4D58CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFF032BC30D513204211ADA5 /* Assets.xcassets */; }; 5F82D50C477F47893FADA8CB /* PasteSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3F776605A4109C047E44A89 /* PasteSheet.swift */; }; 5F8F5E6D2D5277CB90FA98A0 /* ThemeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FA0A0FD737901834AD5705 /* ThemeTests.swift */; }; + 6A52DBC5C2845ADA05B37A35 /* SessionRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 959878B4816DD2617038A339 /* SessionRegistry.swift */; }; 734F2FECD358816F695D26CD /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DD6C03615573E339057EBF /* AppState.swift */; }; 7936EDE3DC79D02CF66F8863 /* QRScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AF0B5FBC3ACEC8EF5C3FF12 /* QRScannerView.swift */; }; 7BD37B4A99532FD542D21526 /* TerminalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75BE928FA90D8AF2C56615D /* TerminalViewController.swift */; }; @@ -29,9 +34,14 @@ 909A26B85FA298A870E407CD /* DeviceTokenRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ABB7E636E55917562D9A2C /* DeviceTokenRegistrar.swift */; }; 9855E1E1C856E20B339F2A0A /* NotificationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844A9C78194E7644A78FFA23 /* NotificationDelegate.swift */; }; 9AC28FD7FD38F250FE477441 /* ScrollbackCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98EF1F714A1A5E8D4C3DA2B /* ScrollbackCache.swift */; }; + 9BDEBAD2C295FDD53946FB8C /* ModifierBarUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E689590D75F152E90467016 /* ModifierBarUITests.swift */; }; + 9E4B31AA31D8A900D36918A3 /* FaceIDGate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87407DA9C464BA9C9E118308 /* FaceIDGate.swift */; }; + A1527F94754A9205576C317C /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1F7B03B47D7766D72BA3B8 /* Helpers.swift */; }; A1B807C3E8586E99507463B9 /* FrameCodecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A85D5F5AF59E84DDC3AE168B /* FrameCodecTests.swift */; }; A3144EA79E01CB9D2DD552C8 /* PairingFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A38B86E930CB34DEB9E4C144 /* PairingFlowView.swift */; }; + A51AABE190B66EE994D8415B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BCDE8061BF3E2DABEA12E7 /* SettingsView.swift */; }; A56F82D6CEC7C6654C02C7BB /* WebSocketClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5205F823929F91450C58D4CA /* WebSocketClient.swift */; }; + AEAB8079223530B0437F3434 /* PairingUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEEC5B3171A29F91F7D9B67E /* PairingUITests.swift */; }; AF1F7740D9A9F40BA8308052 /* TerminalViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39536FD31585716EF30C84C6 /* TerminalViewRepresentable.swift */; }; B3809456CF2E96F1B1B862C2 /* FontStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12767F24EC6ECFA77B280A8D /* FontStore.swift */; }; B73AD1B4B8830C1DEE8A78AE /* ModifierStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 278215F3FD64C681C55F23A4 /* ModifierStateTests.swift */; }; @@ -39,16 +49,28 @@ C1F266B0DC9D7029E5E5B203 /* DeviceTokenRegistrarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83446A0D895B866E880D4F2D /* DeviceTokenRegistrarTests.swift */; }; C776D609DB29E5B4C90881F9 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B772854E3FADA8998C93DAF5 /* Keychain.swift */; }; C823749124F98D46FB993247 /* KeychainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47CA5A1045A264958B360BF /* KeychainTests.swift */; }; + C87E6CCFC4ADC4AC4CAC4C0D /* SessionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3C69D7879985E77A45DE76 /* SessionRow.swift */; }; D3E1C0562B97260D28FF1C11 /* Pairing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F544C25D53F52291E2FDB6F /* Pairing.swift */; }; D3E8D6064F38E4024A6863C9 /* TerminalTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3A7FB4B9C4D2B63B016E11A /* TerminalTheme.swift */; }; D77D662C3311D9646BE57596 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6BDDFB0C0D1D6D6FB490BA8D /* Preview Assets.xcassets */; }; + D8F05ED47CD1A4A8298EDDFB /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C66962D8B5B6869B60F8101 /* StatusBar.swift */; }; E9126D5D059DAD3717FA2398 /* ModifierBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F553E905D716538D9DA442E7 /* ModifierBar.swift */; }; + EAF7C85B3223C503829028D3 /* SettingsUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96A48B8A34A0647A8BF12B55 /* SettingsUITests.swift */; }; + EF47351AD35418E508ED33C9 /* SessionSwitcherUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED280CEF6D334529433F20A3 /* SessionSwitcherUITests.swift */; }; + F10312EF88278BB719CCF7E7 /* StatusBarUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D30D90D7888D84A2EF1B22EA /* StatusBarUITests.swift */; }; F6C311D17A8DAA4F19464E25 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 188683139B863ED1AC03A1BB /* ContentView.swift */; }; F8CBA52AE2CC3D8496361D45 /* TerminalFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B05BBDD469F51657ED89B0 /* TerminalFont.swift */; }; FADABBF0D0229D84832D3B78 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = D095700C52C60FDA2CB38679 /* SwiftTerm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 4BC4CAFF5DFB43FCB5A72089 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B5A2356AA5371FBA25136FA6 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4910ACCEB67B73CBA3440774; + remoteInfo = piRemote; + }; B301DDFED8092F66145718E3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B5A2356AA5371FBA25136FA6 /* Project object */; @@ -61,18 +83,22 @@ /* Begin PBXFileReference section */ 03ABB7E636E55917562D9A2C /* DeviceTokenRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTokenRegistrar.swift; sourceTree = ""; }; 0AF0B5FBC3ACEC8EF5C3FF12 /* QRScannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerView.swift; sourceTree = ""; }; + 0C66962D8B5B6869B60F8101 /* StatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; 0E401DECD467A1D3AB030610 /* piRemote.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = piRemote.entitlements; sourceTree = ""; }; 0F544C25D53F52291E2FDB6F /* Pairing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pairing.swift; sourceTree = ""; }; 12767F24EC6ECFA77B280A8D /* FontStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontStore.swift; sourceTree = ""; }; 15B6B497329B98A4508D963B /* FrameCodec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameCodec.swift; sourceTree = ""; }; 188683139B863ED1AC03A1BB /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 1E689590D75F152E90467016 /* ModifierBarUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierBarUITests.swift; sourceTree = ""; }; 22658EED98A0B3C2183AACDD /* ScrollbackCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollbackCacheTests.swift; sourceTree = ""; }; 278215F3FD64C681C55F23A4 /* ModifierStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierStateTests.swift; sourceTree = ""; }; + 2C3C69D7879985E77A45DE76 /* SessionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRow.swift; sourceTree = ""; }; 2E2370A3190FDC144C822FF6 /* piRemote.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = piRemote.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39536FD31585716EF30C84C6 /* TerminalViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalViewRepresentable.swift; sourceTree = ""; }; 3D4E4BB86FBFBD80287048C1 /* MainTerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTerminalView.swift; sourceTree = ""; }; 5205F823929F91450C58D4CA /* WebSocketClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketClient.swift; sourceTree = ""; }; 55DAE4BC86AE950146CD7B94 /* REVIEW_NOTES_2.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = REVIEW_NOTES_2.md; sourceTree = ""; }; + 5C1F7B03B47D7766D72BA3B8 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; 62DD6C03615573E339057EBF /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; 658CB2FCA96A8913B1753B1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 67F95D26CD899B18D07AB0B2 /* SessionConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionConnection.swift; sourceTree = ""; }; @@ -82,7 +108,12 @@ 7607FF3804A2602B1C6A05D4 /* ResumeCursorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeCursorTests.swift; sourceTree = ""; }; 83446A0D895B866E880D4F2D /* DeviceTokenRegistrarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTokenRegistrarTests.swift; sourceTree = ""; }; 844A9C78194E7644A78FFA23 /* NotificationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDelegate.swift; sourceTree = ""; }; + 87407DA9C464BA9C9E118308 /* FaceIDGate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceIDGate.swift; sourceTree = ""; }; + 94525E2A804BF64F12806D4E /* LockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockView.swift; sourceTree = ""; }; + 959878B4816DD2617038A339 /* SessionRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRegistry.swift; sourceTree = ""; }; + 96A48B8A34A0647A8BF12B55 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = ""; }; 99FA0A0FD737901834AD5705 /* ThemeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeTests.swift; sourceTree = ""; }; + 9E41A5D34EADEA9D2925DDF0 /* piRemoteUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = piRemoteUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A38B86E930CB34DEB9E4C144 /* PairingFlowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingFlowView.swift; sourceTree = ""; }; A3F776605A4109C047E44A89 /* PasteSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteSheet.swift; sourceTree = ""; }; A75BE928FA90D8AF2C56615D /* TerminalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalViewController.swift; sourceTree = ""; }; @@ -90,14 +121,21 @@ AFF032BC30D513204211ADA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B772854E3FADA8998C93DAF5 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; }; BC48E39D19238178A180B30C /* ThemeStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeStore.swift; sourceTree = ""; }; + BEEC5B3171A29F91F7D9B67E /* PairingUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingUITests.swift; sourceTree = ""; }; C5B05BBDD469F51657ED89B0 /* TerminalFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalFont.swift; sourceTree = ""; }; + C5BCDE8061BF3E2DABEA12E7 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; C7961BE126AFEEE4B7AA6621 /* ResumeCursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeCursor.swift; sourceTree = ""; }; C98EF1F714A1A5E8D4C3DA2B /* ScrollbackCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollbackCache.swift; sourceTree = ""; }; CD24C7095F23AF63CCFB23F0 /* piRemoteTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = piRemoteTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D30D90D7888D84A2EF1B22EA /* StatusBarUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarUITests.swift; sourceTree = ""; }; D3FCCEE1BAA0983D83FC84DD /* SidecarCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidecarCredential.swift; sourceTree = ""; }; DD8434E1D87FFE2616683652 /* ModifierState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierState.swift; sourceTree = ""; }; + DF52ADD110456E368EAE217C /* LockScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenUITests.swift; sourceTree = ""; }; E3A7FB4B9C4D2B63B016E11A /* TerminalTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalTheme.swift; sourceTree = ""; }; + E8C77FC9D24BB5F8DA96386C /* SessionSwitcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionSwitcher.swift; sourceTree = ""; }; E9BAF4FBE6CC23FDD9B40040 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = ""; }; + ED280CEF6D334529433F20A3 /* SessionSwitcherUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionSwitcherUITests.swift; sourceTree = ""; }; + EE5E1A0BE69AF1FEF73E373F /* SmokeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmokeUITests.swift; sourceTree = ""; }; F47CA5A1045A264958B360BF /* KeychainTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainTests.swift; sourceTree = ""; }; F553E905D716538D9DA442E7 /* ModifierBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifierBar.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -120,6 +158,7 @@ children = ( 2E2370A3190FDC144C822FF6 /* piRemote.app */, CD24C7095F23AF63CCFB23F0 /* piRemoteTests.xctest */, + 9E41A5D34EADEA9D2925DDF0 /* piRemoteUITests.xctest */, ); name = Products; sourceTree = ""; @@ -128,6 +167,7 @@ isa = PBXGroup; children = ( 67F95D26CD899B18D07AB0B2 /* SessionConnection.swift */, + 959878B4816DD2617038A339 /* SessionRegistry.swift */, ); path = Sessions; sourceTree = ""; @@ -144,6 +184,15 @@ path = Core; sourceTree = ""; }; + 35F24B7F065B257F93810E5B /* Sessions */ = { + isa = PBXGroup; + children = ( + 2C3C69D7879985E77A45DE76 /* SessionRow.swift */, + E8C77FC9D24BB5F8DA96386C /* SessionSwitcher.swift */, + ); + path = Sessions; + sourceTree = ""; + }; 49209A78102230A37C0FF8D0 /* Terminal */ = { isa = PBXGroup; children = ( @@ -197,6 +246,7 @@ children = ( C8D95B3C16FEE9C9FBE38FDE /* Sources */, 69990A9885FB5B354E73AB90 /* Tests */, + B259358DD628B98EC61A6736 /* UITests */, 161D89B853288EC766A0767D /* Products */, ); sourceTree = ""; @@ -211,6 +261,21 @@ path = Input; sourceTree = ""; }; + B259358DD628B98EC61A6736 /* UITests */ = { + isa = PBXGroup; + children = ( + 5C1F7B03B47D7766D72BA3B8 /* Helpers.swift */, + DF52ADD110456E368EAE217C /* LockScreenUITests.swift */, + 1E689590D75F152E90467016 /* ModifierBarUITests.swift */, + BEEC5B3171A29F91F7D9B67E /* PairingUITests.swift */, + ED280CEF6D334529433F20A3 /* SessionSwitcherUITests.swift */, + 96A48B8A34A0647A8BF12B55 /* SettingsUITests.swift */, + EE5E1A0BE69AF1FEF73E373F /* SmokeUITests.swift */, + D30D90D7888D84A2EF1B22EA /* StatusBarUITests.swift */, + ); + path = UITests; + sourceTree = ""; + }; C06242078CD1DD2BD7C7A4FA /* CoreTests */ = { isa = PBXGroup; children = ( @@ -228,6 +293,14 @@ path = CoreTests; sourceTree = ""; }; + C7FBB3C467939760D2971070 /* Status */ = { + isa = PBXGroup; + children = ( + 0C66962D8B5B6869B60F8101 /* StatusBar.swift */, + ); + path = Status; + sourceTree = ""; + }; C8D95B3C16FEE9C9FBE38FDE /* Sources */ = { isa = PBXGroup; children = ( @@ -261,6 +334,9 @@ children = ( 9DF960DFB90BF425282C35D0 /* Input */, 8A477F7D38B42EEB3F70323F /* Pairing */, + 35F24B7F065B257F93810E5B /* Sessions */, + F681ED5F43C5283558361FAC /* Settings */, + C7FBB3C467939760D2971070 /* Status */, 49209A78102230A37C0FF8D0 /* Terminal */, ); path = UI; @@ -283,6 +359,7 @@ ED7AFC5C0EF365C5831C7245 /* Auth */ = { isa = PBXGroup; children = ( + 87407DA9C464BA9C9E118308 /* FaceIDGate.swift */, B772854E3FADA8998C93DAF5 /* Keychain.swift */, 0F544C25D53F52291E2FDB6F /* Pairing.swift */, D3FCCEE1BAA0983D83FC84DD /* SidecarCredential.swift */, @@ -290,9 +367,36 @@ path = Auth; sourceTree = ""; }; + F681ED5F43C5283558361FAC /* Settings */ = { + isa = PBXGroup; + children = ( + 94525E2A804BF64F12806D4E /* LockView.swift */, + C5BCDE8061BF3E2DABEA12E7 /* SettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 2B14D5AC9C0B9175642D6460 /* piRemoteUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FAF190D5257A3BA4C5F939C3 /* Build configuration list for PBXNativeTarget "piRemoteUITests" */; + buildPhases = ( + 6588DBB02A562A96AA88EE1A /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + 94235F03119D91B660C7E290 /* PBXTargetDependency */, + ); + name = piRemoteUITests; + packageProductDependencies = ( + ); + productName = piRemoteUITests; + productReference = 9E41A5D34EADEA9D2925DDF0 /* piRemoteUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 2C3DD20A67B90DDE04FDEE41 /* piRemoteTests */ = { isa = PBXNativeTarget; buildConfigurationList = C553B125FB09A7C04D602AE2 /* Build configuration list for PBXNativeTarget "piRemoteTests" */; @@ -342,6 +446,11 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1640; TargetAttributes = { + 2B14D5AC9C0B9175642D6460 = { + DevelopmentTeam = KNXX8R3648; + ProvisioningStyle = Automatic; + TestTargetID = 4910ACCEB67B73CBA3440774; + }; 2C3DD20A67B90DDE04FDEE41 = { DevelopmentTeam = KNXX8R3648; ProvisioningStyle = Automatic; @@ -372,6 +481,7 @@ targets = ( 4910ACCEB67B73CBA3440774 /* piRemote */, 2C3DD20A67B90DDE04FDEE41 /* piRemoteTests */, + 2B14D5AC9C0B9175642D6460 /* piRemoteUITests */, ); }; /* End PBXProject section */ @@ -405,9 +515,11 @@ 734F2FECD358816F695D26CD /* AppState.swift in Sources */, F6C311D17A8DAA4F19464E25 /* ContentView.swift in Sources */, 909A26B85FA298A870E407CD /* DeviceTokenRegistrar.swift in Sources */, + 9E4B31AA31D8A900D36918A3 /* FaceIDGate.swift in Sources */, B3809456CF2E96F1B1B862C2 /* FontStore.swift in Sources */, 2AA3AC859917D32C1444FC5B /* FrameCodec.swift in Sources */, C776D609DB29E5B4C90881F9 /* Keychain.swift in Sources */, + 132F60783EC43AE420FA7CD5 /* LockView.swift in Sources */, 19E584DD72E8F6DE3AF4E77F /* MainTerminalView.swift in Sources */, E9126D5D059DAD3717FA2398 /* ModifierBar.swift in Sources */, B8800C5E81FBB0C3CE9C6E7D /* ModifierState.swift in Sources */, @@ -419,7 +531,12 @@ 09AC16350B4E83B71B05A9D5 /* ResumeCursor.swift in Sources */, 9AC28FD7FD38F250FE477441 /* ScrollbackCache.swift in Sources */, 1F353AB548615ECD7D241EF7 /* SessionConnection.swift in Sources */, + 6A52DBC5C2845ADA05B37A35 /* SessionRegistry.swift in Sources */, + C87E6CCFC4ADC4AC4CAC4C0D /* SessionRow.swift in Sources */, + 4334301CFE20E6B66BDE7D19 /* SessionSwitcher.swift in Sources */, + A51AABE190B66EE994D8415B /* SettingsView.swift in Sources */, 2D8C05476A83F5CAB9A55A11 /* SidecarCredential.swift in Sources */, + D8F05ED47CD1A4A8298EDDFB /* StatusBar.swift in Sources */, F8CBA52AE2CC3D8496361D45 /* TerminalFont.swift in Sources */, D3E8D6064F38E4024A6863C9 /* TerminalTheme.swift in Sources */, 7BD37B4A99532FD542D21526 /* TerminalViewController.swift in Sources */, @@ -430,6 +547,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 6588DBB02A562A96AA88EE1A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A1527F94754A9205576C317C /* Helpers.swift in Sources */, + 17F996CE1F5CF3FF12E5C1AB /* LockScreenUITests.swift in Sources */, + 9BDEBAD2C295FDD53946FB8C /* ModifierBarUITests.swift in Sources */, + AEAB8079223530B0437F3434 /* PairingUITests.swift in Sources */, + EF47351AD35418E508ED33C9 /* SessionSwitcherUITests.swift in Sources */, + EAF7C85B3223C503829028D3 /* SettingsUITests.swift in Sources */, + 0572AF0EFE02ABD12CD5C584 /* SmokeUITests.swift in Sources */, + F10312EF88278BB719CCF7E7 /* StatusBarUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 6DDDB771E08071591D668B0A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -453,6 +585,11 @@ target = 4910ACCEB67B73CBA3440774 /* piRemote */; targetProxy = B301DDFED8092F66145718E3 /* PBXContainerItemProxy */; }; + 94235F03119D91B660C7E290 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4910ACCEB67B73CBA3440774 /* piRemote */; + targetProxy = 4BC4CAFF5DFB43FCB5A72089 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -653,6 +790,40 @@ }; name = Release; }; + CB33989104A2C99094B6896C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "de.vpsj.pi-remote.uitests"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = piRemote; + }; + name = Debug; + }; + F837F7618AB4341D74A53209 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "de.vpsj.pi-remote.uitests"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = piRemote; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -683,6 +854,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + FAF190D5257A3BA4C5F939C3 /* Build configuration list for PBXNativeTarget "piRemoteUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CB33989104A2C99094B6896C /* Debug */, + F837F7618AB4341D74A53209 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/piRemote.xcodeproj/xcshareddata/xcschemes/piRemote.xcscheme b/piRemote.xcodeproj/xcshareddata/xcschemes/piRemote.xcscheme index a652022..3199951 100644 --- a/piRemote.xcodeproj/xcshareddata/xcschemes/piRemote.xcscheme +++ b/piRemote.xcodeproj/xcshareddata/xcschemes/piRemote.xcscheme @@ -50,6 +50,17 @@ ReferencedContainer = "container:piRemote.xcodeproj"> + + + +