refactor(tests): TDD rewrite of all unit tests with spec documentation

Complete rewrite of unit test suite using TDD methodology:

Planning Engine Tests:
- GameDAGRouterTests: Beam search, anchor games, transitions
- ItineraryBuilderTests: Stop connection, validators, EV enrichment
- RouteFiltersTests: Region, time window, scoring filters
- ScenarioA/B/C/D PlannerTests: All planning scenarios
- TravelEstimatorTests: Distance, duration, travel days
- TripPlanningEngineTests: Orchestration, caching, preferences

Domain Model Tests:
- AchievementDefinitionsTests, AnySportTests, DivisionTests
- GameTests, ProgressTests, RegionTests, StadiumTests
- TeamTests, TravelSegmentTests, TripTests, TripPollTests
- TripPreferencesTests, TripStopTests, SportTests

Service Tests:
- FreeScoreAPITests, RouteDescriptionGeneratorTests
- SuggestedTripsGeneratorTests

Export Tests:
- ShareableContentTests (card types, themes, dimensions)

Bug fixes discovered through TDD:
- ShareCardDimensions: mapSnapshotSize exceeded available width (960x480)
- ScenarioBPlanner: Added anchor game validation filter

All tests include:
- Specification tests (expected behavior)
- Invariant tests (properties that must always hold)
- Edge case tests (boundary conditions)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-16 14:07:41 -06:00
parent 035dd6f5de
commit 8162b4a029
102 changed files with 13409 additions and 9883 deletions

View File

@@ -10,7 +10,13 @@ import CoreLocation
// MARK: - Planning Scenario
/// Exactly one scenario per request. No blending.
/// Planning scenario types - exactly one per request.
///
/// - Expected Behavior:
/// - scenarioA: User provides date range only, system finds games
/// - scenarioB: User selects specific games + date range
/// - scenarioC: User provides start/end locations, system plans route
/// - scenarioD: User follows a team's schedule
enum PlanningScenario: Equatable {
case scenarioA // Date range only
case scenarioB // Selected games + date range
@@ -195,10 +201,19 @@ struct ItineraryOption: Identifiable {
/// - leisureLevel: The user's leisure preference
/// - Returns: Sorted and ranked options (all options, no limit)
///
/// Sorting behavior:
/// - Packed: Most games first, then least driving
/// - Moderate: Best efficiency (games per driving hour)
/// - Relaxed: Least driving first, then fewer games
/// - Expected Behavior:
/// - Empty options empty result
/// - All options are returned (no filtering)
/// - Ranks are reassigned 1, 2, 3... after sorting
///
/// Sorting behavior by leisure level:
/// - Packed: Most games first, then least driving (maximize games)
/// - Moderate: Best efficiency (games/hour), then most games (balance)
/// - Relaxed: Least driving first, then fewer games (minimize driving)
///
/// - Invariants:
/// - Output count == input count
/// - Ranks are sequential starting at 1
static func sortByLeisure(
_ options: [ItineraryOption],
leisureLevel: LeisureLevel
@@ -270,7 +285,19 @@ struct ItineraryStop: Identifiable, Hashable {
// MARK: - Driving Constraints
/// Driving feasibility constraints.
/// Driving feasibility constraints based on number of drivers.
///
/// - Expected Behavior:
/// - numberOfDrivers < 1 clamped to 1
/// - maxHoursPerDriverPerDay < 1.0 clamped to 1.0
/// - maxDailyDrivingHours = numberOfDrivers * maxHoursPerDriverPerDay
/// - Default: 1 driver, 8 hours/day = 8 total hours
/// - 2 drivers, 8 hours each = 16 total hours
///
/// - Invariants:
/// - numberOfDrivers >= 1
/// - maxHoursPerDriverPerDay >= 1.0
/// - maxDailyDrivingHours >= 1.0
struct DrivingConstraints {
let numberOfDrivers: Int
let maxHoursPerDriverPerDay: Double