// // TripOptionsTests.swift // SportsTimeUITests // // Tests the Trip Options results screen: sorting, selection, navigation. // QA Sheet: F-051, F-052, F-053, F-054, F-055, F-056, F-057, F-058, F-059, F-076 // import XCTest final class TripOptionsTests: BaseUITestCase { // MARK: - Helpers /// Plans a trip and returns the options screen ready for sorting tests. @MainActor private func planTripAndGetOptions() -> TripOptionsScreen { let (_, options) = TestFlows.planDateRangeTrip(app: app) options.assertHasResults() return options } // MARK: - Results Display (F-051) /// F-051: Trip options listed with city count, game count, distance, and preview. @MainActor func testF051_ResultsDisplayAfterPlanning() { let options = planTripAndGetOptions() // At least one trip card should be visible let firstCard = options.tripCard(0) XCTAssertTrue(firstCard.exists, "First trip card should be visible") // Sort dropdown should be visible (proves results header rendered) XCTAssertTrue(options.sortDropdown.exists, "Sort dropdown should be visible on results screen") captureScreenshot(named: "F051-ResultsDisplay") } // MARK: - Sort Options (F-052, F-053, F-054, F-055) /// F-052: Sort by Recommended reorders trip options. @MainActor func testF052_SortByRecommended() { let options = planTripAndGetOptions() // Sort option IDs are rawValue.lowercased() with spaces removed options.sort(by: "recommended") // Results should still exist after sorting options.assertHasResults() captureScreenshot(named: "F052-SortByRecommended") } /// F-053: Sort by Most Games reorders trip options. @MainActor func testF053_SortByMostGames() { let options = planTripAndGetOptions() options.sort(by: "mostgames") options.assertHasResults() captureScreenshot(named: "F053-SortByMostGames") } /// F-054: Sort by Least Miles reorders trip options. @MainActor func testF054_SortByLeastMiles() { let options = planTripAndGetOptions() options.sort(by: "leastmiles") options.assertHasResults() captureScreenshot(named: "F054-SortByLeastMiles") } /// F-055: Sort by Best Efficiency reorders trip options. @MainActor func testF055_SortByBestEfficiency() { let options = planTripAndGetOptions() options.sort(by: "bestefficiency") options.assertHasResults() captureScreenshot(named: "F055-SortByBestEfficiency") } // MARK: - Select Trip (F-058) /// F-058: Tapping a trip option card opens TripDetailView with full itinerary. @MainActor func testF058_SelectTripOpensDetail() { let options = planTripAndGetOptions() // Tap first trip card options.selectTrip(at: 0) // Trip detail should open with itinerary let detail = TripDetailScreen(app: app) detail.waitForLoad() detail.assertItineraryVisible() captureScreenshot(named: "F058-SelectTripOpensDetail") } // MARK: - Pace Filter (F-056) /// F-056: Pace filter dropdown selects Packed/Moderate/Relaxed. @MainActor func testF056_PaceFilter() { _ = planTripAndGetOptions() // The pace filter is a Menu. The default label shows "All". // Find and tap the pace filter menu button. let paceMenu = app.buttons.matching(NSPredicate( format: "label CONTAINS 'All' OR label CONTAINS 'Packed' OR label CONTAINS 'Moderate' OR label CONTAINS 'Relaxed'" )).firstMatch XCTAssertTrue(paceMenu.waitForExistence(timeout: BaseUITestCase.shortTimeout), "Pace filter menu should exist") paceMenu.tap() // Select "Packed" from the menu let packedOption = app.buttons["Packed"] XCTAssertTrue(packedOption.waitForExistence(timeout: BaseUITestCase.shortTimeout), "Packed option should exist in pace menu") packedOption.tap() // Results should update (may show fewer or same results) // The pace filter label should now show "Packed" let updatedMenu = app.buttons.matching(NSPredicate( format: "label CONTAINS 'Packed'" )).firstMatch XCTAssertTrue(updatedMenu.waitForExistence(timeout: BaseUITestCase.shortTimeout), "Pace filter should show 'Packed' after selection") captureScreenshot(named: "F056-PaceFilter-Packed") } // MARK: - Cities Filter (F-057) /// F-057: Cities filter limits results to selected max cities. @MainActor func testF057_CitiesFilter() { _ = planTripAndGetOptions() // Cities filter buttons are labeled with numbers: "No Limit", "15", "10", "5", etc. // Find and tap the "5" cities filter button let fiveCitiesButton = app.buttons["5"] fiveCitiesButton.scrollIntoView(in: app.scrollViews.firstMatch) XCTAssertTrue(fiveCitiesButton.waitForExistence(timeout: BaseUITestCase.shortTimeout), "'5' cities filter button should exist") fiveCitiesButton.tap() let firstTrip = app.buttons.matching(NSPredicate(format: "identifier BEGINSWITH 'tripOptions.trip.'")).firstMatch XCTAssertTrue( firstTrip.waitForExistence(timeout: BaseUITestCase.shortTimeout), "Trip results should remain visible after applying the cities filter" ) captureScreenshot(named: "F057-CitiesFilter-5") } // MARK: - Trip Detail Variants (F-076) /// F-076: Trip detail works correctly with a single-stop (minimal) trip. @MainActor func testF076_TripDetailWithSingleStop() { let options = planTripAndGetOptions() // Select the first trip option options.selectTrip(at: 0) // Trip detail should load with itinerary let detail = TripDetailScreen(app: app) detail.waitForLoad() detail.assertItineraryVisible() // Verify the detail screen is functional (favorite button exists) XCTAssertTrue(detail.favoriteButton.waitForExistence( timeout: BaseUITestCase.shortTimeout), "Favorite button should exist on trip detail") captureScreenshot(named: "F076-TripDetailSingleStop") } // MARK: - Back Navigation (F-059) /// F-059: Back button on Trip Options returns to wizard with selections preserved. @MainActor func testF059_BackToWizardFromOptions() { _ = planTripAndGetOptions() // Tap back button to return to wizard app.navigationBars.buttons.firstMatch.tap() // Wizard should reappear with its navigation title or planning mode buttons let wizard = TripWizardScreen(app: app) let wizardVisible = wizard.navigationTitle.waitForExistence(timeout: BaseUITestCase.defaultTimeout) || wizard.planningModeButton("dateRange").waitForExistence(timeout: BaseUITestCase.defaultTimeout) XCTAssertTrue(wizardVisible, "Wizard should be visible after tapping back from options") captureScreenshot(named: "F059-BackToWizard") } }