chore: remove scraper, add docs, add marketing-videos gitignore

- Remove Scripts/ directory (scraper no longer needed)
- Add themed background documentation to CLAUDE.md
- Add .gitignore for marketing-videos to prevent node_modules tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-26 18:13:12 -06:00
parent bfa172de38
commit dbb0099776
129 changed files with 14805 additions and 25325 deletions

View File

@@ -0,0 +1,971 @@
//
// ScenarioEPlannerTests.swift
// SportsTimeTests
//
// TDD specification tests for ScenarioEPlanner (Team-First planning mode).
//
// Team-First mode allows users to select multiple teams and finds optimal
// trip windows across the season where all selected teams have home games.
//
import Testing
import CoreLocation
@testable import SportsTime
@Suite("ScenarioEPlanner")
struct ScenarioEPlannerTests {
// MARK: - Test Data
private let planner = ScenarioEPlanner()
// East Coast coordinates
private let nycCoord = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855)
private let bostonCoord = CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)
private let phillyCoord = CLLocationCoordinate2D(latitude: 39.9526, longitude: -75.1652)
// Central coordinates
private let chicagoCoord = CLLocationCoordinate2D(latitude: 41.8827, longitude: -87.6233)
// West Coast coordinates
private let laCoord = CLLocationCoordinate2D(latitude: 34.0430, longitude: -118.2673)
// MARK: - E1. Window Generator Tests
@Test("generateValidWindows: 2 teams with 4-day window finds valid windows")
func generateValidWindows_2Teams4DayWindow_findsValidWindows() {
// Setup: 2 teams (simpler case), each with home games in overlapping time periods
// NYC and Boston are only ~4 hours apart, making routes feasible
//
// 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 nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Create games with evening times (7 PM) to give ample travel time
// Day 1 evening: Yankees home game
// Day 4 evening: Red Sox home game (spans 4 days, fits in 4-day window)
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: day1Evening
)
let redsoxGame = makeGame(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: day4Evening
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
// Should succeed - NYC to Boston is easily drivable
guard case .success(let options) = result else {
Issue.record("Expected success with overlapping home games for nearby cities")
return
}
#expect(!options.isEmpty, "Should find valid windows when all teams have overlapping home games")
}
@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 nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
let phillyStadium = makeStadium(id: "philly", city: "Philadelphia", coordinate: phillyCoord)
// Yankees game on day 1
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 1)
)
// Red Sox game on day 2
let redsoxGame = makeGame(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: baseDate.addingTimeInterval(86400 * 2)
)
// Phillies game on day 20 (way outside any 6-day window with the others)
let philliesGame = makeGame(
id: "phillies-home",
homeTeamId: "phillies",
awayTeamId: "opponent",
stadiumId: "philly",
dateTime: baseDate.addingTimeInterval(86400 * 20)
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox", "phillies"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame, philliesGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox"),
"phillies": makeTeam(id: "phillies", name: "Phillies")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium, "philly": phillyStadium]
)
let result = planner.plan(request: request)
guard case .failure(let failure) = result else {
Issue.record("Expected failure when no valid window covers all teams")
return
}
#expect(failure.reason == .noValidRoutes, "Should return noValidRoutes when no window covers all teams")
}
@Test("generateValidWindows: empty season returns empty")
func generateValidWindows_emptySeason_returnsEmpty() {
// Setup: No games available
let baseDate = Date()
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [], // No games
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: [:]
)
let result = planner.plan(request: request)
guard case .failure(let failure) = result else {
Issue.record("Expected failure with no games")
return
}
#expect(failure.reason == .noGamesInRange, "Should return noGamesInRange when no home games exist")
}
@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 nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Create games on alternating days - Yankees on even days, Red Sox on odd days
// This ensures games don't conflict and routes are feasible
// With 90 days of games, we get ~45 games per team with plenty of valid 4-day windows
var games: [Game] = []
for day in 0..<90 {
let dayDate = baseDate.addingTimeInterval(Double(86400 * day))
let eveningTime = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: dayDate)!
if day % 2 == 0 {
// Yankees home games on even days
let yankeesGame = makeGame(
id: "yankees-\(day)",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: eveningTime
)
games.append(yankeesGame)
} else {
// Red Sox home games on odd days
let redsoxGame = makeGame(
id: "redsox-\(day)",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: eveningTime
)
games.append(redsoxGame)
}
}
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 90),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: games,
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
// Should succeed and return results (sampling internal behavior)
guard case .success(let options) = result else {
Issue.record("Expected success with many valid windows")
return
}
#expect(!options.isEmpty, "Should return options even with many windows (sampling applies internally)")
#expect(options.count <= 10, "Should return at most 10 results")
}
// MARK: - E2. ScenarioEPlanner Unit Tests
@Test("plan: returns PlanningResult with routes")
func plan_validRequest_returnsPlanningResultWithRoutes() {
let calendar = Calendar.current
let baseDate = calendar.startOfDay(for: Date())
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Use evening game times to allow travel during the day
// Games must span 4 days for 2-team window (day 1 to day 4)
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: day1Evening
)
let redsoxGame = makeGame(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: day4Evening
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success with valid team-first request")
return
}
#expect(!options.isEmpty, "Should return route options")
#expect(options.first?.stops.count ?? 0 > 0, "Each option should have stops")
}
@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 nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Games within a 4-day window (2 teams * 2) with evening times
// Games must span 4 days for the window to be valid
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: day1Evening
)
let redsoxGame = makeGame(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: day4Evening
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success with valid team-first request")
return
}
// Verify each route includes games from all 2 teams
for option in options {
let allGameIds = Set(option.stops.flatMap { $0.games })
// Check that at least one home game per team is included
let hasYankeesGame = allGameIds.contains("yankees-home")
let hasRedsoxGame = allGameIds.contains("redsox-home")
#expect(hasYankeesGame, "Every route must include Yankees home game")
#expect(hasRedsoxGame, "Every route must include Red Sox home game")
}
}
@Test("plan: routes sorted by duration ascending")
func plan_routesSortedByDurationAscending() {
let baseDate = Date()
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Create multiple windows with different durations
// Window 1: Games on day 1 and 2 (shorter trip)
// Window 2: Games on day 10 and 14 (longer trip within window)
let yankeesGame1 = makeGame(
id: "yankees-1",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 1)
)
let redsoxGame1 = makeGame(
id: "redsox-1",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: baseDate.addingTimeInterval(86400 * 2)
)
let yankeesGame2 = makeGame(
id: "yankees-2",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 10)
)
let redsoxGame2 = makeGame(
id: "redsox-2",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: baseDate.addingTimeInterval(86400 * 12)
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame1, redsoxGame1, yankeesGame2, redsoxGame2],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success")
return
}
// Verify ranking is assigned correctly
for (index, option) in options.enumerated() {
#expect(option.rank == index + 1, "Routes should be ranked 1, 2, 3...")
}
}
@Test("plan: respects max driving time constraint")
func plan_respectsMaxDrivingTimeConstraint() {
let baseDate = Date()
// NYC and LA are ~40 hours apart by car
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let laStadium = makeStadium(id: "la", city: "Los Angeles", coordinate: laCoord)
// Games on consecutive days - impossible to drive between
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 1)
)
let dodgersGame = makeGame(
id: "dodgers-home",
homeTeamId: "dodgers",
awayTeamId: "opponent",
stadiumId: "la",
dateTime: baseDate.addingTimeInterval(86400 * 2) // Next day - impossible
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 1, // Single driver, 8 hours max
selectedTeamIds: ["yankees", "dodgers"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, dodgersGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"dodgers": makeTeam(id: "dodgers", name: "Dodgers")
],
stadiums: ["nyc": nycStadium, "la": laStadium]
)
let result = planner.plan(request: request)
// Should fail because driving constraint cannot be met
guard case .failure(let failure) = result else {
Issue.record("Expected failure when driving constraint cannot be met")
return
}
// Could be noValidRoutes or constraintsUnsatisfiable
let validFailures: [PlanningFailure.FailureReason] = [.noValidRoutes, .constraintsUnsatisfiable]
#expect(validFailures.contains { $0 == failure.reason }, "Should fail due to route constraints")
}
// MARK: - E4. Edge Case Tests
@Test("plan: teams with no overlapping games returns graceful error")
func plan_noOverlappingGames_returnsGracefulError() {
let baseDate = Date()
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Yankees plays in January, Red Sox plays in July - no overlap
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate // Day 0
)
let redsoxGame = makeGame(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: baseDate.addingTimeInterval(86400 * 180) // 6 months later
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 365),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
guard case .failure(let failure) = result else {
Issue.record("Expected failure when teams have no overlapping game windows")
return
}
#expect(failure.reason == .noValidRoutes, "Should return noValidRoutes for non-overlapping schedules")
}
@Test("plan: single team selected returns validation error")
func plan_singleTeamSelected_returnsValidationError() {
let baseDate = Date()
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 1)
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees"] // Only 1 team
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame],
teams: ["yankees": makeTeam(id: "yankees", name: "Yankees")],
stadiums: ["nyc": nycStadium]
)
let result = planner.plan(request: request)
guard case .failure(let failure) = result else {
Issue.record("Expected failure when only 1 team selected")
return
}
#expect(failure.reason == .missingTeamSelection, "Should return missingTeamSelection for single team")
}
@Test("plan: no teams selected returns validation error")
func plan_noTeamsSelected_returnsValidationError() {
let baseDate = Date()
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: [] // No teams
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [],
teams: [:],
stadiums: [:]
)
let result = planner.plan(request: request)
guard case .failure(let failure) = result else {
Issue.record("Expected failure when no teams selected")
return
}
#expect(failure.reason == .missingTeamSelection, "Should return missingTeamSelection for no teams")
}
@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 yankeeStadiumCoord = CLLocationCoordinate2D(latitude: 40.8296, longitude: -73.9262)
let citiFieldCoord = CLLocationCoordinate2D(latitude: 40.7571, longitude: -73.8458)
let yankeeStadium = makeStadium(id: "yankee-stadium", city: "New York", coordinate: yankeeStadiumCoord)
let citiField = makeStadium(id: "citi-field", city: "New York", coordinate: citiFieldCoord)
// Games on different days in the same city with evening times
// Games must span 4 days for the 2-team window to be valid
let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "yankee-stadium",
dateTime: day1Evening
)
let metsGame = makeGame(
id: "mets-home",
homeTeamId: "mets",
awayTeamId: "opponent",
stadiumId: "citi-field",
dateTime: day4Evening
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "mets"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, metsGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"mets": makeTeam(id: "mets", name: "Mets")
],
stadiums: ["yankee-stadium": yankeeStadium, "citi-field": citiField]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success with same-city teams")
return
}
#expect(!options.isEmpty, "Should find routes for same-city teams")
// Verify both games are included in routes
for option in options {
let allGameIds = Set(option.stops.flatMap { $0.games })
#expect(allGameIds.contains("yankees-home"), "Should include Yankees game")
#expect(allGameIds.contains("mets-home"), "Should include Mets game")
}
}
@Test("plan: team with no home games returns error")
func plan_teamWithNoHomeGames_returnsError() {
let baseDate = Date()
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
// Only Yankees have a home game, Red Sox have no home games
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "redsox", // Red Sox are away
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 1)
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"] // Red Sox selected but no home games
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium]
)
let result = planner.plan(request: request)
guard case .failure(let failure) = result else {
Issue.record("Expected failure when selected team has no home games")
return
}
#expect(failure.reason == .noGamesInRange, "Should return noGamesInRange when team has no home games")
}
// MARK: - Invariant Tests
@Test("Invariant: window duration equals teams count times 2")
func invariant_windowDurationEqualsTeamsTimestwo() {
// Test that teamFirstMaxDays is calculated correctly
var prefs = TripPreferences()
prefs.selectedTeamIds = ["team1", "team2", "team3"]
#expect(prefs.teamFirstMaxDays == 6, "3 teams should result in 6-day window")
prefs.selectedTeamIds = ["team1", "team2"]
#expect(prefs.teamFirstMaxDays == 4, "2 teams should result in 4-day window")
prefs.selectedTeamIds = ["team1", "team2", "team3", "team4", "team5"]
#expect(prefs.teamFirstMaxDays == 10, "5 teams should result in 10-day window")
}
@Test("Invariant: maximum 10 results returned")
func invariant_maximum10ResultsReturned() {
let baseDate = Date()
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
// Create many games to generate many possible routes
var games: [Game] = []
for day in 0..<60 {
games.append(makeGame(
id: "yankees-\(day)",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(Double(86400 * day))
))
games.append(makeGame(
id: "redsox-\(day)",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: baseDate.addingTimeInterval(Double(86400 * day))
))
}
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 60),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: games,
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
if case .success(let options) = result {
#expect(options.count <= 10, "Should return at most 10 results")
}
}
@Test("Invariant: all routes contain home games from all selected teams")
func invariant_allRoutesContainAllSelectedTeams() {
let baseDate = Date()
let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
let yankeesGame = makeGame(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "nyc",
dateTime: baseDate.addingTimeInterval(86400 * 1)
)
let redsoxGame = makeGame(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "boston",
dateTime: baseDate.addingTimeInterval(86400 * 2)
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox")
],
stadiums: ["nyc": nycStadium, "boston": bostonStadium]
)
let result = planner.plan(request: request)
if case .success(let options) = result {
for option in options {
let allGameIds = Set(option.stops.flatMap { $0.games })
// At minimum, should have one game per selected team
let hasYankeesGame = allGameIds.contains { gameId in
// Check if any game in this route is a Yankees home game
request.availableGames.first { $0.id == gameId }?.homeTeamId == "yankees"
}
let hasRedsoxGame = allGameIds.contains { gameId in
request.availableGames.first { $0.id == gameId }?.homeTeamId == "redsox"
}
#expect(hasYankeesGame, "Every route must include a Yankees home game")
#expect(hasRedsoxGame, "Every route must include a Red Sox home game")
}
}
}
// MARK: - Helper Methods
private func makeStadium(
id: String,
city: String,
coordinate: CLLocationCoordinate2D
) -> Stadium {
Stadium(
id: id,
name: "\(city) Stadium",
city: city,
state: "XX",
latitude: coordinate.latitude,
longitude: coordinate.longitude,
capacity: 40000,
sport: .mlb
)
}
private func makeGame(
id: String,
homeTeamId: String,
awayTeamId: String,
stadiumId: String,
dateTime: Date
) -> Game {
Game(
id: id,
homeTeamId: homeTeamId,
awayTeamId: awayTeamId,
stadiumId: stadiumId,
dateTime: dateTime,
sport: .mlb,
season: "2026",
isPlayoff: false
)
}
private func makeTeam(id: String, name: String) -> Team {
Team(
id: id,
name: name,
abbreviation: String(name.prefix(3)).uppercased(),
sport: .mlb,
city: "Test City",
stadiumId: id,
primaryColor: "#000000",
secondaryColor: "#FFFFFF"
)
}
}

View File

@@ -0,0 +1,599 @@
//
// TeamFirstIntegrationTests.swift
// SportsTimeTests
//
// Integration tests for Team-First planning mode.
//
// These tests verify the end-to-end flow from team selection
// to route generation, ensuring all components work together.
//
import Testing
import CoreLocation
@testable import SportsTime
@Suite("TeamFirst Integration")
struct TeamFirstIntegrationTests {
// MARK: - Test Data
private let planner = ScenarioEPlanner()
// MLB stadiums with realistic coordinates
private let yankeeStadiumCoord = CLLocationCoordinate2D(latitude: 40.8296, longitude: -73.9262)
private let fenwayParkCoord = CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)
private let citizensBankCoord = CLLocationCoordinate2D(latitude: 39.9061, longitude: -75.1665)
// MARK: - E3. Full Flow Integration Tests
@Test("Integration: 3 MLB teams returns top 10 routes")
func integration_3MLBTeams_returnsTop10Routes() {
let baseDate = Date()
// Create realistic MLB stadiums
let yankeeStadium = Stadium(
id: "stadium_mlb_yankee_stadium",
name: "Yankee Stadium",
city: "New York",
state: "NY",
latitude: yankeeStadiumCoord.latitude,
longitude: yankeeStadiumCoord.longitude,
capacity: 47309,
sport: .mlb
)
let fenwayPark = Stadium(
id: "stadium_mlb_fenway_park",
name: "Fenway Park",
city: "Boston",
state: "MA",
latitude: fenwayParkCoord.latitude,
longitude: fenwayParkCoord.longitude,
capacity: 37755,
sport: .mlb
)
let citizensBank = Stadium(
id: "stadium_mlb_citizens_bank",
name: "Citizens Bank Park",
city: "Philadelphia",
state: "PA",
latitude: citizensBankCoord.latitude,
longitude: citizensBankCoord.longitude,
capacity: 42792,
sport: .mlb
)
// Create teams
let yankees = Team(
id: "team_mlb_nyy",
name: "Yankees",
abbreviation: "NYY",
sport: .mlb,
city: "New York",
stadiumId: "stadium_mlb_yankee_stadium"
)
let redsox = Team(
id: "team_mlb_bos",
name: "Red Sox",
abbreviation: "BOS",
sport: .mlb,
city: "Boston",
stadiumId: "stadium_mlb_fenway_park"
)
let phillies = Team(
id: "team_mlb_phi",
name: "Phillies",
abbreviation: "PHI",
sport: .mlb,
city: "Philadelphia",
stadiumId: "stadium_mlb_citizens_bank"
)
// Create games within a reasonable 6-day window (3 teams * 2 = 6 days)
// For the window algorithm to find valid windows, games must span at least 6 days
// Window check: windowEnd <= latestDay + 1, so with 6-day window from day 1,
// windowEnd = day 7, so latestDay must be >= day 6
// Day 1: Yankees home
// Day 3: Red Sox home
// Day 6: Phillies home (spans 6 days, window fits)
let calendar = Calendar.current
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))!
let yankeesGame = Game(
id: "game_mlb_2026_nyy_opp_0401",
homeTeamId: "team_mlb_nyy",
awayTeamId: "team_mlb_opp",
stadiumId: "stadium_mlb_yankee_stadium",
dateTime: day1,
sport: .mlb,
season: "2026",
isPlayoff: false
)
let redsoxGame = Game(
id: "game_mlb_2026_bos_opp_0403",
homeTeamId: "team_mlb_bos",
awayTeamId: "team_mlb_opp",
stadiumId: "stadium_mlb_fenway_park",
dateTime: day3,
sport: .mlb,
season: "2026",
isPlayoff: false
)
let philliesGame = Game(
id: "game_mlb_2026_phi_opp_0405",
homeTeamId: "team_mlb_phi",
awayTeamId: "team_mlb_opp",
stadiumId: "stadium_mlb_citizens_bank",
dateTime: day6,
sport: .mlb,
season: "2026",
isPlayoff: false
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["team_mlb_nyy", "team_mlb_bos", "team_mlb_phi"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame, philliesGame],
teams: [
"team_mlb_nyy": yankees,
"team_mlb_bos": redsox,
"team_mlb_phi": phillies
],
stadiums: [
"stadium_mlb_yankee_stadium": yankeeStadium,
"stadium_mlb_fenway_park": fenwayPark,
"stadium_mlb_citizens_bank": citizensBank
]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success with 3 MLB teams in drivable region")
return
}
// Verify we get routes (may be less than 10 due to limited game combinations)
#expect(!options.isEmpty, "Should return at least one route")
#expect(options.count <= 10, "Should return at most 10 routes")
}
@Test("Integration: each route visits all 3 stadiums")
func integration_eachRouteVisitsAll3Stadiums() {
let baseDate = Date()
let yankeeStadium = makeStadium(
id: "yankee-stadium",
name: "Yankee Stadium",
city: "New York",
coordinate: yankeeStadiumCoord
)
let fenwayPark = makeStadium(
id: "fenway-park",
name: "Fenway Park",
city: "Boston",
coordinate: fenwayParkCoord
)
let citizensBank = makeStadium(
id: "citizens-bank",
name: "Citizens Bank Park",
city: "Philadelphia",
coordinate: citizensBankCoord
)
// Create multiple games per team to ensure routes can be found
var games: [Game] = []
// Yankees games
for dayOffset in [1, 7, 14] {
games.append(Game(
id: "yankees-\(dayOffset)",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "yankee-stadium",
dateTime: baseDate.addingTimeInterval(Double(86400 * dayOffset)),
sport: .mlb,
season: "2026",
isPlayoff: false
))
}
// Red Sox games
for dayOffset in [2, 8, 15] {
games.append(Game(
id: "redsox-\(dayOffset)",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "fenway-park",
dateTime: baseDate.addingTimeInterval(Double(86400 * dayOffset)),
sport: .mlb,
season: "2026",
isPlayoff: false
))
}
// Phillies games
for dayOffset in [3, 9, 16] {
games.append(Game(
id: "phillies-\(dayOffset)",
homeTeamId: "phillies",
awayTeamId: "opponent",
stadiumId: "citizens-bank",
dateTime: baseDate.addingTimeInterval(Double(86400 * dayOffset)),
sport: .mlb,
season: "2026",
isPlayoff: false
))
}
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox", "phillies"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: games,
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox"),
"phillies": makeTeam(id: "phillies", name: "Phillies")
],
stadiums: [
"yankee-stadium": yankeeStadium,
"fenway-park": fenwayPark,
"citizens-bank": citizensBank
]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success")
return
}
// Verify each route visits all 3 stadiums
for option in options {
let citiesVisited = Set(option.stops.map { $0.city })
#expect(citiesVisited.contains("New York"), "Every route must visit New York")
#expect(citiesVisited.contains("Boston"), "Every route must visit Boston")
#expect(citiesVisited.contains("Philadelphia"), "Every route must visit Philadelphia")
}
}
@Test("Integration: total duration within 6 days (teams x 2)")
func integration_totalDurationWithinLimit() {
let baseDate = Date()
let yankeeStadium = makeStadium(
id: "yankee-stadium",
name: "Yankee Stadium",
city: "New York",
coordinate: yankeeStadiumCoord
)
let fenwayPark = makeStadium(
id: "fenway-park",
name: "Fenway Park",
city: "Boston",
coordinate: fenwayParkCoord
)
let citizensBank = makeStadium(
id: "citizens-bank",
name: "Citizens Bank Park",
city: "Philadelphia",
coordinate: citizensBankCoord
)
// 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 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))!
let yankeesGame = Game(
id: "yankees-home",
homeTeamId: "yankees",
awayTeamId: "opponent",
stadiumId: "yankee-stadium",
dateTime: day1,
sport: .mlb,
season: "2026",
isPlayoff: false
)
let redsoxGame = Game(
id: "redsox-home",
homeTeamId: "redsox",
awayTeamId: "opponent",
stadiumId: "fenway-park",
dateTime: day3,
sport: .mlb,
season: "2026",
isPlayoff: false
)
let philliesGame = Game(
id: "phillies-home",
homeTeamId: "phillies",
awayTeamId: "opponent",
stadiumId: "citizens-bank",
dateTime: day6,
sport: .mlb,
season: "2026",
isPlayoff: false
)
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox", "phillies"]
)
// 3 teams * 2 = 6 days max window
let maxDays = prefs.teamFirstMaxDays
#expect(maxDays == 6, "Window should be 6 days for 3 teams")
let request = PlanningRequest(
preferences: prefs,
availableGames: [yankeesGame, redsoxGame, philliesGame],
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox"),
"phillies": makeTeam(id: "phillies", name: "Phillies")
],
stadiums: [
"yankee-stadium": yankeeStadium,
"fenway-park": fenwayPark,
"citizens-bank": citizensBank
]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success")
return
}
// Verify each route's duration is reasonable
for option in options {
guard let firstStop = option.stops.first,
let lastStop = option.stops.last else {
continue
}
let calendar = Calendar.current
let tripDays = calendar.dateComponents(
[.day],
from: calendar.startOfDay(for: firstStop.arrivalDate),
to: calendar.startOfDay(for: lastStop.departureDate)
).day ?? 0
// Trip duration should be within the window (allowing +1 for same-day start/end)
#expect(tripDays <= maxDays, "Trip duration (\(tripDays) days) should not exceed window (\(maxDays) days)")
}
}
@Test("Integration: factory selects ScenarioEPlanner for teamFirst mode")
func integration_factorySelectsScenarioEPlanner() {
let baseDate = Date()
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["team1", "team2"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: [],
teams: [:],
stadiums: [:]
)
let scenario = ScenarioPlannerFactory.classify(request)
#expect(scenario == .scenarioE, "Should classify as scenarioE for teamFirst mode with 2+ teams")
let planner = ScenarioPlannerFactory.planner(for: request)
#expect(planner is ScenarioEPlanner, "Should return ScenarioEPlanner for teamFirst mode")
}
@Test("Integration: factory requires 2+ teams for ScenarioE")
func integration_factoryRequires2TeamsForScenarioE() {
let baseDate = Date()
// With only 1 team, should NOT select ScenarioE
var prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["team1"] // Only 1 team
)
var request = PlanningRequest(
preferences: prefs,
availableGames: [],
teams: [:],
stadiums: [:]
)
var scenario = ScenarioPlannerFactory.classify(request)
#expect(scenario != .scenarioE, "Should NOT classify as scenarioE with only 1 team")
// With 0 teams, should also NOT select ScenarioE
prefs.selectedTeamIds = []
request = PlanningRequest(
preferences: prefs,
availableGames: [],
teams: [:],
stadiums: [:]
)
scenario = ScenarioPlannerFactory.classify(request)
#expect(scenario != .scenarioE, "Should NOT classify as scenarioE with 0 teams")
}
@Test("Integration: realistic east coast trip with 4 teams")
func integration_realisticEastCoastTrip() {
let baseDate = Date()
// East coast stadiums (NYC, Boston, Philly, Baltimore)
let yankeeStadium = makeStadium(
id: "yankee-stadium",
name: "Yankee Stadium",
city: "New York",
coordinate: CLLocationCoordinate2D(latitude: 40.8296, longitude: -73.9262)
)
let fenwayPark = makeStadium(
id: "fenway-park",
name: "Fenway Park",
city: "Boston",
coordinate: CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)
)
let citizensBank = makeStadium(
id: "citizens-bank",
name: "Citizens Bank Park",
city: "Philadelphia",
coordinate: CLLocationCoordinate2D(latitude: 39.9061, longitude: -75.1665)
)
let camdenYards = makeStadium(
id: "camden-yards",
name: "Camden Yards",
city: "Baltimore",
coordinate: CLLocationCoordinate2D(latitude: 39.2838, longitude: -76.6215)
)
// 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 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))!
let day8 = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 8))!
let games = [
Game(id: "yankees-1", homeTeamId: "yankees", awayTeamId: "opp", stadiumId: "yankee-stadium",
dateTime: day1, sport: .mlb, season: "2026", isPlayoff: false),
Game(id: "redsox-1", homeTeamId: "redsox", awayTeamId: "opp", stadiumId: "fenway-park",
dateTime: day3, sport: .mlb, season: "2026", isPlayoff: false),
Game(id: "phillies-1", homeTeamId: "phillies", awayTeamId: "opp", stadiumId: "citizens-bank",
dateTime: day5, sport: .mlb, season: "2026", isPlayoff: false),
Game(id: "orioles-1", homeTeamId: "orioles", awayTeamId: "opp", stadiumId: "camden-yards",
dateTime: day8, sport: .mlb, season: "2026", isPlayoff: false)
]
let prefs = TripPreferences(
planningMode: .teamFirst,
sports: [.mlb],
startDate: baseDate,
endDate: baseDate.addingTimeInterval(86400 * 30),
leisureLevel: .moderate,
lodgingType: .hotel,
numberOfDrivers: 2,
selectedTeamIds: ["yankees", "redsox", "phillies", "orioles"]
)
let request = PlanningRequest(
preferences: prefs,
availableGames: games,
teams: [
"yankees": makeTeam(id: "yankees", name: "Yankees"),
"redsox": makeTeam(id: "redsox", name: "Red Sox"),
"phillies": makeTeam(id: "phillies", name: "Phillies"),
"orioles": makeTeam(id: "orioles", name: "Orioles")
],
stadiums: [
"yankee-stadium": yankeeStadium,
"fenway-park": fenwayPark,
"citizens-bank": citizensBank,
"camden-yards": camdenYards
]
)
let result = planner.plan(request: request)
guard case .success(let options) = result else {
Issue.record("Expected success with drivable east coast trip")
return
}
#expect(!options.isEmpty, "Should find routes for east coast trip")
// Verify each route visits all 4 cities
for option in options {
let citiesVisited = Set(option.stops.map { $0.city })
#expect(citiesVisited.count >= 4, "Each route should visit at least 4 cities (one per team)")
}
}
// MARK: - Helper Methods
private func makeStadium(
id: String,
name: String,
city: String,
coordinate: CLLocationCoordinate2D
) -> Stadium {
Stadium(
id: id,
name: name,
city: city,
state: "XX",
latitude: coordinate.latitude,
longitude: coordinate.longitude,
capacity: 40000,
sport: .mlb
)
}
private func makeTeam(id: String, name: String) -> Team {
Team(
id: id,
name: name,
abbreviation: String(name.prefix(3)).uppercased(),
sport: .mlb,
city: "Test City",
stadiumId: id
)
}
}