- Reorder wizard steps: dates before sports (enables availability check) - Add contentShape(Rectangle()) for full tap targets on all cards - Fix route preference showing preselected value - Fix sport cards having inconsistent heights - Speed up step reveal animation (0.3s → 0.15s) - Add debounced scroll delay to avoid interrupting selection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
272 lines
8.5 KiB
Swift
272 lines
8.5 KiB
Swift
//
|
|
// TripWizardViewModelTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
|
|
import XCTest
|
|
@testable import SportsTime
|
|
|
|
final class TripWizardViewModelTests: XCTestCase {
|
|
|
|
func test_initialState_onlyPlanningModeStepVisible() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
XCTAssertTrue(viewModel.isPlanningModeStepVisible)
|
|
XCTAssertFalse(viewModel.isSportsStepVisible)
|
|
XCTAssertFalse(viewModel.isDatesStepVisible)
|
|
XCTAssertFalse(viewModel.isRegionsStepVisible)
|
|
}
|
|
|
|
func test_selectingPlanningMode_revealsSportsStep() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
viewModel.planningMode = .dateRange
|
|
|
|
XCTAssertTrue(viewModel.isSportsStepVisible)
|
|
}
|
|
|
|
func test_selectingSport_revealsDatesStep() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
|
|
viewModel.selectedSports = [.mlb]
|
|
|
|
XCTAssertTrue(viewModel.isDatesStepVisible)
|
|
}
|
|
|
|
func test_changingPlanningMode_resetsDownstreamSelections() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb, .nba]
|
|
viewModel.hasSetDates = true
|
|
|
|
viewModel.planningMode = .gameFirst
|
|
|
|
XCTAssertTrue(viewModel.selectedSports.isEmpty)
|
|
XCTAssertFalse(viewModel.hasSetDates)
|
|
}
|
|
|
|
// MARK: - Full Flow Integration Tests
|
|
|
|
func test_fullWizardFlow_reachesReviewStep() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
// Step 1: Select planning mode
|
|
viewModel.planningMode = .dateRange
|
|
XCTAssertTrue(viewModel.isSportsStepVisible)
|
|
|
|
// Step 2: Select sport
|
|
viewModel.selectedSports = [.mlb]
|
|
XCTAssertTrue(viewModel.isDatesStepVisible)
|
|
|
|
// Step 3: Set dates
|
|
viewModel.hasSetDates = true
|
|
XCTAssertTrue(viewModel.isRegionsStepVisible)
|
|
|
|
// Step 4: Select regions
|
|
viewModel.selectedRegions = [.east]
|
|
XCTAssertTrue(viewModel.isRoutePreferenceStepVisible)
|
|
|
|
// Step 5: Set route preference
|
|
viewModel.hasSetRoutePreference = true
|
|
XCTAssertTrue(viewModel.isRepeatCitiesStepVisible)
|
|
|
|
// Step 6: Set repeat cities preference
|
|
viewModel.hasSetRepeatCities = true
|
|
XCTAssertTrue(viewModel.isMustStopsStepVisible)
|
|
XCTAssertTrue(viewModel.isReviewStepVisible)
|
|
}
|
|
|
|
func test_regionSelection_revealsRoutePreference() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.nba]
|
|
viewModel.hasSetDates = true
|
|
|
|
XCTAssertFalse(viewModel.isRoutePreferenceStepVisible)
|
|
|
|
viewModel.selectedRegions = [.central, .west]
|
|
|
|
XCTAssertTrue(viewModel.isRoutePreferenceStepVisible)
|
|
}
|
|
|
|
func test_routePreference_revealsRepeatCities() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb]
|
|
viewModel.hasSetDates = true
|
|
viewModel.selectedRegions = [.east]
|
|
|
|
XCTAssertFalse(viewModel.isRepeatCitiesStepVisible)
|
|
|
|
viewModel.hasSetRoutePreference = true
|
|
|
|
XCTAssertTrue(viewModel.isRepeatCitiesStepVisible)
|
|
}
|
|
|
|
func test_repeatCities_revealsMustStopsAndReview() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb]
|
|
viewModel.hasSetDates = true
|
|
viewModel.selectedRegions = [.east]
|
|
viewModel.hasSetRoutePreference = true
|
|
|
|
XCTAssertFalse(viewModel.isMustStopsStepVisible)
|
|
XCTAssertFalse(viewModel.isReviewStepVisible)
|
|
|
|
viewModel.hasSetRepeatCities = true
|
|
|
|
XCTAssertTrue(viewModel.isMustStopsStepVisible)
|
|
XCTAssertTrue(viewModel.isReviewStepVisible)
|
|
}
|
|
|
|
// MARK: - Reset Behavior Tests
|
|
|
|
func test_changingPlanningMode_resetsAllDownstreamState() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
// Set up full wizard state
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb, .nba]
|
|
viewModel.hasSetDates = true
|
|
viewModel.selectedRegions = [.east, .central]
|
|
viewModel.hasSetRoutePreference = true
|
|
viewModel.hasSetRepeatCities = true
|
|
viewModel.mustStopLocations = [LocationInput(name: "Test", coordinates: nil)]
|
|
|
|
// Change planning mode
|
|
viewModel.planningMode = .locations
|
|
|
|
// Verify all downstream state is reset
|
|
XCTAssertTrue(viewModel.selectedSports.isEmpty)
|
|
XCTAssertFalse(viewModel.hasSetDates)
|
|
XCTAssertTrue(viewModel.selectedRegions.isEmpty)
|
|
XCTAssertFalse(viewModel.hasSetRoutePreference)
|
|
XCTAssertFalse(viewModel.hasSetRepeatCities)
|
|
XCTAssertTrue(viewModel.mustStopLocations.isEmpty)
|
|
}
|
|
|
|
func test_settingSamePlanningMode_doesNotResetState() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb]
|
|
viewModel.hasSetDates = true
|
|
|
|
// Re-set the same mode
|
|
viewModel.planningMode = .dateRange
|
|
|
|
// State should NOT be reset
|
|
XCTAssertEqual(viewModel.selectedSports, [.mlb])
|
|
XCTAssertTrue(viewModel.hasSetDates)
|
|
}
|
|
|
|
// MARK: - Edge Cases
|
|
|
|
func test_emptyRegionSelection_hidesRoutePreference() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb]
|
|
viewModel.hasSetDates = true
|
|
viewModel.selectedRegions = [.west]
|
|
XCTAssertTrue(viewModel.isRoutePreferenceStepVisible)
|
|
|
|
viewModel.selectedRegions = []
|
|
|
|
XCTAssertFalse(viewModel.isRoutePreferenceStepVisible)
|
|
}
|
|
|
|
func test_multipleSports_canBeSelected() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
|
|
viewModel.selectedSports = [.mlb, .nba, .nhl]
|
|
|
|
XCTAssertEqual(viewModel.selectedSports.count, 3)
|
|
XCTAssertTrue(viewModel.isDatesStepVisible)
|
|
}
|
|
|
|
func test_multipleRegions_canBeSelected() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb]
|
|
viewModel.hasSetDates = true
|
|
|
|
viewModel.selectedRegions = [.east, .central, .west]
|
|
|
|
XCTAssertEqual(viewModel.selectedRegions.count, 3)
|
|
XCTAssertTrue(viewModel.isRoutePreferenceStepVisible)
|
|
}
|
|
|
|
// MARK: - Reveal State Bitmask Tests
|
|
|
|
func test_revealState_initialValue() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
XCTAssertEqual(viewModel.revealState, 0)
|
|
}
|
|
|
|
func test_revealState_incrementsWithProgress() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
viewModel.planningMode = .dateRange
|
|
XCTAssertEqual(viewModel.revealState, 1) // Sports visible
|
|
|
|
viewModel.selectedSports = [.mlb]
|
|
XCTAssertEqual(viewModel.revealState, 3) // Sports + Dates
|
|
|
|
viewModel.hasSetDates = true
|
|
XCTAssertEqual(viewModel.revealState, 7) // Sports + Dates + Regions
|
|
}
|
|
|
|
func test_revealState_fullFlow() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.planningMode = .dateRange
|
|
viewModel.selectedSports = [.mlb]
|
|
viewModel.hasSetDates = true
|
|
viewModel.selectedRegions = [.east]
|
|
viewModel.hasSetRoutePreference = true
|
|
viewModel.hasSetRepeatCities = true
|
|
|
|
// 1 + 2 + 4 + 8 + 16 + 32 + 64 = 127
|
|
XCTAssertEqual(viewModel.revealState, 127)
|
|
}
|
|
|
|
// MARK: - Sport Availability Tests
|
|
|
|
func test_canSelectSport_defaultsToTrue() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
XCTAssertTrue(viewModel.canSelectSport(.mlb))
|
|
XCTAssertTrue(viewModel.canSelectSport(.nba))
|
|
XCTAssertTrue(viewModel.canSelectSport(.nhl))
|
|
}
|
|
|
|
func test_canSelectSport_respectsAvailability() {
|
|
let viewModel = TripWizardViewModel()
|
|
viewModel.sportAvailability = [.mlb: true, .nba: false, .nhl: true]
|
|
|
|
XCTAssertTrue(viewModel.canSelectSport(.mlb))
|
|
XCTAssertFalse(viewModel.canSelectSport(.nba))
|
|
XCTAssertTrue(viewModel.canSelectSport(.nhl))
|
|
}
|
|
|
|
// MARK: - Planning State Tests
|
|
|
|
func test_isPlanning_defaultsToFalse() {
|
|
let viewModel = TripWizardViewModel()
|
|
|
|
XCTAssertFalse(viewModel.isPlanning)
|
|
}
|
|
|
|
func test_mustStopLocations_canBeAdded() {
|
|
let viewModel = TripWizardViewModel()
|
|
let location = LocationInput(name: "Chicago, IL", coordinates: nil)
|
|
|
|
viewModel.mustStopLocations.append(location)
|
|
|
|
XCTAssertEqual(viewModel.mustStopLocations.count, 1)
|
|
XCTAssertEqual(viewModel.mustStopLocations.first?.name, "Chicago, IL")
|
|
}
|
|
}
|