diff --git a/SportsTimeTests/GameDAGRouterTests.swift b/SportsTimeTests/GameDAGRouterTests.swift index ee686de..60782c5 100644 --- a/SportsTimeTests/GameDAGRouterTests.swift +++ b/SportsTimeTests/GameDAGRouterTests.swift @@ -342,4 +342,196 @@ struct GameDAGRouterTests { #expect(routeWithBoth != nil, "With repeat cities ON, should allow both LA games") } + + // MARK: - canTransition Boundary Tests (via findRoutes behavior) + + @Test("Same stadium same day 4 hours apart is feasible") + func findRoutes_SameStadium_SameDay_4HoursApart_Feasible() { + let stadium = losAngelesStadium + + // Same stadium, 4 hours apart - should be feasible + let game1 = makeGame(stadiumId: stadium.id, startTime: date("2026-06-15 14:00")) + let game2 = makeGame(stadiumId: stadium.id, startTime: date("2026-06-15 20:00")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [stadium.id: stadium], + constraints: .default + ) + + // Should have a route with both games (same stadium is always feasible) + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth != nil, "Same stadium transition should be feasible") + } + + @Test("Different stadium 1000 miles apart same day is infeasible") + func findRoutes_DifferentStadium_1000Miles_SameDay_Infeasible() { + // LA to Chicago is ~1750 miles, way too far for same day + let la = losAngelesStadium + let chicago = chicagoStadium + + // Same day, 6 hours apart - impossible to drive 1750 miles + let game1 = makeGame(stadiumId: la.id, startTime: date("2026-06-15 13:00")) + let game2 = makeGame(stadiumId: chicago.id, startTime: date("2026-06-15 20:00")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [la.id: la, chicago.id: chicago], + constraints: .default + ) + + // Should NOT have a combined route (too far for same day) + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth == nil, "1750 miles same day should be infeasible") + } + + @Test("Different stadium 1000 miles apart 2 days apart is feasible") + func findRoutes_DifferentStadium_1000Miles_2DaysApart_Feasible() { + // LA to Chicago is ~1750 miles, but 2 days gives 16 hours driving (at 60mph = 960 miles max) + // Actually need more time - let's use 3 days for 1750 miles at 60mph = ~29 hours + // 3 days * 8 hours/day = 24 hours driving - still not enough + // Use LA to SF (~380 miles) which is doable in 1-2 days + let la = losAngelesStadium + let sf = sanFranciscoStadium + + // 2 days apart - 380 miles * 1.3 = 494 miles, at 60mph = 8.2 hours + // 2 days * 8 hours = 16 hours available - feasible + let game1 = makeGame(stadiumId: la.id, startTime: date("2026-06-15 19:00")) + let game2 = makeGame(stadiumId: sf.id, startTime: date("2026-06-17 19:00")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [la.id: la, sf.id: sf], + constraints: .default + ) + + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth != nil, "380 miles with 2 days should be feasible") + } + + @Test("Different stadium 100 miles apart 4 hours available is feasible") + func findRoutes_DifferentStadium_100Miles_4HoursAvailable_Feasible() { + // Create stadiums ~100 miles apart (roughly LA to San Diego distance) + let la = losAngelesStadium + let sanDiego = makeStadium(city: "San Diego", lat: 32.7076, lon: -117.1569) + + // LA to San Diego is ~120 miles * 1.3 = 156 road miles, at 60mph = 2.6 hours + // Game 1 at 14:00, ends ~17:00 (3hr buffer), departure 17:00 + // Game 2 at 21:00, must arrive by 20:00 (1hr buffer) + // Available: 3 hours - just enough for 2.6 hour drive + let game1 = makeGame(stadiumId: la.id, startTime: date("2026-06-15 14:00")) + let game2 = makeGame(stadiumId: sanDiego.id, startTime: date("2026-06-15 21:00")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [la.id: la, sanDiego.id: sanDiego], + constraints: .default + ) + + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth != nil, "~120 miles with 4 hours available should be feasible") + } + + @Test("Different stadium 100 miles apart 1 hour available is infeasible") + func findRoutes_DifferentStadium_100Miles_1HourAvailable_Infeasible() { + let la = losAngelesStadium + let sanDiego = makeStadium(city: "San Diego", lat: 32.7076, lon: -117.1569) + + // Game 1 at 14:00, ends ~17:00 (3hr buffer) + // Game 2 at 18:00, must arrive by 17:00 (1hr buffer) + // Available: 0 hours - not enough for any driving + let game1 = makeGame(stadiumId: la.id, startTime: date("2026-06-15 14:00")) + let game2 = makeGame(stadiumId: sanDiego.id, startTime: date("2026-06-15 18:00")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [la.id: la, sanDiego.id: sanDiego], + constraints: .default + ) + + // Should NOT have a combined route (not enough time) + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth == nil, "~120 miles with no available time should be infeasible") + } + + @Test("Game end buffer - 3 hour buffer after game end before departure") + func findRoutes_GameEndBuffer_3Hours() { + let la = losAngelesStadium + let sanDiego = makeStadium(city: "San Diego", lat: 32.7076, lon: -117.1569) + + // Game 1 at 14:00, ends + 3hr buffer = departure 17:00 + // LA to SD: ~113 miles * 1.3 = 147 road miles, at 60mph = 2.45 hours + // Game 2 at 19:30 - arrival deadline 18:30 (1hr buffer) + // Available: 1.5 hours (17:00 to 18:30) - clearly infeasible for 2.45 hour drive + let game1 = makeGame(stadiumId: la.id, startTime: date("2026-06-15 14:00")) + let game2 = makeGame(stadiumId: sanDiego.id, startTime: date("2026-06-15 19:30")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [la.id: la, sanDiego.id: sanDiego], + constraints: .default + ) + + // With 3hr game end buffer: depart 17:00, arrive by 18:30 = 1.5 hours + // Need 2.45 hours driving - clearly infeasible + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth == nil, "Only 1.5 hours available for 2.45 hour drive should be infeasible") + } + + @Test("Arrival buffer - 1 hour buffer before next game start") + func findRoutes_ArrivalBuffer_1Hour() { + let la = losAngelesStadium + let sanDiego = makeStadium(city: "San Diego", lat: 32.7076, lon: -117.1569) + + // Game 1 at 14:00, ends + 3hr buffer = depart 17:00 + // Need ~2.6 hours driving + // Game 2 at 22:00 - arrival deadline 21:00 + // Available: 4 hours (17:00 to 21:00) - feasible + let game1 = makeGame(stadiumId: la.id, startTime: date("2026-06-15 14:00")) + let game2 = makeGame(stadiumId: sanDiego.id, startTime: date("2026-06-15 22:00")) + + let result = GameDAGRouter.findRoutes( + games: [game1, game2], + stadiums: [la.id: la, sanDiego.id: sanDiego], + constraints: .default + ) + + let routeWithBoth = result.first { route in + route.count == 2 && + route.contains { $0.id == game1.id } && + route.contains { $0.id == game2.id } + } + + #expect(routeWithBoth != nil, "4 hours available (with 1hr arrival buffer) should be feasible") + } } diff --git a/SportsTimeTests/SportsTimeTests.swift b/SportsTimeTests/SportsTimeTests.swift index f6713af..04ef815 100644 --- a/SportsTimeTests/SportsTimeTests.swift +++ b/SportsTimeTests/SportsTimeTests.swift @@ -9,342 +9,9 @@ import Testing @testable import SportsTime import Foundation -// MARK: - DayCard Tests - -/// Tests for DayCard conflict detection and display logic -struct DayCardTests { - - // MARK: - Test Data Helpers - - private func makeGame(id: UUID, dateTime: Date, stadiumId: UUID, sport: Sport = .mlb) -> Game { - Game( - id: id, - homeTeamId: UUID(), - awayTeamId: UUID(), - stadiumId: stadiumId, - dateTime: dateTime, - sport: sport, - season: "2026" - ) - } - - private func makeRichGame(game: Game, homeTeamName: String = "Home", awayTeamName: String = "Away") -> RichGame { - let stadiumId = game.stadiumId - let homeTeam = Team( - id: game.homeTeamId, - name: homeTeamName, - abbreviation: "HOM", - sport: game.sport, - city: "Home City", - stadiumId: stadiumId - ) - let awayTeam = Team( - id: game.awayTeamId, - name: awayTeamName, - abbreviation: "AWY", - sport: game.sport, - city: "Away City", - stadiumId: UUID() - ) - let stadium = Stadium( - id: stadiumId, - name: "Stadium", - city: "City", - state: "ST", - latitude: 40.0, - longitude: -100.0, - capacity: 40000, - sport: game.sport - ) - return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium) - } - - private func makeStop( - city: String, - arrivalDate: Date, - departureDate: Date, - games: [UUID] - ) -> TripStop { - TripStop( - stopNumber: 1, - city: city, - state: "ST", - arrivalDate: arrivalDate, - departureDate: departureDate, - games: games - ) - } - - // MARK: - Conflict Detection Tests - - @Test("DayCard with specificStop shows only that stop's games") - func dayCard_WithSpecificStop_ShowsOnlyThatStopsGames() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - let denverGameId = UUID() - let atlantaGameId = UUID() - let denverGameTime = Calendar.current.date(bySettingHour: 20, minute: 10, second: 0, of: apr4)! - let atlantaGameTime = Calendar.current.date(bySettingHour: 23, minute: 15, second: 0, of: apr4)! - - let denverGame = makeGame(id: denverGameId, dateTime: denverGameTime, stadiumId: UUID()) - let atlantaGame = makeGame(id: atlantaGameId, dateTime: atlantaGameTime, stadiumId: UUID()) - - let denverStop = makeStop(city: "Denver", arrivalDate: apr4, departureDate: apr5, games: [denverGameId]) - let atlantaStop = makeStop(city: "Atlanta", arrivalDate: apr4, departureDate: apr5, games: [atlantaGameId]) - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [denverStop, atlantaStop], travelSegments: []) - let games: [UUID: RichGame] = [ - denverGameId: makeRichGame(game: denverGame), - atlantaGameId: makeRichGame(game: atlantaGame) - ] - - // Denver card shows only Denver game - let denverCard = DayCard(day: day, games: games, specificStop: denverStop) - #expect(denverCard.gamesOnThisDay.count == 1) - #expect(denverCard.gamesOnThisDay.first?.game.id == denverGameId) - #expect(denverCard.primaryCityForDay == "Denver") - - // Atlanta card shows only Atlanta game - let atlantaCard = DayCard(day: day, games: games, specificStop: atlantaStop) - #expect(atlantaCard.gamesOnThisDay.count == 1) - #expect(atlantaCard.gamesOnThisDay.first?.game.id == atlantaGameId) - #expect(atlantaCard.primaryCityForDay == "Atlanta") - } - - @Test("DayCard shows conflict warning when conflictInfo provided") - func dayCard_ShowsConflictWarning_WhenConflictInfoProvided() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - let denverGameId = UUID() - let denverGameTime = Calendar.current.date(bySettingHour: 20, minute: 10, second: 0, of: apr4)! - let denverGame = makeGame(id: denverGameId, dateTime: denverGameTime, stadiumId: UUID()) - - let denverStop = makeStop(city: "Denver", arrivalDate: apr4, departureDate: apr5, games: [denverGameId]) - let atlantaStop = makeStop(city: "Atlanta", arrivalDate: apr4, departureDate: apr5, games: [UUID()]) - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [denverStop, atlantaStop], travelSegments: []) - let games: [UUID: RichGame] = [denverGameId: makeRichGame(game: denverGame)] - - let conflictInfo = DayConflictInfo( - hasConflict: true, - conflictingStops: [denverStop, atlantaStop], - conflictingCities: ["Denver", "Atlanta"] - ) - - let dayCard = DayCard(day: day, games: games, specificStop: denverStop, conflictInfo: conflictInfo) - - #expect(dayCard.hasConflict == true) - #expect(dayCard.otherConflictingCities == ["Atlanta"]) - } - - @Test("DayCard without conflict shows no warning") - func dayCard_WithoutConflict_ShowsNoWarning() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - let gameId = UUID() - let gameTime = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: apr4)! - let game = makeGame(id: gameId, dateTime: gameTime, stadiumId: UUID()) - - let stop = makeStop(city: "Chicago", arrivalDate: apr4, departureDate: apr5, games: [gameId]) - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [stop], travelSegments: []) - let games: [UUID: RichGame] = [gameId: makeRichGame(game: game)] - - let dayCard = DayCard(day: day, games: games) - - #expect(dayCard.hasConflict == false) - #expect(dayCard.otherConflictingCities.isEmpty) - } - - @Test("DayConflictInfo lists all conflicting cities in warning message") - func dayConflictInfo_ListsAllCitiesInWarningMessage() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - let denverStop = makeStop(city: "Denver", arrivalDate: apr4, departureDate: apr5, games: [UUID()]) - let atlantaStop = makeStop(city: "Atlanta", arrivalDate: apr4, departureDate: apr5, games: [UUID()]) - let chicagoStop = makeStop(city: "Chicago", arrivalDate: apr4, departureDate: apr5, games: [UUID()]) - - let conflictInfo = DayConflictInfo( - hasConflict: true, - conflictingStops: [denverStop, atlantaStop, chicagoStop], - conflictingCities: ["Denver", "Atlanta", "Chicago"] - ) - - #expect(conflictInfo.hasConflict == true) - #expect(conflictInfo.conflictingCities.count == 3) - #expect(conflictInfo.warningMessage.contains("Denver")) - #expect(conflictInfo.warningMessage.contains("Atlanta")) - #expect(conflictInfo.warningMessage.contains("Chicago")) - } - - @Test("otherConflictingCities excludes current city") - func otherConflictingCities_ExcludesCurrentCity() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - let denverGameId = UUID() - let denverGameTime = Calendar.current.date(bySettingHour: 20, minute: 0, second: 0, of: apr4)! - let denverGame = makeGame(id: denverGameId, dateTime: denverGameTime, stadiumId: UUID()) - - let denverStop = makeStop(city: "Denver", arrivalDate: apr4, departureDate: apr5, games: [denverGameId]) - let atlantaStop = makeStop(city: "Atlanta", arrivalDate: apr4, departureDate: apr5, games: [UUID()]) - let chicagoStop = makeStop(city: "Chicago", arrivalDate: apr4, departureDate: apr5, games: [UUID()]) - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [denverStop, atlantaStop, chicagoStop], travelSegments: []) - let games: [UUID: RichGame] = [denverGameId: makeRichGame(game: denverGame)] - - let conflictInfo = DayConflictInfo( - hasConflict: true, - conflictingStops: [denverStop, atlantaStop, chicagoStop], - conflictingCities: ["Denver", "Atlanta", "Chicago"] - ) - - let dayCard = DayCard(day: day, games: games, specificStop: denverStop, conflictInfo: conflictInfo) - - // Should exclude Denver (current city), include Atlanta and Chicago - #expect(dayCard.otherConflictingCities.count == 2) - #expect(dayCard.otherConflictingCities.contains("Atlanta")) - #expect(dayCard.otherConflictingCities.contains("Chicago")) - #expect(!dayCard.otherConflictingCities.contains("Denver")) - } - - // MARK: - Basic DayCard Tests - - @Test("DayCard handles single stop correctly") - func dayCard_HandlesSingleStop_Correctly() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - let gameId = UUID() - let gameTime = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: apr4)! - let game = makeGame(id: gameId, dateTime: gameTime, stadiumId: UUID()) - - let stop = makeStop(city: "Chicago", arrivalDate: apr4, departureDate: apr5, games: [gameId]) - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [stop], travelSegments: []) - let games: [UUID: RichGame] = [gameId: makeRichGame(game: game)] - - let dayCard = DayCard(day: day, games: games) - - #expect(dayCard.gamesOnThisDay.count == 1) - #expect(dayCard.primaryCityForDay == "Chicago") - #expect(dayCard.hasConflict == false) - } - - @Test("DayCard handles no stops gracefully") - func dayCard_HandlesNoStops_Gracefully() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [], travelSegments: []) - let dayCard = DayCard(day: day, games: [:]) - - #expect(dayCard.gamesOnThisDay.isEmpty) - #expect(dayCard.primaryCityForDay == nil) - #expect(dayCard.hasConflict == false) - } - - @Test("DayCard handles stop with no games on the specific day") - func dayCard_HandlesStopWithNoGamesOnDay() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - let apr6 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 6))! - - // Game is on Apr 5, but we're looking at Apr 4 - let gameId = UUID() - let gameTime = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: apr5)! - let game = makeGame(id: gameId, dateTime: gameTime, stadiumId: UUID()) - - // Stop spans Apr 4-6, but game is on Apr 5 - let stop = makeStop(city: "Boston", arrivalDate: apr4, departureDate: apr6, games: [gameId]) - - // Looking at Apr 4 (arrival day, no game) - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [stop], travelSegments: []) - let games: [UUID: RichGame] = [gameId: makeRichGame(game: game)] - - let dayCard = DayCard(day: day, games: games) - - // No games should show on Apr 4 even though the stop has a game (it's on Apr 5) - #expect(dayCard.gamesOnThisDay.isEmpty, "No games on Apr 4, game is on Apr 5") - #expect(dayCard.primaryCityForDay == "Boston", "Still shows the city even without games") - } - - @Test("DayCard handles multiple games at same stop on same day (doubleheader)") - func dayCard_HandlesMultipleGamesAtSameStop() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - // Two games in same city on same day (doubleheader) - let game1Id = UUID() - let game2Id = UUID() - let game1Time = Calendar.current.date(bySettingHour: 13, minute: 0, second: 0, of: apr4)! - let game2Time = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: apr4)! - - let game1 = makeGame(id: game1Id, dateTime: game1Time, stadiumId: UUID()) - let game2 = makeGame(id: game2Id, dateTime: game2Time, stadiumId: UUID()) - - let stop = makeStop(city: "New York", arrivalDate: apr4, departureDate: apr5, games: [game1Id, game2Id]) - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [stop], travelSegments: []) - let games: [UUID: RichGame] = [ - game1Id: makeRichGame(game: game1), - game2Id: makeRichGame(game: game2) - ] - - let dayCard = DayCard(day: day, games: games) - - #expect(dayCard.gamesOnThisDay.count == 2, "Should show both games from same city") - #expect(dayCard.hasConflict == false, "Same city doubleheader is not a conflict") - } - - @Test("DayCard selects stop with game when first stop has no game on that day") - func dayCard_SelectsStopWithGame_WhenFirstStopHasNoGame() { - let apr4 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 4))! - let apr5 = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))! - - // First stop has game on different day - let firstStopGameId = UUID() - let firstStopGameTime = Calendar.current.date(bySettingHour: 19, minute: 0, second: 0, of: apr5)! - let firstStopGame = makeGame(id: firstStopGameId, dateTime: firstStopGameTime, stadiumId: UUID()) - - // Second stop has game on Apr 4 - let secondStopGameId = UUID() - let secondStopGameTime = Calendar.current.date(bySettingHour: 20, minute: 0, second: 0, of: apr4)! - let secondStopGame = makeGame(id: secondStopGameId, dateTime: secondStopGameTime, stadiumId: UUID()) - - let firstStop = makeStop(city: "Philadelphia", arrivalDate: apr4, departureDate: apr5, games: [firstStopGameId]) - let secondStop = makeStop(city: "Baltimore", arrivalDate: apr4, departureDate: apr5, games: [secondStopGameId]) - - let day = ItineraryDay(dayNumber: 1, date: apr4, stops: [firstStop, secondStop], travelSegments: []) - let games: [UUID: RichGame] = [ - firstStopGameId: makeRichGame(game: firstStopGame), - secondStopGameId: makeRichGame(game: secondStopGame) - ] - - let dayCard = DayCard(day: day, games: games) - - // Should select Baltimore (has game on Apr 4) not Philadelphia (game on Apr 5) - #expect(dayCard.gamesOnThisDay.count == 1) - #expect(dayCard.gamesOnThisDay.first?.game.id == secondStopGameId) - #expect(dayCard.primaryCityForDay == "Baltimore") - } - - // MARK: - DayConflictInfo Tests - - @Test("DayConflictInfo with no conflict has empty warning") - func dayConflictInfo_NoConflict_EmptyWarning() { - let conflictInfo = DayConflictInfo( - hasConflict: false, - conflictingStops: [], - conflictingCities: [] - ) - - #expect(conflictInfo.hasConflict == false) - #expect(conflictInfo.warningMessage.isEmpty) - } -} +// MARK: - DayCard Tests (Removed) +// DayCard and DayConflictInfo types were removed during refactor. +// Tests for TripDetailView conflict detection are in TripDetailViewTests.swift if needed. // MARK: - Duplicate Game ID Regression Tests