import XCTest /// "Jump to latest" has to bring back the newest message after fast flings. XCUITest waits /// for the list to settle before each tap, so a tap during momentum can't be simulated here; /// these cover what broke it: the button vanishing mid-fling and long jumps landing short. @MainActor final class TranscriptJumpUITests: XCTestCase { private func launchLongDemo() -> XCUIApplication { continueAfterFailure = false let app = XCUIApplication() // Launch arguments land in UserDefaults' argument domain: skip setup, stay on the transcript. app.launchArguments = ["-echo.demoLong"] + XCUIApplication.chatsSection + XCUIApplication.straightToChat app.launch() XCTAssertTrue(app.staticTexts["End of the long demo."].waitForExistence(timeout: 15), "The long demo didn't open at its end") return app } /// Opened from a reply's notification: at the reply's first line, not its end (GitHub #63), /// with Jump to latest for the rest. func testOpenedFromAReplyStartsAtItsTop() { continueAfterFailure = false let app = XCUIApplication() app.launchArguments = ["-echo.demoHeavy", "3", "-echo.fromReply"] + XCUIApplication.chatsSection + XCUIApplication.straightToChat app.launch() let jump = app.buttons["Jump to the newest message"] XCTAssertTrue(jump.waitForExistence(timeout: 15), "It opened at the end of the reply") let asked = app.staticTexts.matching(NSPredicate(format: "label BEGINSWITH 'Turn 3'")).firstMatch XCTAssertTrue(asked.exists && asked.frame.maxY < app.frame.midY, "The question the reply answers isn't just above it") } func testJumpToLatestReachesTheEndAtRest() { let app = launchLongDemo() let end = app.staticTexts["End of the long demo."] let transcript = app.scrollViews.firstMatch transcript.swipeDown(velocity: .fast) transcript.swipeDown(velocity: .fast) let jump = app.buttons["Jump to the newest message"] XCTAssertTrue(jump.waitForExistence(timeout: 5), "No Jump to latest after scrolling up") jump.tap() XCTAssertTrue(end.waitForExistence(timeout: 5), "The arrow didn't bring back the newest message") XCTAssertTrue(end.isHittable, "The newest message exists but isn't on screen") } func testJumpToLatestReachesTheEndAfterLongFlings() { let app = launchLongDemo() let end = app.staticTexts["End of the long demo."] let transcript = app.scrollViews.firstMatch // Read back up the transcript so the arrow appears. transcript.swipeDown(velocity: .fast) transcript.swipeDown(velocity: .fast) let jump = app.buttons["Jump to the newest message"] XCTAssertTrue(jump.waitForExistence(timeout: 5), "No Jump to latest after scrolling up") // A screen point captured earlier, so the tap goes where the arrow was even if it vanished. let frame = jump.frame let target = app.coordinate(withNormalizedOffset: .zero).withOffset(CGVector(dx: frame.midX, dy: frame.midY)) // Fling far up the transcript, then hit the arrow. transcript.swipeDown(velocity: 3000) target.tap() XCTAssertTrue(end.waitForExistence(timeout: 5), "The arrow didn't bring back the newest message") XCTAssertTrue(end.isHittable, "The newest message exists but isn't on screen") } /// Scrolled back to the end by hand, the pill has nothing left to offer and goes. func testJumpToLatestGoesOnceTheEndIsReachedByHand() { let app = launchLongDemo() let end = app.staticTexts["End of the long demo."] let transcript = app.scrollViews.firstMatch transcript.swipeDown(velocity: .fast) let jump = app.buttons["Jump to the newest message"] XCTAssertTrue(jump.waitForExistence(timeout: 5), "No Jump to latest after scrolling up") for _ in 0 ..< 6 where !end.isHittable { transcript.swipeUp(velocity: .fast) } XCTAssertTrue(end.isHittable, "Swiping didn't reach the end") XCTAssertTrue(jump.waitForNonExistence(timeout: 3), "Jump to latest is still there at the end") // A touch on the transcript at its end doesn't bring it back. transcript.swipeUp(velocity: .slow) XCTAssertFalse(jump.exists, "A swipe at the end brought Jump to latest back") } }