The phone gets its first witnesses — KanbanMobileUITests drive the MVP against a fixture board
The mobile app's first runtime coverage, and its first runtime, full stop: the suite launches the real app over a scratch directory seeded from rich-board.kanban via LANEWORK_LOCAL_ROOT, walks boards to lanes to cards to the card editor, retitles a card and polls the frontmatter on disk until the write lands, then taps through the Settings About section to see the changelog and license actually render. No iCloud account, no metadata query — the DEBUG override is the whole harness. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import XCTest
|
||||
|
||||
/// Board list → lanes → cards → card detail, then an edit that has to reach disk — the one
|
||||
/// end-to-end walk of the mobile MVP's navigation stack (`BoardRoute`'s three destinations).
|
||||
final class BoardsNavigationUITests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testBoardsNavigationAndTitleEdit() throws {
|
||||
let (app, root) = XCUIApplication.launchedWithFixtureBoard()
|
||||
|
||||
// Boards is the first tab (`RootTabView`), so no tab switch is needed here. The row's
|
||||
// label is the flattened `BoardSummaryRow` — title plus the "N lanes · N cards · modified"
|
||||
// subtitle `BoardIndexStore`'s first scan fills in.
|
||||
let boardRow = app.element(labelContaining: RichBoard.title)
|
||||
XCTAssertTrue(
|
||||
boardRow.waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"the \"\(RichBoard.title)\" row never appeared — check the fixture copy or the first scan"
|
||||
)
|
||||
boardRow.tap()
|
||||
|
||||
let laneRow = app.element(labelContaining: RichBoard.firstLane)
|
||||
XCTAssertTrue(
|
||||
laneRow.waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"the \"\(RichBoard.firstLane)\" lane row never appeared"
|
||||
)
|
||||
laneRow.tap()
|
||||
|
||||
let cardRow = app.element(labelContaining: RichBoard.firstLaneFirstCard)
|
||||
XCTAssertTrue(
|
||||
cardRow.waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"the \"\(RichBoard.firstLaneFirstCard)\" card row never appeared"
|
||||
)
|
||||
cardRow.tap()
|
||||
|
||||
// CardDetailScreen: the only `textField` on this screen is the title
|
||||
// (`Section("Title")`) — the body is a `TextEditor`, which is a `textView`.
|
||||
let titleField = app.textFields.firstMatch
|
||||
XCTAssertTrue(
|
||||
titleField.waitForExistence(timeout: XCUIApplication.uiTimeout),
|
||||
"CardDetailScreen's title field never appeared"
|
||||
)
|
||||
XCTAssertEqual(
|
||||
titleField.value as? String, RichBoard.firstLaneFirstCard,
|
||||
"the title field did not seed with the fixture card's own title"
|
||||
)
|
||||
|
||||
let sentinel = "Retitled by UI test"
|
||||
titleField.tap()
|
||||
// Select-all via a hardware-keyboard shortcut (iOS answers ⌘A the same as macOS when a
|
||||
// keyboard is attached, which the Simulator always presents one as) rather than a
|
||||
// backspace-per-character workaround, whose delete count only works if the tap happened
|
||||
// to land the cursor at the end of the existing text.
|
||||
titleField.typeKey("a", modifierFlags: .command)
|
||||
titleField.typeText(sentinel)
|
||||
|
||||
// Commit the way the screen commits (`CardDetailScreen.body`'s `.confirmationAction`):
|
||||
// the Done button folds `commitAll()` in and drops focus, without navigating back.
|
||||
app.navigationBars.buttons["Done"].tap()
|
||||
|
||||
XCTAssertTrue(
|
||||
waitForFile(under: root, containing: sentinel),
|
||||
"the retitled card never landed on disk under \(root.path)"
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user