import XCTest /// The read screen's comment thread, end to end: the fixture card's own posted comment renders /// read-only, the composer posts a new comment through the draft-then-rename bracket, and the /// context menu's Edit rewrites a posted body — each write asserted on disk, not just on screen. final class CardCommentsUITests: XCTestCase { @MainActor func testCommentsReadAddAndEdit() throws { let (app, root) = XCUIApplication.launchedWithFixtureBoard() app.navigateToFirstCard() // Read mode first: the fixture card carries exactly one posted comment (author "rzen", // dated, never edited), so the header's count and the author line are both assertable // before this test writes anything. let header = app.element(labelContaining: "Comments · 1") XCTAssertTrue( header.waitForExistence(timeout: XCUIApplication.uiTimeout), "the comments header never appeared with the fixture's own count" ) XCTAssertTrue( app.element(labelContaining: "rzen").exists, "the fixture comment's author line never rendered" ) // Post a new comment. The Add button sits at the very bottom of the scroll view, below // the fixture card's body and footer. let addButton = app.buttons["Add Comment"] app.scrollUntilHittable(addButton) addButton.tap() let postButton = app.navigationBars.buttons["Post"] XCTAssertTrue( postButton.waitForExistence(timeout: XCUIApplication.uiTimeout), "the composer sheet never appeared" ) let composer = app.textViews.firstMatch XCTAssertTrue( composer.waitForExistence(timeout: XCUIApplication.uiTimeout), "the composer's text editor never appeared" ) let sentinel = "Posted from the phone by UI test" composer.tap() composer.typeText(sentinel) postButton.tap() // The sheet dismisses only on the perform's success, and the perform awaits its own // reload — so by the time the thread re-reads, both the screen and the disk carry it. XCTAssertTrue( app.element(labelContaining: "Comments · 2").waitForExistence(timeout: XCUIApplication.uiTimeout), "the header never counted the posted comment" ) XCTAssertTrue( app.staticTexts[sentinel].waitForExistence(timeout: XCUIApplication.uiTimeout), "the posted comment's body never rendered in the thread" ) XCTAssertTrue( waitForFile(under: root, containing: sentinel), "the posted comment never landed on disk under \(root.path)" ) // Edit the comment just posted, through its row's context menu. let postedBody = app.staticTexts[sentinel] postedBody.press(forDuration: 1.0) let editAction = app.buttons["Edit Comment"] XCTAssertTrue( editAction.waitForExistence(timeout: XCUIApplication.uiTimeout), "the context menu's Edit Comment action never appeared" ) editAction.tap() let saveButton = app.navigationBars.buttons["Save"] XCTAssertTrue( saveButton.waitForExistence(timeout: XCUIApplication.uiTimeout), "the edit sheet never appeared" ) let editor = app.textViews.firstMatch XCTAssertEqual( editor.value as? String, sentinel, "the edit sheet did not seed with the posted comment's body" ) let rewritten = "Rewritten from the phone by UI test" editor.replaceAllText(with: rewritten) saveButton.tap() XCTAssertTrue( app.staticTexts[rewritten].waitForExistence(timeout: XCUIApplication.uiTimeout), "the rewritten comment never rendered in the thread" ) XCTAssertTrue( waitForFile(under: root, containing: rewritten), "the rewritten comment never landed on disk under \(root.path)" ) XCTAssertTrue( fileDoesNotContain(under: root, substring: sentinel), "the pre-edit body survived on disk — the edit did not replace the body span" ) // The edited indicator is `modified` differing from `created` — the save just made that // true, and the author line renders it as a trailing "· edited". XCTAssertTrue( app.element(labelContaining: "· edited").waitForExistence(timeout: XCUIApplication.uiTimeout), "the edited marker never appeared on the rewritten comment's author line" ) } }