Stabilize unit and UI tests for SportsTime

This commit is contained in:
treyt
2026-02-18 13:00:15 -06:00
parent 1488be7c1f
commit 20ac1a7e59
49 changed files with 432 additions and 325 deletions

View File

@@ -24,7 +24,7 @@ struct GameDAGRouterTests {
private let laCoord = CLLocationCoordinate2D(latitude: 34.0141, longitude: -118.2879)
private let seattleCoord = CLLocationCoordinate2D(latitude: 47.5914, longitude: -122.3316)
private let calendar = Calendar.current
private let calendar = TestClock.calendar
// MARK: - Specification Tests: Edge Cases
@@ -40,7 +40,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: single game with no anchors returns single-game route")
func findRoutes_singleGame_noAnchors_returnsSingleRoute() {
let (game, stadium) = makeGameAndStadium(city: "New York", date: Date())
let (game, stadium) = makeGameAndStadium(city: "New York", date: TestClock.now)
let routes = GameDAGRouter.findRoutes(
games: [game],
@@ -55,7 +55,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: single game matching anchor returns single-game route")
func findRoutes_singleGame_matchingAnchor_returnsSingleRoute() {
let (game, stadium) = makeGameAndStadium(city: "New York", date: Date())
let (game, stadium) = makeGameAndStadium(city: "New York", date: TestClock.now)
let routes = GameDAGRouter.findRoutes(
games: [game],
@@ -70,7 +70,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: single game not matching anchor returns empty")
func findRoutes_singleGame_notMatchingAnchor_returnsEmpty() {
let (game, stadium) = makeGameAndStadium(city: "New York", date: Date())
let (game, stadium) = makeGameAndStadium(city: "New York", date: TestClock.now)
let routes = GameDAGRouter.findRoutes(
games: [game],
@@ -86,7 +86,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: two feasible games returns combined route")
func findRoutes_twoFeasibleGames_returnsCombinedRoute() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Date = calendar.date(bySettingHour: 13, minute: 0, second: 0, of: today)!
let game2Date = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: today)!
@@ -112,7 +112,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: two infeasible same-day games returns separate routes when no anchors")
func findRoutes_twoInfeasibleGames_noAnchors_returnsSeparateRoutes() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Date = calendar.date(bySettingHour: 13, minute: 0, second: 0, of: today)!
let game2Date = calendar.date(bySettingHour: 15, minute: 0, second: 0, of: today)! // Only 2 hours later
@@ -135,7 +135,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: two infeasible games with both as anchors returns empty")
func findRoutes_twoInfeasibleGames_bothAnchors_returnsEmpty() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Date = calendar.date(bySettingHour: 13, minute: 0, second: 0, of: today)!
let game2Date = calendar.date(bySettingHour: 15, minute: 0, second: 0, of: today)!
@@ -158,7 +158,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: routes contain all anchor games")
func findRoutes_routesContainAllAnchors() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let dates = (0..<5).map { dayOffset in
calendar.date(byAdding: .day, value: dayOffset, to: today)!
}
@@ -199,7 +199,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: allowRepeatCities=false excludes routes with duplicate cities")
func findRoutes_disallowRepeatCities_excludesDuplicates() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let dates = (0..<3).map { dayOffset in
calendar.date(byAdding: .day, value: dayOffset, to: today)!
}
@@ -228,7 +228,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: allowRepeatCities=true allows routes with duplicate cities")
func findRoutes_allowRepeatCities_allowsDuplicates() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let dates = (0..<3).map { dayOffset in
calendar.date(byAdding: .day, value: dayOffset, to: today)!
}
@@ -270,7 +270,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: all routes are chronologically ordered")
func findRoutes_allRoutesChronological() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let dates = (0..<5).map { dayOffset in
calendar.date(byAdding: .day, value: dayOffset, to: today)!
}
@@ -307,7 +307,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: respects maxDailyDrivingHours for same-day games")
func findRoutes_respectsSameDayDrivingLimit() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Time = calendar.date(bySettingHour: 13, minute: 0, second: 0, of: today)!
let game2Time = calendar.date(bySettingHour: 20, minute: 0, second: 0, of: today)!
@@ -331,7 +331,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: multi-day trips allow longer total driving")
func findRoutes_multiDayTrips_allowLongerDriving() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Date = today
let game2Date = calendar.date(byAdding: .day, value: 2, to: today)! // 2 days later
@@ -355,7 +355,7 @@ struct GameDAGRouterTests {
@Test("findRoutes: anchor routes can span gaps larger than 5 days")
func findRoutes_anchorRoutesAllowLongDateGaps() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let day0 = today
let day1 = calendar.date(byAdding: .day, value: 1, to: today)!
let day8 = calendar.date(byAdding: .day, value: 8, to: today)!
@@ -387,7 +387,7 @@ struct GameDAGRouterTests {
@Test("Property: route count never exceeds maxOptions (75)")
func property_routeCountNeverExceedsMax() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
// Create many games to stress test
var games: [Game] = []
@@ -413,7 +413,7 @@ struct GameDAGRouterTests {
@Test("Property: all routes satisfy constraints")
func property_allRoutesSatisfyConstraints() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let dates = (0..<5).map { calendar.date(byAdding: .day, value: $0, to: today)! }
let gamesAndStadiums = [
@@ -469,7 +469,7 @@ struct GameDAGRouterTests {
@Test("Edge: games at same stadium always feasible")
func edge_sameStadium_alwaysFeasible() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Time = calendar.date(bySettingHour: 13, minute: 0, second: 0, of: today)!
let game2Time = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: today)! // Doubleheader
@@ -489,7 +489,7 @@ struct GameDAGRouterTests {
@Test("Edge: games out of order are sorted chronologically")
func edge_unsortedGames_areSorted() {
let today = calendar.startOfDay(for: Date())
let today = calendar.startOfDay(for: TestClock.now)
let game1Date = calendar.date(byAdding: .day, value: 2, to: today)!
let game2Date = today
let game3Date = calendar.date(byAdding: .day, value: 1, to: today)!
@@ -517,8 +517,8 @@ struct GameDAGRouterTests {
@Test("Edge: missing stadium for game is handled gracefully")
func edge_missingStadium_handledGracefully() {
let (game1, stadium1) = makeGameAndStadium(city: "New York", date: Date(), coord: nycCoord)
let game2 = makeGame(stadiumId: "nonexistent-stadium", date: Date().addingTimeInterval(86400))
let (game1, stadium1) = makeGameAndStadium(city: "New York", date: TestClock.now, coord: nycCoord)
let game2 = makeGame(stadiumId: "nonexistent-stadium", date: TestClock.now.addingTimeInterval(86400))
// Only provide stadium for game1
let stadiums = [stadium1.id: stadium1]

View File

@@ -22,7 +22,7 @@ struct ItineraryBuilderTests {
private let chicagoCoord = CLLocationCoordinate2D(latitude: 41.8827, longitude: -87.6233)
private let seattleCoord = CLLocationCoordinate2D(latitude: 47.5914, longitude: -122.3316)
private let calendar = Calendar.current
private let calendar = TestClock.calendar
// MARK: - Specification Tests: build()
@@ -203,7 +203,7 @@ struct ItineraryBuilderTests {
@Test("arrivalBeforeGameStart: sufficient time passes")
func arrivalBeforeGameStart_sufficientTime_passes() {
let now = Date()
let now = TestClock.now
let tomorrow = calendar.date(byAdding: .day, value: 1, to: now)!
let gameTime = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: tomorrow)!
@@ -232,7 +232,7 @@ struct ItineraryBuilderTests {
@Test("arrivalBeforeGameStart: insufficient time fails")
func arrivalBeforeGameStart_insufficientTime_fails() {
let now = Date()
let now = TestClock.now
let gameTime = now.addingTimeInterval(2 * 3600) // Game in 2 hours
let stop1 = makeStop(
@@ -349,7 +349,7 @@ struct ItineraryBuilderTests {
private func makeStop(
city: String,
coordinate: CLLocationCoordinate2D?,
departureDate: Date = Date(),
departureDate: Date = TestClock.now,
firstGameStart: Date? = nil
) -> ItineraryStop {
ItineraryStop(

View File

@@ -306,8 +306,8 @@ struct PlanningModelsTests {
state: "XX",
coordinate: nil,
games: games,
arrivalDate: Date(),
departureDate: Date(),
arrivalDate: TestClock.now,
departureDate: TestClock.now,
location: LocationInput(name: "Test", coordinate: nil),
firstGameStart: nil
)
@@ -348,8 +348,8 @@ struct PlanningModelsTests {
state: "NY",
coordinate: nil,
games: ["g1"],
arrivalDate: Date(),
departureDate: Date(),
arrivalDate: TestClock.now,
departureDate: TestClock.now,
location: LocationInput(name: "NY", coordinate: nil),
firstGameStart: nil
)
@@ -364,8 +364,8 @@ struct PlanningModelsTests {
state: "XX",
coordinate: nil,
games: games,
arrivalDate: Date(),
departureDate: Date(),
arrivalDate: TestClock.now,
departureDate: TestClock.now,
location: LocationInput(name: "Test", coordinate: nil),
firstGameStart: nil
)

View File

@@ -22,8 +22,8 @@ struct Bug1_TeamFirstSingleTeamTests {
planningMode: .teamFirst,
sports: [.mlb],
travelMode: .drive,
startDate: Date(),
endDate: Calendar.current.date(byAdding: .day, value: 7, to: Date())!,
startDate: TestClock.now,
endDate: TestClock.calendar.date(byAdding: .day, value: 7, to: TestClock.now)!,
leisureLevel: .moderate,
routePreference: .balanced,
selectedTeamIds: ["team_mlb_boston"]
@@ -47,8 +47,8 @@ struct Bug1_TeamFirstSingleTeamTests {
planningMode: .teamFirst,
sports: [.mlb],
travelMode: .drive,
startDate: Date(),
endDate: Calendar.current.date(byAdding: .day, value: 7, to: Date())!,
startDate: TestClock.now,
endDate: TestClock.calendar.date(byAdding: .day, value: 7, to: TestClock.now)!,
leisureLevel: .moderate,
routePreference: .balanced,
selectedTeamIds: ["team_mlb_boston", "team_mlb_new_york"]
@@ -73,7 +73,7 @@ struct Bug2_InfiniteLoopTests {
@Test("calculateRestDays does not hang for normal multi-day stop")
func calculateRestDays_normalMultiDay_terminates() {
let calendar = Calendar.current
let calendar = TestClock.calendar
let arrival = TestFixtures.date(year: 2026, month: 6, day: 10, hour: 12)
let departure = TestFixtures.date(year: 2026, month: 6, day: 14, hour: 12)
@@ -236,7 +236,7 @@ struct Bug5_ScenarioDDepartureDateTests {
@Test("stop departureDate should be after last game, not same day")
func departureDate_shouldBeAfterLastGame() {
let gameDate = TestFixtures.date(year: 2026, month: 7, day: 5, hour: 19)
let calendar = Calendar.current
let calendar = TestClock.calendar
let bostonStadium = TestFixtures.stadium(city: "Boston")
let game = TestFixtures.game(id: "g1", city: "Boston", dateTime: gameDate, stadiumId: bostonStadium.id)
@@ -283,7 +283,7 @@ struct Bug6_ScenarioCDateRangeTests {
@Test("games spanning exactly daySpan should be included")
func gamesSpanningExactDaySpan_shouldBeIncluded() {
// If daySpan is 7, games exactly 7 days apart should be valid
let calendar = Calendar.current
let calendar = TestClock.calendar
let startDate = TestFixtures.date(year: 2026, month: 7, day: 1, hour: 19)
let endDate = calendar.date(byAdding: .day, value: 7, to: startDate)!
@@ -341,8 +341,8 @@ struct Bug7_DrivingConstraintsClampTests {
planningMode: .dateRange,
sports: [.mlb],
travelMode: .drive,
startDate: Date(),
endDate: Calendar.current.date(byAdding: .day, value: 7, to: Date())!,
startDate: TestClock.now,
endDate: TestClock.calendar.date(byAdding: .day, value: 7, to: TestClock.now)!,
leisureLevel: .moderate,
routePreference: .balanced,
maxDrivingHoursPerDriver: 0.5
@@ -365,8 +365,8 @@ struct Bug7_DrivingConstraintsClampTests {
planningMode: .dateRange,
sports: [.mlb],
travelMode: .drive,
startDate: Date(),
endDate: Calendar.current.date(byAdding: .day, value: 7, to: Date())!,
startDate: TestClock.now,
endDate: TestClock.calendar.date(byAdding: .day, value: 7, to: TestClock.now)!,
leisureLevel: .moderate,
routePreference: .balanced,
maxDrivingHoursPerDriver: nil
@@ -522,18 +522,18 @@ struct Bug11_SortByLeisureTests {
city: "Boston", state: "MA",
coordinate: TestFixtures.coordinates["Boston"],
games: ["g1", "g2", "g3"],
arrivalDate: Date(), departureDate: Date().addingTimeInterval(86400),
arrivalDate: TestClock.now, departureDate: TestClock.now.addingTimeInterval(86400),
location: LocationInput(name: "Boston", coordinate: TestFixtures.coordinates["Boston"]),
firstGameStart: Date()
firstGameStart: TestClock.now
)
let stop2 = ItineraryStop(
city: "Boston", state: "MA",
coordinate: TestFixtures.coordinates["Boston"],
games: ["g4"],
arrivalDate: Date(), departureDate: Date().addingTimeInterval(86400),
arrivalDate: TestClock.now, departureDate: TestClock.now.addingTimeInterval(86400),
location: LocationInput(name: "Boston", coordinate: TestFixtures.coordinates["Boston"]),
firstGameStart: Date()
firstGameStart: TestClock.now
)
let option3Games = ItineraryOption(
@@ -563,7 +563,7 @@ struct Bug12_ValidatorGameEndTimeTests {
@Test("validator checks arrival feasibility with buffer")
func validator_checksArrivalBuffer() {
// Use deterministic dates to avoid time-of-day sensitivity
let calendar = Calendar.current
let calendar = TestClock.calendar
let today = TestFixtures.date(year: 2026, month: 7, day: 10, hour: 14) // 2pm today
let tomorrow = calendar.date(byAdding: .day, value: 1, to: today)!
let tomorrowMorning = TestFixtures.date(year: 2026, month: 7, day: 11, hour: 8) // 8am departure
@@ -735,13 +735,13 @@ struct TravelEstimatorConsistencyTests {
// ItineraryStop overload
let fromStop = ItineraryStop(
city: "Boston", state: "MA", coordinate: bostonCoord,
games: [], arrivalDate: Date(), departureDate: Date(),
games: [], arrivalDate: TestClock.now, departureDate: TestClock.now,
location: LocationInput(name: "Boston", coordinate: bostonCoord),
firstGameStart: nil
)
let toStop = ItineraryStop(
city: "New York", state: "NY", coordinate: nycCoord,
games: [], arrivalDate: Date(), departureDate: Date(),
games: [], arrivalDate: TestClock.now, departureDate: TestClock.now,
location: LocationInput(name: "New York", coordinate: nycCoord),
firstGameStart: nil
)

View File

@@ -14,8 +14,8 @@ struct RouteFiltersTests {
// MARK: - Test Data
private let calendar = Calendar.current
private let today = Calendar.current.startOfDay(for: Date())
private let calendar = TestClock.calendar
private let today = TestClock.calendar.startOfDay(for: TestClock.now)
private var tomorrow: Date {
calendar.date(byAdding: .day, value: 1, to: today)!

View File

@@ -15,7 +15,7 @@ struct ScenarioAPlannerTests {
// MARK: - Test Data
private let planner = ScenarioAPlanner()
private let calendar = Calendar.current
private let calendar = TestClock.calendar
// Coordinates for testing
private let nycCoord = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855)
@@ -27,7 +27,7 @@ struct ScenarioAPlannerTests {
@Test("plan: no games in date range returns noGamesInRange failure")
func plan_noGamesInRange_returnsFailure() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let prefs = TripPreferences(
@@ -58,7 +58,7 @@ struct ScenarioAPlannerTests {
@Test("plan: games outside date range returns noGamesInRange")
func plan_gamesOutsideDateRange_returnsNoGamesInRange() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
// Game is after the date range
@@ -97,7 +97,7 @@ struct ScenarioAPlannerTests {
@Test("plan: with selectedRegions filters to those regions")
func plan_withSelectedRegions_filtersGames() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let gameDate = startDate.addingTimeInterval(86400 * 2)
@@ -144,7 +144,7 @@ struct ScenarioAPlannerTests {
@Test("plan: with mustStopLocation filters to that city")
func plan_withMustStopLocation_filtersToCity() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 14)
let gameDate = startDate.addingTimeInterval(86400 * 2)
@@ -185,7 +185,7 @@ struct ScenarioAPlannerTests {
@Test("plan: mustStopLocation with no games in that city returns noGamesInRange")
func plan_mustStopNoGamesInCity_returnsNoGamesInRange() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let gameDate = startDate.addingTimeInterval(86400 * 2)
@@ -222,7 +222,7 @@ struct ScenarioAPlannerTests {
@Test("plan: multiple must-stop cities are required without excluding other route games")
func plan_multipleMustStops_requireCoverageWithoutExclusiveFiltering() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -273,7 +273,7 @@ struct ScenarioAPlannerTests {
@Test("plan: single game in range returns success with one option")
func plan_singleGame_returnsSuccess() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let gameDate = startDate.addingTimeInterval(86400 * 2)
@@ -309,7 +309,7 @@ struct ScenarioAPlannerTests {
@Test("plan: multiple games at same stadium creates single stop")
func plan_multipleGamesAtSameStadium_createsSingleStop() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
@@ -353,7 +353,7 @@ struct ScenarioAPlannerTests {
@Test("Invariant: returned games are within date range")
func invariant_returnedGamesWithinDateRange() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
@@ -388,7 +388,7 @@ struct ScenarioAPlannerTests {
@Test("Invariant: A-B-A creates 3 stops not 2")
func invariant_visitSameCityTwice_createsThreeStops() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -437,7 +437,7 @@ struct ScenarioAPlannerTests {
@Test("Property: success always has non-empty options")
func property_successHasNonEmptyOptions() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)

View File

@@ -24,7 +24,7 @@ struct ScenarioBPlannerTests {
@Test("plan: no selected games returns failure")
func plan_noSelectedGames_returnsFailure() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let prefs = TripPreferences(
@@ -58,7 +58,7 @@ struct ScenarioBPlannerTests {
@Test("plan: single selected game returns success with that game")
func plan_singleSelectedGame_returnsSuccess() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let gameDate = startDate.addingTimeInterval(86400 * 2)
@@ -97,7 +97,7 @@ struct ScenarioBPlannerTests {
@Test("plan: all selected games appear in every route")
func plan_allSelectedGamesAppearInRoutes() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -147,7 +147,7 @@ struct ScenarioBPlannerTests {
// Bug: planTrip() was overriding the 7-day date range with just anchor dates,
// causing only the anchor game to appear in results.
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7) // 7-day span
// NYC and Boston are geographically close (drivable)
@@ -205,7 +205,7 @@ struct ScenarioBPlannerTests {
// Regression test: Verify that the planner considers games across the entire
// date range, not just on the anchor game dates.
let startDate = Date()
let startDate = TestClock.now
// 7-day date range
let day1 = startDate
@@ -266,15 +266,15 @@ struct ScenarioBPlannerTests {
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
// Game on a specific date
let gameDate = Date().addingTimeInterval(86400 * 5)
let gameDate = TestClock.now.addingTimeInterval(86400 * 5)
let game = makeGame(id: "game1", stadiumId: "stadium1", dateTime: gameDate)
let prefs = TripPreferences(
planningMode: .gameFirst,
sports: [.mlb],
mustSeeGameIds: ["game1"],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 30),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1,
@@ -299,7 +299,7 @@ struct ScenarioBPlannerTests {
@Test("plan: explicit date range with out-of-range selected game returns dateRangeViolation")
func plan_explicitDateRange_selectedGameOutsideRange_returnsDateRangeViolation() {
let baseDate = Date()
let baseDate = TestClock.now
let rangeStart = baseDate
let rangeEnd = baseDate.addingTimeInterval(86400 * 3)
let outOfRangeDate = baseDate.addingTimeInterval(86400 * 10)
@@ -347,7 +347,7 @@ struct ScenarioBPlannerTests {
// This test verifies that ScenarioB uses arrival time validation
// by creating a scenario where travel time makes arrival impossible
let now = Date()
let now = TestClock.now
let game1Date = now.addingTimeInterval(86400) // Tomorrow
let game2Date = now.addingTimeInterval(86400 + 3600) // Tomorrow + 1 hour (impossible to drive from coast to coast)
@@ -389,7 +389,7 @@ struct ScenarioBPlannerTests {
@Test("Invariant: selected games cannot be dropped")
func invariant_selectedGamesCannotBeDropped() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 14)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -431,7 +431,7 @@ struct ScenarioBPlannerTests {
@Test("Property: success with selected games includes all anchors")
func property_successIncludesAllAnchors() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let gameDate = startDate.addingTimeInterval(86400 * 2)

View File

@@ -33,8 +33,8 @@ struct ScenarioCPlannerTests {
startLocation: nil, // Missing
endLocation: endLocation,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -67,8 +67,8 @@ struct ScenarioCPlannerTests {
startLocation: startLocation,
endLocation: nil, // Missing
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -102,8 +102,8 @@ struct ScenarioCPlannerTests {
startLocation: startLocation,
endLocation: endLocation,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -139,8 +139,8 @@ struct ScenarioCPlannerTests {
startLocation: startLocation,
endLocation: endLocation,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -174,8 +174,8 @@ struct ScenarioCPlannerTests {
startLocation: startLocation,
endLocation: endLocation,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -199,7 +199,7 @@ struct ScenarioCPlannerTests {
@Test("plan: city names with state suffixes match stadium city names")
func plan_cityNamesWithStateSuffixes_matchStadiumCities() {
let baseDate = Date()
let baseDate = TestClock.now
let endDate = baseDate.addingTimeInterval(86400 * 10)
let startLocation = LocationInput(name: "Chicago, IL", coordinate: chicagoCoord)
@@ -242,7 +242,7 @@ struct ScenarioCPlannerTests {
@Test("plan: directional filtering includes stadiums toward destination")
func plan_directionalFiltering_includesCorrectStadiums() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 14)
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
@@ -297,7 +297,7 @@ struct ScenarioCPlannerTests {
@Test("plan: adds start and end as non-game stops")
func plan_addsStartEndAsNonGameStops() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
@@ -350,7 +350,7 @@ struct ScenarioCPlannerTests {
@Test("Invariant: start stop has no games")
func invariant_startStopHasNoGames() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)
@@ -400,7 +400,7 @@ struct ScenarioCPlannerTests {
@Test("Invariant: end stop appears last")
func invariant_endStopAppearsLast() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let startLocation = LocationInput(name: "Chicago", coordinate: chicagoCoord)

View File

@@ -24,7 +24,7 @@ struct ScenarioDPlannerTests {
@Test("plan: no followTeamId returns missingTeamSelection failure")
func plan_noFollowTeamId_returnsMissingTeamSelection() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let prefs = TripPreferences(
@@ -58,7 +58,7 @@ struct ScenarioDPlannerTests {
@Test("plan: no games for team returns noGamesInRange failure")
func plan_noGamesForTeam_returnsNoGamesInRange() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
@@ -105,7 +105,7 @@ struct ScenarioDPlannerTests {
@Test("plan: includes both home and away games for team")
func plan_includesBothHomeAndAwayGames() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 14)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -168,7 +168,7 @@ struct ScenarioDPlannerTests {
@Test("plan: with selectedRegions filters team games to those regions")
func plan_withSelectedRegions_filtersTeamGames() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 14)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord) // East
@@ -229,7 +229,7 @@ struct ScenarioDPlannerTests {
@Test("plan: valid request returns success")
func plan_validRequest_returnsSuccess() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
@@ -274,7 +274,7 @@ struct ScenarioDPlannerTests {
@Test("plan: useHomeLocation with startLocation adds home start and end stops")
func plan_useHomeLocationWithStartLocation_addsHomeEndpoints() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 10)
let homeCoord = CLLocationCoordinate2D(latitude: 39.7392, longitude: -104.9903) // Denver
@@ -333,7 +333,7 @@ struct ScenarioDPlannerTests {
@Test("Invariant: all returned games have team as home or away")
func invariant_allGamesHaveTeam() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 14)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -404,7 +404,7 @@ struct ScenarioDPlannerTests {
@Test("Invariant: duplicate routes are removed")
func invariant_duplicateRoutesRemoved() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
@@ -454,7 +454,7 @@ struct ScenarioDPlannerTests {
@Test("Property: success always has non-empty options")
func property_successHasNonEmptyOptions() {
let startDate = Date()
let startDate = TestClock.now
let endDate = startDate.addingTimeInterval(86400 * 7)
let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)

View File

@@ -40,8 +40,8 @@ struct ScenarioEPlannerTests {
// With 2 teams, window duration = 4 days.
// The window algorithm checks: windowEnd <= latestGameDay + 1
// So with games on day 1 and day 4: latestDay=4, windowEnd=5 <= day 5 (4+1) - valid!
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -102,7 +102,7 @@ struct ScenarioEPlannerTests {
@Test("generateValidWindows: window with only 2 of 3 teams excluded")
func generateValidWindows_windowMissingTeam_excluded() {
// Setup: 3 teams selected, but games are spread so no single window covers all 3
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -168,7 +168,7 @@ struct ScenarioEPlannerTests {
@Test("generateValidWindows: empty season returns empty")
func generateValidWindows_emptySeason_returnsEmpty() {
// Setup: No games available
let baseDate = Date()
let baseDate = TestClock.now
let prefs = TripPreferences(
planningMode: .teamFirst,
@@ -204,8 +204,8 @@ struct ScenarioEPlannerTests {
@Test("generateValidWindows: sampling works when more than 50 windows")
func generateValidWindows_manyWindows_samplesProperly() {
// Setup: Create many overlapping games so there are >50 valid windows
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -278,8 +278,8 @@ struct ScenarioEPlannerTests {
@Test("plan: returns PlanningResult with routes")
func plan_validRequest_returnsPlanningResultWithRoutes() {
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -339,8 +339,8 @@ struct ScenarioEPlannerTests {
@Test("plan: all routes include all selected teams")
func plan_allRoutesIncludeAllSelectedTeams() {
// Use just 2 teams for a simpler, more reliable test
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -408,8 +408,8 @@ struct ScenarioEPlannerTests {
@Test("plan: falls back when earliest per-team anchors are infeasible")
func plan_fallbackWhenEarliestAnchorsInfeasible() {
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let chicagoStadium = makeStadium(id: "chi", city: "Chicago", coordinate: chicagoCoord)
@@ -477,8 +477,8 @@ struct ScenarioEPlannerTests {
@Test("plan: keeps date-distinct options even when city order is identical")
func plan_keepsDistinctGameSetsWithSameCityOrder() {
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -548,7 +548,7 @@ struct ScenarioEPlannerTests {
@Test("plan: routes sorted by duration ascending")
func plan_routesSortedByDurationAscending() {
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -621,7 +621,7 @@ struct ScenarioEPlannerTests {
@Test("plan: respects max driving time constraint")
func plan_respectsMaxDrivingTimeConstraint() {
let baseDate = Date()
let baseDate = TestClock.now
// NYC and LA are ~40 hours apart by car
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -681,7 +681,7 @@ struct ScenarioEPlannerTests {
@Test("plan: teams with no overlapping games returns graceful error")
func plan_noOverlappingGames_returnsGracefulError() {
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -735,7 +735,7 @@ struct ScenarioEPlannerTests {
@Test("plan: single team selected returns validation error")
func plan_singleTeamSelected_returnsValidationError() {
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -777,7 +777,7 @@ struct ScenarioEPlannerTests {
@Test("plan: no teams selected returns validation error")
func plan_noTeamsSelected_returnsValidationError() {
let baseDate = Date()
let baseDate = TestClock.now
let prefs = TripPreferences(
planningMode: .teamFirst,
@@ -810,8 +810,8 @@ struct ScenarioEPlannerTests {
@Test("plan: teams in same city treated as separate stops")
func plan_teamsInSameCity_treatedAsSeparateStops() {
// Setup: Yankees and Mets both play in NYC but at different stadiums
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let calendar = TestClock.calendar
let baseDate = calendar.startOfDay(for: TestClock.now)
let yankeeStadiumCoord = CLLocationCoordinate2D(latitude: 40.8296, longitude: -73.9262)
let citiFieldCoord = CLLocationCoordinate2D(latitude: 40.7571, longitude: -73.8458)
@@ -879,7 +879,7 @@ struct ScenarioEPlannerTests {
@Test("plan: team with no home games returns error")
func plan_teamWithNoHomeGames_returnsError() {
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
@@ -942,7 +942,7 @@ struct ScenarioEPlannerTests {
@Test("Invariant: maximum 10 results returned")
func invariant_maximum10ResultsReturned() {
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
@@ -996,7 +996,7 @@ struct ScenarioEPlannerTests {
@Test("Invariant: all routes contain home games from all selected teams")
func invariant_allRoutesContainAllSelectedTeams() {
let baseDate = Date()
let baseDate = TestClock.now
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

View File

@@ -20,8 +20,8 @@ struct ScenarioPlannerFactoryTests {
let prefs = TripPreferences(
planningMode: .followTeam,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1,
@@ -41,8 +41,8 @@ struct ScenarioPlannerFactoryTests {
planningMode: .gameFirst,
sports: [.mlb],
mustSeeGameIds: [game.id],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -64,8 +64,8 @@ struct ScenarioPlannerFactoryTests {
startLocation: LocationInput(name: "NYC", coordinate: nycCoord),
endLocation: LocationInput(name: "LA", coordinate: laCoord),
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -82,8 +82,8 @@ struct ScenarioPlannerFactoryTests {
let prefs = TripPreferences(
planningMode: .dateRange,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -108,8 +108,8 @@ struct ScenarioPlannerFactoryTests {
endLocation: LocationInput(name: "LA", coordinate: laCoord), // C condition
sports: [.mlb],
mustSeeGameIds: [game.id], // B condition
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1,
@@ -135,8 +135,8 @@ struct ScenarioPlannerFactoryTests {
endLocation: LocationInput(name: "LA", coordinate: laCoord), // C condition
sports: [.mlb],
mustSeeGameIds: [game.id], // B condition
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -156,8 +156,8 @@ struct ScenarioPlannerFactoryTests {
let prefs = TripPreferences(
planningMode: .followTeam,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1,
@@ -177,8 +177,8 @@ struct ScenarioPlannerFactoryTests {
planningMode: .gameFirst,
sports: [.mlb],
mustSeeGameIds: [game.id],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -200,8 +200,8 @@ struct ScenarioPlannerFactoryTests {
startLocation: LocationInput(name: "NYC", coordinate: nycCoord),
endLocation: LocationInput(name: "LA", coordinate: laCoord),
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -218,8 +218,8 @@ struct ScenarioPlannerFactoryTests {
let prefs = TripPreferences(
planningMode: .dateRange,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -239,8 +239,8 @@ struct ScenarioPlannerFactoryTests {
let prefsA = TripPreferences(
planningMode: .dateRange,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1
@@ -254,8 +254,8 @@ struct ScenarioPlannerFactoryTests {
let prefsD = TripPreferences(
planningMode: .followTeam,
sports: [.mlb],
startDate: Date(),
endDate: Date().addingTimeInterval(86400 * 7),
startDate: TestClock.now,
endDate: TestClock.now.addingTimeInterval(86400 * 7),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1,
@@ -287,7 +287,7 @@ struct ScenarioPlannerFactoryTests {
homeTeamId: "team1",
awayTeamId: "team2",
stadiumId: "stadium1",
dateTime: Date(),
dateTime: TestClock.now,
sport: .mlb,
season: "2026",
isPlayoff: false

View File

@@ -28,7 +28,7 @@ struct TeamFirstIntegrationTests {
@Test("Integration: 3 MLB teams returns top 10 routes")
func integration_3MLBTeams_returnsTop10Routes() {
let baseDate = Date()
let baseDate = TestClock.now
// Create realistic MLB stadiums
let yankeeStadium = Stadium(
@@ -99,7 +99,7 @@ struct TeamFirstIntegrationTests {
// Day 1: Yankees home
// Day 3: Red Sox home
// Day 6: Phillies home (spans 6 days, window fits)
let calendar = Calendar.current
let calendar = TestClock.calendar
let day1 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day3 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 3))!
let day6 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 6))!
@@ -177,7 +177,7 @@ struct TeamFirstIntegrationTests {
@Test("Integration: each route visits all 3 stadiums")
func integration_eachRouteVisitsAll3Stadiums() {
let baseDate = Date()
let baseDate = TestClock.now
let yankeeStadium = makeStadium(
id: "yankee-stadium",
@@ -288,7 +288,7 @@ struct TeamFirstIntegrationTests {
@Test("Integration: total duration within 6 days (teams x 2)")
func integration_totalDurationWithinLimit() {
let baseDate = Date()
let baseDate = TestClock.now
let yankeeStadium = makeStadium(
id: "yankee-stadium",
@@ -311,7 +311,7 @@ struct TeamFirstIntegrationTests {
// Create games that fit within a 6-day window
// For 3 teams, window = 6 days. Games must span at least 6 days.
let calendar = Calendar.current
let calendar = TestClock.calendar
let day1 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day3 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 3))!
let day6 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 6))!
@@ -393,7 +393,7 @@ struct TeamFirstIntegrationTests {
continue
}
let calendar = Calendar.current
let calendar = TestClock.calendar
let tripDays = calendar.dateComponents(
[.day],
from: calendar.startOfDay(for: firstStop.arrivalDate),
@@ -407,7 +407,7 @@ struct TeamFirstIntegrationTests {
@Test("Integration: factory selects ScenarioEPlanner for teamFirst mode")
func integration_factorySelectsScenarioEPlanner() {
let baseDate = Date()
let baseDate = TestClock.now
let prefs = TripPreferences(
planningMode: .teamFirst,
@@ -436,7 +436,7 @@ struct TeamFirstIntegrationTests {
@Test("Integration: factory requires 2+ teams for ScenarioE")
func integration_factoryRequires2TeamsForScenarioE() {
let baseDate = Date()
let baseDate = TestClock.now
// With only 1 team, should NOT select ScenarioE
var prefs = TripPreferences(
@@ -475,7 +475,7 @@ struct TeamFirstIntegrationTests {
@Test("Integration: realistic east coast trip with 4 teams")
func integration_realisticEastCoastTrip() {
let baseDate = Date()
let baseDate = TestClock.now
// East coast stadiums (NYC, Boston, Philly, Baltimore)
let yankeeStadium = makeStadium(
@@ -505,7 +505,7 @@ struct TeamFirstIntegrationTests {
// Create games spread across 8-day window (4 teams * 2 = 8 days)
// For 4 teams, window = 8 days. Games must span at least 8 days.
let calendar = Calendar.current
let calendar = TestClock.calendar
let day1 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day3 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 3))!
let day5 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 5))!

View File

@@ -213,7 +213,7 @@ struct TravelEstimatorTests {
@Test("calculateTravelDays: zero hours returns departure day only")
func calculateTravelDays_zeroHours_returnsDepartureDay() {
let departure = Date()
let departure = TestClock.now
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 0)
#expect(days.count == 1)
@@ -221,7 +221,7 @@ struct TravelEstimatorTests {
@Test("calculateTravelDays: 1-8 hours returns single day")
func calculateTravelDays_1to8Hours_returnsSingleDay() {
let departure = Date()
let departure = TestClock.now
for hours in [1.0, 4.0, 7.0, 8.0] {
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: hours)
@@ -231,7 +231,7 @@ struct TravelEstimatorTests {
@Test("calculateTravelDays: 8.01-16 hours returns two days")
func calculateTravelDays_8to16Hours_returnsTwoDays() {
let departure = Date()
let departure = TestClock.now
for hours in [8.01, 12.0, 16.0] {
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: hours)
@@ -241,7 +241,7 @@ struct TravelEstimatorTests {
@Test("calculateTravelDays: 16.01-24 hours returns three days")
func calculateTravelDays_16to24Hours_returnsThreeDays() {
let departure = Date()
let departure = TestClock.now
for hours in [16.01, 20.0, 24.0] {
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: hours)
@@ -251,9 +251,9 @@ struct TravelEstimatorTests {
@Test("calculateTravelDays: all dates are start of day")
func calculateTravelDays_allDatesAreStartOfDay() {
let calendar = Calendar.current
let calendar = TestClock.calendar
// Use a specific time that's not midnight
var components = calendar.dateComponents([.year, .month, .day], from: Date())
var components = calendar.dateComponents([.year, .month, .day], from: TestClock.now)
components.hour = 14
components.minute = 30
let departure = calendar.date(from: components)!
@@ -269,8 +269,8 @@ struct TravelEstimatorTests {
@Test("calculateTravelDays: consecutive days")
func calculateTravelDays_consecutiveDays() {
let calendar = Calendar.current
let departure = Date()
let calendar = TestClock.calendar
let departure = TestClock.now
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 24)
#expect(days.count == 3)
@@ -414,21 +414,21 @@ struct TravelEstimatorTests {
@Test("Edge: calculateTravelDays with exactly 8 hours")
func edge_calculateTravelDays_exactly8Hours() {
let departure = Date()
let departure = TestClock.now
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 8.0)
#expect(days.count == 1, "Exactly 8 hours should be 1 day")
}
@Test("Edge: calculateTravelDays just over 8 hours")
func edge_calculateTravelDays_justOver8Hours() {
let departure = Date()
let departure = TestClock.now
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 8.001)
#expect(days.count == 2, "Just over 8 hours should be 2 days")
}
@Test("Edge: negative driving hours treated as minimum 1 day")
func edge_negativeDrivingHours() {
let departure = Date()
let departure = TestClock.now
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: -5)
#expect(days.count >= 1, "Negative hours should still return at least 1 day")
}
@@ -445,8 +445,8 @@ struct TravelEstimatorTests {
state: state,
coordinate: coordinate,
games: [],
arrivalDate: Date(),
departureDate: Date(),
arrivalDate: TestClock.now,
departureDate: TestClock.now,
location: LocationInput(name: city, coordinate: coordinate),
firstGameStart: nil
)

View File

@@ -113,7 +113,7 @@ struct TripPlanningEngineTests {
@Test("effectiveTripDuration: calculates from date range when tripDuration is nil")
func effectiveTripDuration_calculated() {
let calendar = Calendar.current
let calendar = TestClock.calendar
let startDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
let endDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 22))!