// // TripSavingTests.swift // SportsTimeUITests // // Tests the end-to-end trip saving flow: plan → select → save → verify in My Trips. // QA Sheet: F-060, F-062, F-064, F-065, F-077, F-078, F-079, F-080 // import XCTest final class TripSavingTests: BaseUITestCase { // MARK: - Helpers /// Plans a trip, saves it, navigates back to My Trips, and returns the screens. @MainActor private func planSaveAndReturnToMyTrips() -> (home: HomeScreen, myTrips: MyTripsScreen) { let (wizard, detail) = TestFlows.planAndSelectFirstTrip(app: app) detail.assertSaveState(isSaved: false) detail.tapFavorite() detail.assertSaveState(isSaved: true) // Navigate back: Detail → Options → Wizard → Cancel app.navigationBars.buttons.firstMatch.tap() let wizardBackBtn = app.navigationBars.buttons.firstMatch wizardBackBtn.waitUntilHittable(timeout: BaseUITestCase.shortTimeout).tap() wizard.tapCancel() let home = HomeScreen(app: app) home.switchToTab(home.myTripsTab) let myTrips = MyTripsScreen(app: app) return (home, myTrips) } // MARK: - Save Trip (F-043) /// F-043/F-044: Plans a trip, selects an option, saves it, and verifies it appears in My Trips. @MainActor func testF043_SaveTripAppearsInMyTrips() { let (_, myTrips) = planSaveAndReturnToMyTrips() myTrips.assertHasTrips() captureScreenshot(named: "F043-TripSaving-InMyTrips") } // MARK: - Save/Unsave Toggle (F-048, F-049) /// F-048: Save trip — unsaved trip shows "Save to favorites", tap changes to "Remove from favorites". @MainActor func testF048_SaveTrip() { let (_, detail) = TestFlows.planAndSelectFirstTrip(app: app) detail.assertSaveState(isSaved: false) detail.tapFavorite() detail.assertSaveState(isSaved: true) captureScreenshot(named: "F048-TripSaved") } /// F-049: Unsave trip — saved trip can be un-favorited by tapping again. @MainActor func testF049_UnsaveTrip() { let (_, detail) = TestFlows.planAndSelectFirstTrip(app: app) // Save first detail.assertSaveState(isSaved: false) detail.tapFavorite() detail.assertSaveState(isSaved: true) // Unsave detail.tapFavorite() detail.assertSaveState(isSaved: false) captureScreenshot(named: "F049-TripUnsaved") } // MARK: - Saved Trips List (F-059) /// F-059: Saved trips list shows trip card after saving. @MainActor func testF059_SavedTripsList() { let (_, myTrips) = planSaveAndReturnToMyTrips() myTrips.assertHasTrips() // First trip card should exist let firstTrip = myTrips.tripCard(0) XCTAssertTrue( firstTrip.waitForExistence(timeout: BaseUITestCase.defaultTimeout), "First saved trip card should be visible" ) captureScreenshot(named: "F059-SavedTripsList") } // MARK: - Empty State (F-058) /// F-058: My Trips empty state when no trips saved. @MainActor func testF058_MyTripsEmptyState() { let home = HomeScreen(app: app) home.waitForLoad() home.switchToTab(home.myTripsTab) let myTrips = MyTripsScreen(app: app) myTrips.assertEmpty() captureScreenshot(named: "F058-MyTrips-Empty") } // MARK: - Tap Saved Trip Opens Detail (F-079) /// F-079: Tapping a saved trip card opens the detail view. @MainActor func testF079_TapSavedTripOpensDetail() { let (_, myTrips) = planSaveAndReturnToMyTrips() myTrips.assertHasTrips() // Tap the first saved trip myTrips.tapTrip(at: 0) // Trip detail should open let detail = TripDetailScreen(app: app) detail.waitForLoad() detail.assertItineraryVisible() captureScreenshot(named: "F079-TapSavedTripOpensDetail") } // MARK: - Remove Saved Trip (F-080) /// F-080: Unfavoriting a trip removes it from My Trips. @MainActor func testF080_DeleteSavedTrip() { let (_, myTrips) = planSaveAndReturnToMyTrips() myTrips.assertHasTrips() // Tap into the saved trip detail myTrips.tapTrip(at: 0) let detail = TripDetailScreen(app: app) detail.waitForLoad() // Unfavorite to remove from saved trips detail.assertSaveState(isSaved: true) detail.tapFavorite() detail.assertSaveState(isSaved: false) // Navigate back to My Trips app.navigationBars.buttons.firstMatch.tap() // After unfavoriting, should show empty state myTrips.assertEmpty() captureScreenshot(named: "F080-DeleteSavedTrip") } // MARK: - Stats Row (F-061) /// F-061: Trip detail stats row shows city count, game count, distance, driving time. @MainActor func testF061_StatsRowDisplaysCorrectly() { let (_, detail) = TestFlows.planAndSelectFirstTrip(app: app) detail.assertStatsRowVisible() captureScreenshot(named: "F061-StatsRow") } // MARK: - Trip Detail Loads with Map (F-060) /// F-060: Trip detail shows the hero map and itinerary after opening. @MainActor func testF060_TripDetailLoadsWithMap() { let (_, detail) = TestFlows.planAndSelectFirstTrip(app: app) // Favorite button sits on the map overlay — its presence proves the map loaded XCTAssertTrue( detail.favoriteButton.exists, "Favorite button (map overlay) should be visible, proving map loaded" ) // Itinerary section should also be visible below the map detail.assertItineraryVisible() captureScreenshot(named: "F060-TripDetailWithMap") } // MARK: - Itinerary Day Cards (F-062) /// F-062: Scrolling through the itinerary reveals day cards (Day 1, Day 2, etc.). @MainActor func testF062_ItineraryDayCardsVisible() { let (_, _) = TestFlows.planAndSelectFirstTrip(app: app) // Day 1 should exist in the itinerary let day1 = app.staticTexts["Day 1"] var scrollAttempts = 0 while !day1.exists && scrollAttempts < 15 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(day1.exists, "Day 1 card should be visible in itinerary") // Day 2 should also exist (planned trips have at least 2 days) let day2 = app.staticTexts["Day 2"] scrollAttempts = 0 while !day2.exists && scrollAttempts < 10 { app.swipeUp(velocity: .slow) scrollAttempts += 1 } XCTAssertTrue(day2.exists, "Day 2 card should be visible in itinerary") captureScreenshot(named: "F062-ItineraryDayCards") } }