Add F-068, F-069, F-070 UI tests for custom itinerary item lifecycle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-19 23:49:29 -06:00
parent 8421b23f0c
commit 2d759274a8
5 changed files with 299 additions and 0 deletions

View File

@@ -80,6 +80,7 @@ struct QuickAddItemSheet: View {
.foregroundStyle(canSave ? Theme.warmOrange : Theme.textMuted(colorScheme))
.disabled(!canSave)
.accessibilityLabel(saveButtonAccessibilityLabel)
.accessibilityIdentifier("quickAdd.saveButton")
}
}
.sheet(isPresented: $showLocationSearch) {
@@ -137,6 +138,7 @@ struct QuickAddItemSheet: View {
)
.accessibilityLabel("Item description")
.accessibilityHint(placeholderText)
.accessibilityIdentifier("quickAdd.titleField")
// Character hint
if !title.isEmpty {

View File

@@ -1113,6 +1113,7 @@ final class ItineraryTableViewController: UITableViewController {
cell.backgroundColor = .clear
cell.selectionStyle = .default // Shows highlight on tap
cell.accessibilityIdentifier = "tripDetail.customItem"
}
}
@@ -1175,6 +1176,7 @@ struct DaySectionHeaderView: View {
.tint(Theme.warmOrange)
.accessibilityLabel("Add item to Day \(dayNumber)")
.accessibilityHint("Add restaurants, activities, or notes to this day")
.accessibilityIdentifier("tripDetail.addItemButton")
}
.padding(.horizontal, Theme.Spacing.lg)
.padding(.top, Theme.Spacing.lg) // More space above for section separation

View File

@@ -456,6 +456,54 @@ struct TripDetailScreen {
XCTAssertEqual(favoriteButton.label, expected,
"Favorite button label should reflect saved state")
}
// MARK: - Custom Item Elements
/// The "Add" button on any day header row (first match).
var addItemButton: XCUIElement {
app.buttons["tripDetail.addItemButton"].firstMatch
}
/// A custom item cell in the itinerary (first match).
var customItemCell: XCUIElement {
app.cells["tripDetail.customItem"].firstMatch
}
// MARK: - Custom Item Actions
/// Scrolls to and taps the first "Add" button on a day header.
func tapAddItem() {
let button = addItemButton
var scrollAttempts = 0
while !(button.exists && button.isHittable) && scrollAttempts < 15 {
app.swipeUp(velocity: .slow)
scrollAttempts += 1
}
button.waitUntilHittable().tap()
}
/// Taps a custom item cell to open the edit sheet.
func tapCustomItem() {
let cell = customItemCell
var scrollAttempts = 0
while !(cell.exists && cell.isHittable) && scrollAttempts < 15 {
app.swipeUp(velocity: .slow)
scrollAttempts += 1
}
cell.waitUntilHittable().tap()
}
/// Long-presses a custom item to show the context menu.
func longPressCustomItem() {
let cell = customItemCell
var scrollAttempts = 0
while !(cell.exists && cell.isHittable) && scrollAttempts < 15 {
app.swipeUp(velocity: .slow)
scrollAttempts += 1
}
cell.waitUntilHittable()
cell.press(forDuration: 1.0)
}
}
// MARK: - My Trips Screen
@@ -792,6 +840,68 @@ struct StadiumVisitSheetScreen {
}
}
// MARK: - Quick Add Item Sheet Screen
struct QuickAddItemSheetScreen {
let app: XCUIApplication
// MARK: Elements
var titleField: XCUIElement {
app.textFields["quickAdd.titleField"]
}
var saveButton: XCUIElement {
app.buttons["quickAdd.saveButton"]
}
var cancelButton: XCUIElement {
app.navigationBars.buttons["Cancel"]
}
// MARK: Actions
@discardableResult
func waitForLoad() -> QuickAddItemSheetScreen {
titleField.waitForExistenceOrFail(
timeout: BaseUITestCase.defaultTimeout,
"Quick add item sheet should appear with title field"
)
return self
}
/// Waits for the sheet to dismiss by checking the title field disappears.
func waitForDismiss() {
titleField.waitForNonExistence(
timeout: BaseUITestCase.defaultTimeout,
"Quick add item sheet should dismiss"
)
}
/// Types a title into the description field.
func typeTitle(_ text: String) {
titleField.waitUntilHittable().tap()
titleField.typeText(text)
}
/// Clears existing text and types a new title (for edit mode).
func clearAndTypeTitle(_ text: String) {
let field = titleField
field.waitUntilHittable().tap()
// Triple-tap to select all existing text
field.tap(withNumberOfTaps: 3, numberOfTouches: 1)
field.typeText(text)
}
func tapSave() {
saveButton.waitUntilHittable().tap()
}
func tapCancel() {
cancelButton.waitUntilHittable().tap()
}
}
// MARK: - Games History Screen
struct GamesHistoryScreen {
@@ -980,4 +1090,36 @@ enum TestFlows {
return (wizard, detail)
}
/// Plans a trip, saves it, navigates back to My Trips, and opens the saved trip.
/// Returns a TripDetailScreen with `allowCustomItems` enabled.
@MainActor @discardableResult
static func planSaveAndOpenFromMyTrips(
app: XCUIApplication
) -> TripDetailScreen {
let (wizard, detail) = planAndSelectFirstTrip(app: app)
// Save the trip
detail.assertSaveState(isSaved: false)
detail.tapFavorite()
detail.assertSaveState(isSaved: true)
// Navigate back: Detail Options Wizard Cancel
app.navigationBars.buttons.firstMatch.tap()
let optionsBackBtn = app.navigationBars.buttons.firstMatch
optionsBackBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap()
wizard.tapCancel()
// Switch to My Trips tab and open the saved trip
let home = HomeScreen(app: app)
home.switchToTab(home.myTripsTab)
let myTrips = MyTripsScreen(app: app)
myTrips.assertHasTrips()
myTrips.tapTrip(at: 0)
let savedDetail = TripDetailScreen(app: app)
savedDetail.waitForLoad()
return savedDetail
}
}

View File

@@ -0,0 +1,153 @@
//
// TripCustomItemTests.swift
// SportsTimeUITests
//
// Tests for custom itinerary item lifecycle: add, edit, delete.
// QA Sheet: F-068, F-069, F-070
//
import XCTest
final class TripCustomItemTests: BaseUITestCase {
// MARK: - Helpers
/// Plans a trip, saves it, and opens it from My Trips (enables custom items).
@MainActor
private func openSavedTripDetail() -> TripDetailScreen {
return TestFlows.planSaveAndOpenFromMyTrips(app: app)
}
/// Adds a custom item with the given title via the add sheet flow.
@MainActor
private func addCustomItem(detail: TripDetailScreen, title: String) {
detail.tapAddItem()
let addSheet = QuickAddItemSheetScreen(app: app)
addSheet.waitForLoad()
addSheet.typeTitle(title)
addSheet.tapSave()
// Wait for sheet to dismiss
addSheet.waitForDismiss()
}
// MARK: - F-068: Add custom item to day
/// F-068: Open saved trip tap Add on day header fill in title save item appears.
@MainActor
func testF068_AddCustomItemToDay() {
let detail = openSavedTripDetail()
// Tap "Add" on a day header
detail.tapAddItem()
let addSheet = QuickAddItemSheetScreen(app: app)
addSheet.waitForLoad()
// Save button should be disabled without a title
XCTAssertFalse(
addSheet.saveButton.isEnabled,
"Save button should be disabled without a title"
)
// Type a description
addSheet.typeTitle("Lunch at Pizzeria")
// Save button should now be enabled
XCTAssertTrue(
addSheet.saveButton.isEnabled,
"Save button should be enabled after entering a title"
)
addSheet.tapSave()
// Sheet should dismiss
addSheet.waitForDismiss()
// Custom item should appear in the itinerary
let customItem = detail.customItemCell
XCTAssertTrue(
customItem.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Custom item should appear in itinerary after adding"
)
captureScreenshot(named: "F068-CustomItemAdded")
}
// MARK: - F-069: Edit custom item
/// F-069: Add a custom item tap to edit change title save updated title visible.
@MainActor
func testF069_EditCustomItem() {
let detail = openSavedTripDetail()
// Add an item first
addCustomItem(detail: detail, title: "Original Title")
// Verify item exists
XCTAssertTrue(
detail.customItemCell.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Custom item should exist before editing"
)
// Tap the custom item to open edit sheet
detail.tapCustomItem()
let editSheet = QuickAddItemSheetScreen(app: app)
editSheet.waitForLoad()
// Clear and type new title
editSheet.clearAndTypeTitle("Updated Title")
editSheet.tapSave()
// Sheet should dismiss
editSheet.waitForDismiss()
// Verify updated title is visible
let updatedItem = app.staticTexts["Updated Title"]
XCTAssertTrue(
updatedItem.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Updated custom item title should be visible in itinerary"
)
captureScreenshot(named: "F069-CustomItemEdited")
}
// MARK: - F-070: Delete custom item
/// F-070: Add a custom item long-press tap Delete in context menu item removed.
@MainActor
func testF070_DeleteCustomItem() {
let detail = openSavedTripDetail()
// Add an item first
addCustomItem(detail: detail, title: "Item to Delete")
// Verify item exists
let customItem = detail.customItemCell
XCTAssertTrue(
customItem.waitForExistence(timeout: BaseUITestCase.defaultTimeout),
"Custom item should exist before deletion"
)
// Long-press to show context menu
detail.longPressCustomItem()
// Tap "Delete" in the context menu
let deleteButton = app.buttons["Delete"]
XCTAssertTrue(
deleteButton.waitForExistence(timeout: BaseUITestCase.shortTimeout),
"Delete button should appear in context menu"
)
deleteButton.tap()
// Custom item should disappear
customItem.waitForNonExistence(
timeout: BaseUITestCase.defaultTimeout,
"Custom item should be removed after deletion"
)
captureScreenshot(named: "F070-CustomItemDeleted")
}
}

Binary file not shown.