import XCTest /// Task create/read/update/delete UI tests. /// /// Merged from the former `Suite5_TaskTests` and `Tests/TaskIntegrationTests`. /// Per-test isolation is provided by `AuthenticatedUITestCase`: every test mints /// a fresh account, logs in, and tears it down. Task creation gates on a residence /// existing, so `requiresResidence` seeds one BEFORE login (the fresh account is /// otherwise empty and the Add-Task button would stay disabled). /// /// Tests that must SEE a pre-existing task (uncancel flows) seed that task in /// `seedAccountPreconditions` so the app loads it on its post-login fetch. final class TaskCRUDUITests: AuthenticatedUITestCase { // Task creation gates on a residence existing; seed one before login so the // fresh account's app sees it (otherwise the Add-Task button stays disabled). override var requiresResidence: Bool { true } // MARK: - Preconditions /// Cancelled task seeded before login for the uncancel flows. A fresh account /// is empty at login, so a task seeded in the test body would be invisible to /// the app without a manual refresh — seed it here instead. private(set) var seededCancelledTask_uncancelFlow: TestTask? private(set) var seededCancelledTask_uncancelV2: TestTask? override func seedAccountPreconditions(_ account: TestAccount) { super.seedAccountPreconditions(account) // seeds seededResidence (requiresResidence) // A residence MUST exist before we can seed the cancelled tasks. The base // populates `seededResidence` when `requiresResidence` is true, but rather // than early-returning (and silently skipping the cancelled-task seeding — // which then makes the uncancel tests SKIP instead of run), guarantee one // here: fall back to seeding a residence directly if it's somehow nil. let residence = seededResidence ?? account.seedResidence(name: "Precondition Home") // TASK-010: a cancelled task that the test will uncancel/reopen. // createCancelledTask is non-optional — it XCTFails (and crashes) on a real // API failure, so a genuine break surfaces as a failure, never a silent skip. seededCancelledTask_uncancelFlow = TestDataSeeder.createCancelledTask( token: account.token, residenceId: residence.id ) // TASK-010 (v2): a named task, cancelled, that the test restores. let v2Task = account.seedTask( residenceId: residence.id, title: "Uncancel Me \(Int(Date().timeIntervalSince1970))" ) seededCancelledTask_uncancelV2 = TestAccountAPIClient.cancelTask(token: account.token, id: v2Task.id) ?? v2Task } /// Try to bring a task card into view on the Tasks Kanban by its title. /// /// Refreshes via the toolbar button (the Kanban has no pull-to-refresh) and /// swipes the board horizontally, returning the static-text element for the /// card. NOTE: the backend intentionally HIDES cancelled and archived tasks /// from `GET /tasks/` (the board's only data source — see the API's /// `determineExpectedColumn`: cancelled/archived return "" = hidden). So a /// seeded *cancelled* task will never surface here; callers must handle the /// not-found case explicitly. @discardableResult private func revealKanbanTask(titled title: String, maxSwipes: Int = 6) -> XCUIElement { let taskText = app.staticTexts[title] refreshTasks() if taskText.waitForExistence(timeout: defaultTimeout) { return taskText } let board = app.scrollViews.firstMatch.exists ? app.scrollViews.firstMatch : app.collectionViews.firstMatch for _ in 0..