Drives the test through app.keyboards.firstMatch.keys[…].tap() when the
on-screen keyboard is up, which mirrors actual user input through the
UIKey pipeline. Falls back to app.typeText only when the simulator
suppresses the soft keyboard via Connect Hardware Keyboard.
Disable Connect Hardware Keyboard in the host's iphonesimulator defaults
so the soft keyboard stays visible during the test. With both verified,
this commit guarantees that pressing 'h' on the iOS keyboard reaches
FramebufferUIView.insertText("h").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
142 lines
6.2 KiB
Swift
142 lines
6.2 KiB
Swift
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)
|
|
}
|
|
}
|