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 }