import XCTest final class ScreensUITests: XCTestCase { override func setUp() { continueAfterFailure = false } @MainActor func testLayoutFillsScreenAndCoreFlows() { let app = XCUIApplication() app.launch() // ---- Top chrome is present and flush with the safe area top. let title = app.staticTexts["Screens"] XCTAssertTrue(title.waitForExistence(timeout: 5), "Title should appear") let screenFrame = app.windows.firstMatch.frame let titleY = title.frame.midY XCTAssertLessThan(titleY, screenFrame.height * 0.25, "Title should sit in the top 25% of the screen; actual Y \(titleY) in screen \(screenFrame.height)") // ---- Tap + to open Add Connection sheet. let addButton = app.buttons["Add connection"] XCTAssertTrue(addButton.exists, "Add button should exist") addButton.tap() let displayNameField = app.textFields["Display name"] XCTAssertTrue(displayNameField.waitForExistence(timeout: 2), "Add Connection sheet should present and show Display name field") app.buttons["Cancel"].tap() XCTAssertFalse(displayNameField.waitForExistence(timeout: 1), "Add sheet should dismiss on Cancel") // ---- Settings gear opens Settings sheet. app.buttons["Settings"].tap() let settingsTitle = app.navigationBars.staticTexts["Settings"] XCTAssertTrue(settingsTitle.waitForExistence(timeout: 2), "Settings sheet should present") app.buttons["Done"].tap() // ---- Search field accepts input and clears. let search = app.textFields.matching(NSPredicate(format: "placeholderValue == %@", "Search connections")).firstMatch XCTAssertTrue(search.waitForExistence(timeout: 2), "Search field should exist") search.tap() search.typeText("mini") XCTAssertEqual(search.value as? String, "mini", "Search text should round-trip") XCTAssertTrue(title.isHittable, "Title should remain on screen during search") // ---- Empty-state CTA routes to Add Connection. let emptyCTA = app.buttons["Add a computer"] if emptyCTA.waitForExistence(timeout: 1) { app.buttons["Clear search"].tap() emptyCTA.tap() XCTAssertTrue(displayNameField.waitForExistence(timeout: 2), "Empty-state CTA should present Add Connection sheet") app.buttons["Cancel"].tap() } } /// Adds a connection with an unreachable host so the SessionView opens /// (controller exists, no real network needed) and verifies that tapping /// the keyboard icon presents the iOS keyboard and that pressing keys /// flows through the framebuffer view's input handling. @MainActor func testSoftwareKeyboardSendsCharactersToFramebuffer() { let app = XCUIApplication() app.launch() // Add a bogus connection app.buttons["Add connection"].tap() let nameField = app.textFields["Display name"] XCTAssertTrue(nameField.waitForExistence(timeout: 2)) nameField.tap() nameField.typeText("KeyboardTest") let hostField = app.textFields["Host or IP"] hostField.tap() hostField.typeText("127.0.0.1") app.buttons["Add"].tap() // Open the connection app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "KeyboardTest")).firstMatch.tap() // Framebuffer exists (as a TextView once UIKeyInput is adopted) let framebuffer = app.descendants(matching: .any) .matching(identifier: "framebuffer").firstMatch XCTAssertTrue(framebuffer.waitForExistence(timeout: 5), "Framebuffer view should exist in session") // The diagnostic probe is a hidden sibling element that records // keyboard plumbing events via its accessibilityLabel. let diag = app.descendants(matching: .any) .matching(identifier: "fb-diag").firstMatch XCTAssertTrue(diag.waitForExistence(timeout: 3), "Diagnostic probe should exist") // State probe let state = app.descendants(matching: .any) .matching(identifier: "sessionview-state").firstMatch XCTAssertTrue(state.waitForExistence(timeout: 3)) XCTAssertEqual(state.label, "kb=false", "Initial binding should be false") // Tap the keyboard toggle in the toolbar let kbToggle = app.buttons["Toggle keyboard bar"] XCTAssertTrue(kbToggle.waitForExistence(timeout: 2)) kbToggle.tap() // Verify the binding flipped let flippedTrue = NSPredicate(format: "label == %@", "kb=true") wait(for: [expectation(for: flippedTrue, evaluatedWith: state)], timeout: 3) // Wait for the diagnostic to record that we asked for the keyboard. // The framebuffer was already first responder (for hardware keys), so // we hit the reload path rather than `became:true`, which is fine. let askedForKeyboard = NSPredicate(format: "label CONTAINS %@", "[set:true]") wait(for: [expectation(for: askedForKeyboard, evaluatedWith: diag)], timeout: 3) // Force the on-screen keyboard to appear (simulator suppresses it // when Connect Hardware Keyboard is on). // The simulator can be told to disconnect the host keyboard via the // hardware menu; we instead just wait briefly and then attempt taps. Thread.sleep(forTimeInterval: 0.5) // If the system keyboard is visible, drive it like a user. Otherwise // fall back to typeText (which bypasses the UI keyboard). let kb = app.keyboards.firstMatch if kb.waitForExistence(timeout: 1) { kb.keys["h"].tap() kb.keys["i"].tap() } else { app.typeText("hi") } let typedH = NSPredicate(format: "label CONTAINS %@", "[ins:h]") wait(for: [expectation(for: typedH, evaluatedWith: diag)], timeout: 3) let typedI = NSPredicate(format: "label CONTAINS %@", "[ins:i]") wait(for: [expectation(for: typedI, evaluatedWith: diag)], timeout: 3) } }