fix: resolve 4 UI/planning bugs from issue tracker
- Lock all maps to North America (no pan/zoom) in ProgressMapView and TripDetailView - Sort saved trips by most cities (stops count) - Filter cross-country trips to top 2 by stops on home screen - Use LocationSearchSheet for Follow Team home location (consistent with must-stop) - Initialize DateRangePicker to show selected dates on appear Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,7 +78,8 @@ struct ScenarioAPlannerTests {
|
||||
teams: [UUID: Team] = [:],
|
||||
allowRepeatCities: Bool = true,
|
||||
numberOfDrivers: Int = 1,
|
||||
maxDrivingHoursPerDriver: Double = 8.0
|
||||
maxDrivingHoursPerDriver: Double = 8.0,
|
||||
mustStopLocations: [LocationInput] = []
|
||||
) -> PlanningRequest {
|
||||
let preferences = TripPreferences(
|
||||
planningMode: .dateRange,
|
||||
@@ -86,6 +87,7 @@ struct ScenarioAPlannerTests {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
leisureLevel: .moderate,
|
||||
mustStopLocations: mustStopLocations,
|
||||
numberOfDrivers: numberOfDrivers,
|
||||
maxDrivingHoursPerDriver: maxDrivingHoursPerDriver,
|
||||
allowRepeatCities: allowRepeatCities
|
||||
@@ -465,4 +467,230 @@ struct ScenarioAPlannerTests {
|
||||
"Should have options with relaxed driver constraints")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 4D: Must-Stop Filtering (Issues #4 & #8)
|
||||
|
||||
@Test("4.10 - Must-stop filters to games in that city")
|
||||
func test_planByDates_MustStop_FiltersToGamesInCity() {
|
||||
// Setup: Games in Chicago, Milwaukee, Detroit
|
||||
// Must-stop = Chicago → should only return Chicago games
|
||||
let chicagoId = UUID()
|
||||
let milwaukeeId = UUID()
|
||||
let detroitId = UUID()
|
||||
|
||||
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
|
||||
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
|
||||
let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)
|
||||
|
||||
let stadiums = [chicagoId: chicago, milwaukeeId: milwaukee, detroitId: detroit]
|
||||
|
||||
let chicagoGame = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
|
||||
let milwaukeeGame = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 7, hour: 19))
|
||||
let detroitGame = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 9, hour: 19))
|
||||
|
||||
let mustStop = LocationInput(name: "Chicago")
|
||||
|
||||
let request = makePlanningRequest(
|
||||
startDate: makeDate(day: 4, hour: 0),
|
||||
endDate: makeDate(day: 10, hour: 23),
|
||||
games: [chicagoGame, milwaukeeGame, detroitGame],
|
||||
stadiums: stadiums,
|
||||
mustStopLocations: [mustStop]
|
||||
)
|
||||
|
||||
// Execute
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Verify: Should succeed and ONLY include Chicago games
|
||||
#expect(result.isSuccess, "Should succeed with must-stop in Chicago")
|
||||
#expect(!result.options.isEmpty, "Should return at least one option")
|
||||
|
||||
// All games in result should be in Chicago only
|
||||
for option in result.options {
|
||||
let allGameIds = Set(option.stops.flatMap { $0.games })
|
||||
#expect(allGameIds.contains(chicagoGame.id), "Should include Chicago game")
|
||||
#expect(!allGameIds.contains(milwaukeeGame.id), "Should NOT include Milwaukee game")
|
||||
#expect(!allGameIds.contains(detroitGame.id), "Should NOT include Detroit game")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("4.11 - Must-stop with no matching games returns failure")
|
||||
func test_planByDates_MustStop_NoMatchingGames_ReturnsFailure() {
|
||||
// Setup: Games only in Milwaukee and Detroit
|
||||
// Must-stop = Chicago → no games there, should fail
|
||||
let milwaukeeId = UUID()
|
||||
let detroitId = UUID()
|
||||
|
||||
let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
|
||||
let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)
|
||||
|
||||
let stadiums = [milwaukeeId: milwaukee, detroitId: detroit]
|
||||
|
||||
let milwaukeeGame = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 7, hour: 19))
|
||||
let detroitGame = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 9, hour: 19))
|
||||
|
||||
let mustStop = LocationInput(name: "Chicago")
|
||||
|
||||
let request = makePlanningRequest(
|
||||
startDate: makeDate(day: 4, hour: 0),
|
||||
endDate: makeDate(day: 10, hour: 23),
|
||||
games: [milwaukeeGame, detroitGame],
|
||||
stadiums: stadiums,
|
||||
mustStopLocations: [mustStop]
|
||||
)
|
||||
|
||||
// Execute
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Verify: Should fail with noGamesInRange (no home games in Chicago)
|
||||
#expect(!result.isSuccess, "Should fail when no games in must-stop city")
|
||||
#expect(result.failure?.reason == .noGamesInRange,
|
||||
"Should return noGamesInRange failure")
|
||||
}
|
||||
|
||||
@Test("4.12 - Must-stop only returns HOME games (Issue #8)")
|
||||
func test_planByDates_MustStop_OnlyReturnsHomeGames() {
|
||||
// Setup: Cubs home game in Chicago + Cubs away game in Milwaukee (playing at Milwaukee)
|
||||
// Must-stop = Chicago → should ONLY return the Chicago home game
|
||||
// This tests Issue #8: "Must stop needs to be home team"
|
||||
let chicagoStadiumId = UUID()
|
||||
let milwaukeeStadiumId = UUID()
|
||||
let cubsTeamId = UUID()
|
||||
let brewersTeamId = UUID()
|
||||
|
||||
let wrigleyField = makeStadium(id: chicagoStadiumId, city: "Chicago", lat: 41.9484, lon: -87.6553)
|
||||
let millerPark = makeStadium(id: milwaukeeStadiumId, city: "Milwaukee", lat: 43.0280, lon: -87.9712)
|
||||
|
||||
let stadiums = [chicagoStadiumId: wrigleyField, milwaukeeStadiumId: millerPark]
|
||||
|
||||
// Cubs HOME game at Wrigley (Chicago)
|
||||
let cubsHomeGame = makeGame(
|
||||
stadiumId: chicagoStadiumId,
|
||||
homeTeamId: cubsTeamId,
|
||||
awayTeamId: brewersTeamId,
|
||||
dateTime: makeDate(day: 5, hour: 19)
|
||||
)
|
||||
|
||||
// Cubs AWAY game at Miller Park (Milwaukee) - Cubs are playing but NOT at home
|
||||
let cubsAwayGame = makeGame(
|
||||
stadiumId: milwaukeeStadiumId,
|
||||
homeTeamId: brewersTeamId,
|
||||
awayTeamId: cubsTeamId,
|
||||
dateTime: makeDate(day: 7, hour: 19)
|
||||
)
|
||||
|
||||
let mustStop = LocationInput(name: "Chicago")
|
||||
|
||||
let request = makePlanningRequest(
|
||||
startDate: makeDate(day: 4, hour: 0),
|
||||
endDate: makeDate(day: 10, hour: 23),
|
||||
games: [cubsHomeGame, cubsAwayGame],
|
||||
stadiums: stadiums,
|
||||
mustStopLocations: [mustStop]
|
||||
)
|
||||
|
||||
// Execute
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Verify: Should succeed with ONLY the Chicago home game
|
||||
#expect(result.isSuccess, "Should succeed with Chicago home game")
|
||||
#expect(!result.options.isEmpty, "Should return at least one option")
|
||||
|
||||
// The away game in Milwaukee should NOT be included even though Cubs are playing
|
||||
for option in result.options {
|
||||
let allGameIds = Set(option.stops.flatMap { $0.games })
|
||||
#expect(allGameIds.contains(cubsHomeGame.id), "Should include Cubs HOME game in Chicago")
|
||||
#expect(!allGameIds.contains(cubsAwayGame.id), "Should NOT include Cubs AWAY game in Milwaukee")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("4.13 - Must-stop with partial city name match works")
|
||||
func test_planByDates_MustStop_PartialCityMatch_Works() {
|
||||
// Setup: User types "Chicago" but stadium city is "Chicago, IL"
|
||||
// Should still match via contains
|
||||
let chicagoId = UUID()
|
||||
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
|
||||
let stadiums = [chicagoId: chicago]
|
||||
|
||||
let game = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
|
||||
|
||||
// User might type partial name
|
||||
let mustStop = LocationInput(name: "Chicago, IL")
|
||||
|
||||
let request = makePlanningRequest(
|
||||
startDate: makeDate(day: 4, hour: 0),
|
||||
endDate: makeDate(day: 10, hour: 23),
|
||||
games: [game],
|
||||
stadiums: stadiums,
|
||||
mustStopLocations: [mustStop]
|
||||
)
|
||||
|
||||
// Execute
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Verify: Should still find Chicago games with partial match
|
||||
#expect(result.isSuccess, "Should succeed with partial city name match")
|
||||
#expect(!result.options.isEmpty, "Should return options")
|
||||
}
|
||||
|
||||
@Test("4.14 - Must-stop case insensitive")
|
||||
func test_planByDates_MustStop_CaseInsensitive() {
|
||||
// Setup: Must-stop = "CHICAGO" (uppercase) should match "Chicago"
|
||||
let chicagoId = UUID()
|
||||
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
|
||||
let stadiums = [chicagoId: chicago]
|
||||
|
||||
let game = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
|
||||
|
||||
let mustStop = LocationInput(name: "CHICAGO")
|
||||
|
||||
let request = makePlanningRequest(
|
||||
startDate: makeDate(day: 4, hour: 0),
|
||||
endDate: makeDate(day: 10, hour: 23),
|
||||
games: [game],
|
||||
stadiums: stadiums,
|
||||
mustStopLocations: [mustStop]
|
||||
)
|
||||
|
||||
// Execute
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Verify: Case insensitive match
|
||||
#expect(result.isSuccess, "Should succeed with case-insensitive match")
|
||||
}
|
||||
|
||||
@Test("4.15 - Multiple games in must-stop city all included")
|
||||
func test_planByDates_MustStop_MultipleGamesInCity_AllIncluded() {
|
||||
// Setup: Multiple games in Chicago on different days
|
||||
let chicagoId = UUID()
|
||||
let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
|
||||
let stadiums = [chicagoId: chicago]
|
||||
|
||||
let game1 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
|
||||
let game2 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 7, hour: 13))
|
||||
let game3 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 9, hour: 19))
|
||||
|
||||
let mustStop = LocationInput(name: "Chicago")
|
||||
|
||||
let request = makePlanningRequest(
|
||||
startDate: makeDate(day: 4, hour: 0),
|
||||
endDate: makeDate(day: 10, hour: 23),
|
||||
games: [game1, game2, game3],
|
||||
stadiums: stadiums,
|
||||
mustStopLocations: [mustStop]
|
||||
)
|
||||
|
||||
// Execute
|
||||
let result = planner.plan(request: request)
|
||||
|
||||
// Verify: All Chicago games should be included
|
||||
#expect(result.isSuccess, "Should succeed with multiple games in must-stop city")
|
||||
|
||||
if let option = result.options.first {
|
||||
let allGameIds = Set(option.stops.flatMap { $0.games })
|
||||
#expect(allGameIds.contains(game1.id), "Should include first Chicago game")
|
||||
#expect(allGameIds.contains(game2.id), "Should include second Chicago game")
|
||||
#expect(allGameIds.contains(game3.id), "Should include third Chicago game")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user