listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

ListlessiOSUITests.swift (5455B)


      1 import XCTest
      2 
      3 final class ListlessiOSUITests: XCTestCase {
      4     var app: XCUIApplication!
      5 
      6     override func setUpWithError() throws {
      7         continueAfterFailure = false
      8         app = XCUIApplication()
      9         app.launchArguments = ["UI_TESTING"]
     10         app.launch()
     11     }
     12 
     13     override func tearDownWithError() throws {
     14         app.terminate()
     15     }
     16 
     17     // MARK: - Helpers
     18 
     19     /// The "Pull down to create" empty state label.
     20     var emptyStateLabel: XCUIElement {
     21         app.staticTexts["Pull down to create"]
     22     }
     23 
     24     /// The main scroll view area; tapping empty space here creates a draft item.
     25     var itemListScrollView: XCUIElement {
     26         app.scrollViews["item-list-scrollview"]
     27     }
     28 
     29     /// The draft row text view that appears after tapping empty space.
     30     /// TappableTextField wraps UITextView, so XCUITest sees it as a textView.
     31     var draftTextField: XCUIElement {
     32         app.textViews.matching(identifier: "draft-row-append").firstMatch
     33     }
     34 
     35     /// Returns the static text for a committed item with the given title.
     36     func itemText(_ title: String) -> XCUIElement {
     37         app.staticTexts.matching(
     38             NSPredicate(format: "identifier BEGINSWITH 'item-text-' AND label == %@", title)
     39         ).firstMatch
     40     }
     41 
     42     /// Creates a item by tapping empty space to reveal the draft field,
     43     /// typing a title, and pressing Return.
     44     func createItem(_ title: String) {
     45         let textView = draftTextField
     46         if !textView.exists {
     47             itemListScrollView.tap()
     48             if !textView.waitForExistence(timeout: 2) {
     49                 XCTFail("Draft text view should appear after tapping empty space")
     50                 return
     51             }
     52         }
     53         textView.tap()
     54         textView.typeText(title + "\n")
     55     }
     56 
     57     /// Returns the Nth checkbox button (0-indexed).
     58     func itemCheckbox(at index: Int) -> XCUIElement {
     59         app.buttons.matching(identifier: "item-checkbox").element(boundBy: index)
     60     }
     61 
     62     /// Exits editing mode. After createItem, the new draft text view is
     63     /// focused. Dismiss it by tapping the scroll view background (which
     64     /// calls handleBackgroundTap to commit/dismiss the empty draft).
     65     func exitEditingMode() {
     66         let draft = draftTextField
     67         if draft.exists {
     68             draft.typeText("\n")
     69         }
     70         // Tap background to deselect
     71         itemListScrollView.tap()
     72     }
     73 
     74     // MARK: - Empty State
     75 
     76     func testLaunchShowsEmptyState() {
     77         XCTAssertTrue(
     78             emptyStateLabel.waitForExistence(timeout: 2),
     79             "Empty state label should be visible on launch"
     80         )
     81     }
     82 
     83     func testEmptyStateDisappearsAfterCreatingItem() {
     84         createItem("First item")
     85         exitEditingMode()
     86         XCTAssertFalse(emptyStateLabel.exists, "Empty state should disappear after creating a item")
     87     }
     88 
     89     // MARK: - Item Creation
     90 
     91     func testCreateItemViaTap() {
     92         createItem("Buy groceries")
     93         exitEditingMode()
     94         XCTAssertTrue(
     95             itemText("Buy groceries").waitForExistence(timeout: 2),
     96             "Item should appear with the typed title"
     97         )
     98     }
     99 
    100     func testReturnChainsNewItem() {
    101         createItem("First item")
    102         XCTAssertTrue(
    103             draftTextField.waitForExistence(timeout: 2),
    104             "New draft text view should appear after Return"
    105         )
    106     }
    107 
    108     func testCreateMultipleItems() {
    109         createItem("Alpha")
    110         createItem("Bravo")
    111         createItem("Charlie")
    112         exitEditingMode()
    113 
    114         XCTAssertTrue(itemText("Alpha").waitForExistence(timeout: 2))
    115         XCTAssertTrue(itemText("Bravo").exists)
    116         XCTAssertTrue(itemText("Charlie").exists)
    117     }
    118 
    119     func testEmptyItemDeletedOnCommit() {
    120         itemListScrollView.tap()
    121         XCTAssertTrue(draftTextField.waitForExistence(timeout: 2))
    122         draftTextField.typeText("\n")
    123         XCTAssertTrue(
    124             emptyStateLabel.waitForExistence(timeout: 2),
    125             "Empty state should reappear when empty item is discarded"
    126         )
    127     }
    128 
    129     // MARK: - Item Completion
    130 
    131     func testCompleteItemViaCheckbox() {
    132         createItem("Finish report")
    133         exitEditingMode()
    134 
    135         let checkbox = itemCheckbox(at: 0)
    136         XCTAssertTrue(checkbox.waitForExistence(timeout: 2))
    137         XCTAssertEqual(checkbox.value as? String, "circle")
    138         checkbox.tap()
    139 
    140         let completed = app.buttons.matching(
    141             NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
    142         ).firstMatch
    143         XCTAssertTrue(
    144             completed.waitForExistence(timeout: 3),
    145             "Checkbox should show checkmark after tapping"
    146         )
    147     }
    148 
    149     func testUncompleteItem() {
    150         createItem("Finish report")
    151         exitEditingMode()
    152         usleep(500_000)  // Wait for draft row reveal animation to settle
    153 
    154         itemCheckbox(at: 0).tap()
    155 
    156         let completed = app.buttons.matching(
    157             NSPredicate(format: "identifier == 'item-checkbox' AND value == 'checkmark.circle.fill'")
    158         ).firstMatch
    159         XCTAssertTrue(completed.waitForExistence(timeout: 3))
    160         completed.tap()
    161 
    162         let uncompleted = app.buttons.matching(
    163             NSPredicate(format: "identifier == 'item-checkbox' AND value == 'circle'")
    164         ).firstMatch
    165         XCTAssertTrue(
    166             uncompleted.waitForExistence(timeout: 3),
    167             "Checkbox should revert to circle after uncompleting"
    168         )
    169     }
    170 }