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:
@@ -9,6 +9,21 @@
|
||||
import Foundation
|
||||
import CoreLocation
|
||||
|
||||
/// Travel estimation utilities for calculating distances and driving times.
|
||||
///
|
||||
/// Uses Haversine formula for coordinate-based distance with a road routing factor,
|
||||
/// or fallback distances when coordinates are unavailable.
|
||||
///
|
||||
/// - Constants:
|
||||
/// - averageSpeedMph: 60 mph
|
||||
/// - roadRoutingFactor: 1.3 (accounts for roads vs straight-line distance)
|
||||
/// - fallbackDistanceMiles: 300 miles (when coordinates unavailable)
|
||||
///
|
||||
/// - Invariants:
|
||||
/// - All distance calculations are symmetric: distance(A,B) == distance(B,A)
|
||||
/// - Road distance >= straight-line distance (roadRoutingFactor >= 1.0)
|
||||
/// - Travel duration is always distance / averageSpeedMph
|
||||
/// - Segments exceeding 5x maxDailyDrivingHours return nil (unreachable)
|
||||
enum TravelEstimator {
|
||||
|
||||
// MARK: - Constants
|
||||
@@ -19,8 +34,21 @@ enum TravelEstimator {
|
||||
|
||||
// MARK: - Travel Estimation
|
||||
|
||||
/// Estimates a travel segment between two stops.
|
||||
/// Returns nil if trip exceeds maximum allowed driving hours (2 days worth).
|
||||
/// Estimates a travel segment between two ItineraryStops.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Origin stop
|
||||
/// - to: Destination stop
|
||||
/// - constraints: Driving constraints (drivers, hours per day)
|
||||
/// - Returns: TravelSegment or nil if unreachable
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - With valid coordinates → calculates distance using Haversine * roadRoutingFactor
|
||||
/// - Missing coordinates → uses fallback distance (300 miles)
|
||||
/// - Same city (no coords) → 0 distance, 0 duration
|
||||
/// - Driving hours > 5x maxDailyDrivingHours → returns nil
|
||||
/// - Duration = distance / 60 mph
|
||||
/// - Result distance in meters, duration in seconds
|
||||
static func estimate(
|
||||
from: ItineraryStop,
|
||||
to: ItineraryStop,
|
||||
@@ -47,7 +75,19 @@ enum TravelEstimator {
|
||||
}
|
||||
|
||||
/// Estimates a travel segment between two LocationInputs.
|
||||
/// Returns nil if coordinates are missing or if trip exceeds maximum allowed driving hours (2 days worth).
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Origin location
|
||||
/// - to: Destination location
|
||||
/// - constraints: Driving constraints
|
||||
/// - Returns: TravelSegment or nil if unreachable/invalid
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - Missing from.coordinate → returns nil
|
||||
/// - Missing to.coordinate → returns nil
|
||||
/// - Valid coordinates → calculates distance using Haversine * roadRoutingFactor
|
||||
/// - Driving hours > 5x maxDailyDrivingHours → returns nil
|
||||
/// - Duration = distance / 60 mph
|
||||
static func estimate(
|
||||
from: LocationInput,
|
||||
to: LocationInput,
|
||||
@@ -81,8 +121,18 @@ enum TravelEstimator {
|
||||
|
||||
// MARK: - Distance Calculations
|
||||
|
||||
/// Calculates distance in miles between two stops.
|
||||
/// Uses Haversine formula if coordinates available, fallback otherwise.
|
||||
/// Calculates road distance in miles between two ItineraryStops.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Origin stop
|
||||
/// - to: Destination stop
|
||||
/// - Returns: Distance in miles
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - Both have coordinates → Haversine distance * 1.3
|
||||
/// - Either missing coordinates → fallback distance
|
||||
/// - Same city (no coords) → 0 miles
|
||||
/// - Different cities (no coords) → 300 miles
|
||||
static func calculateDistanceMiles(
|
||||
from: ItineraryStop,
|
||||
to: ItineraryStop
|
||||
@@ -94,7 +144,18 @@ enum TravelEstimator {
|
||||
return estimateFallbackDistance(from: from, to: to)
|
||||
}
|
||||
|
||||
/// Calculates distance in miles between two coordinates using Haversine.
|
||||
/// Calculates straight-line distance in miles using Haversine formula.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Origin coordinate
|
||||
/// - to: Destination coordinate
|
||||
/// - Returns: Straight-line distance in miles
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - Same point → 0 miles
|
||||
/// - NYC to Boston → ~190 miles (validates formula accuracy)
|
||||
/// - Symmetric: distance(A,B) == distance(B,A)
|
||||
/// - Uses Earth radius of 3958.8 miles
|
||||
static func haversineDistanceMiles(
|
||||
from: CLLocationCoordinate2D,
|
||||
to: CLLocationCoordinate2D
|
||||
@@ -114,7 +175,18 @@ enum TravelEstimator {
|
||||
return earthRadiusMiles * c
|
||||
}
|
||||
|
||||
/// Calculates distance in meters between two coordinates using Haversine.
|
||||
/// Calculates straight-line distance in meters using Haversine formula.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Origin coordinate
|
||||
/// - to: Destination coordinate
|
||||
/// - Returns: Straight-line distance in meters
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - Same point → 0 meters
|
||||
/// - Symmetric: distance(A,B) == distance(B,A)
|
||||
/// - Uses Earth radius of 6,371,000 meters
|
||||
/// - haversineDistanceMeters / 1609.34 ≈ haversineDistanceMiles
|
||||
static func haversineDistanceMeters(
|
||||
from: CLLocationCoordinate2D,
|
||||
to: CLLocationCoordinate2D
|
||||
@@ -135,6 +207,15 @@ enum TravelEstimator {
|
||||
}
|
||||
|
||||
/// Fallback distance when coordinates aren't available.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Origin stop
|
||||
/// - to: Destination stop
|
||||
/// - Returns: Estimated distance in miles
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - Same city → 0 miles
|
||||
/// - Different cities → 300 miles (fallback constant)
|
||||
static func estimateFallbackDistance(
|
||||
from: ItineraryStop,
|
||||
to: ItineraryStop
|
||||
@@ -147,7 +228,20 @@ enum TravelEstimator {
|
||||
|
||||
// MARK: - Travel Days
|
||||
|
||||
/// Calculates which calendar days travel spans.
|
||||
/// Calculates which calendar days a driving segment spans.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - departure: Departure date/time
|
||||
/// - drivingHours: Total driving hours
|
||||
/// - Returns: Array of calendar days (start of day) that travel spans
|
||||
///
|
||||
/// - Expected Behavior:
|
||||
/// - 0 hours → [departure day]
|
||||
/// - 1-8 hours → [departure day] (1 day)
|
||||
/// - 8.01-16 hours → [departure day, next day] (2 days)
|
||||
/// - 16.01-24 hours → [departure day, +1, +2] (3 days)
|
||||
/// - All dates are normalized to start of day (midnight)
|
||||
/// - Assumes 8 driving hours per day max
|
||||
static func calculateTravelDays(
|
||||
departure: Date,
|
||||
drivingHours: Double
|
||||
|
||||
Reference in New Issue
Block a user