Files
Sportstime/SportsTimeTests/TravelEstimatorTests.swift
Trey t ab89c25f2f Refactor trip planning: DAG router + trip options UI + simplified itinerary
- Replace O(2^n) GeographicRouteExplorer with O(n) GameDAGRouter using DAG + beam search
- Add geographic diversity to route selection (returns routes from distinct regions)
- Add trip options selector UI (TripOptionsView, TripOptionCard) to choose between routes
- Simplify itinerary display: separate games and travel segments by date
- Remove complex ItineraryDay bundling, query games/travel directly per day
- Update ScenarioA/B/C planners to use GameDAGRouter
- Add new test suites for planners and travel estimator

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 12:26:17 -06:00

586 lines
25 KiB
Swift

//
// TravelEstimatorTests.swift
// SportsTimeTests
//
// 50 comprehensive tests for TravelEstimator covering:
// - Haversine distance calculations (miles and meters)
// - Travel segment estimation from stops
// - Travel segment estimation from LocationInputs
// - Fallback distance when coordinates missing
// - Travel day calculations
// - Edge cases and boundary conditions
//
import Testing
@testable import SportsTime
import Foundation
import CoreLocation
// MARK: - TravelEstimator Tests
struct TravelEstimatorTests {
// MARK: - Test Data Helpers
private func makeStop(
city: String,
latitude: Double? = nil,
longitude: Double? = nil,
arrivalDate: Date = Date(),
departureDate: Date? = nil
) -> ItineraryStop {
let coordinate = (latitude != nil && longitude != nil)
? CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
: nil
let location = LocationInput(
name: city,
coordinate: coordinate,
address: nil
)
return ItineraryStop(
city: city,
state: "ST",
coordinate: coordinate,
games: [],
arrivalDate: arrivalDate,
departureDate: departureDate ?? arrivalDate,
location: location,
firstGameStart: nil
)
}
private func makeLocation(
name: String,
latitude: Double? = nil,
longitude: Double? = nil
) -> LocationInput {
let coordinate = (latitude != nil && longitude != nil)
? CLLocationCoordinate2D(latitude: latitude!, longitude: longitude!)
: nil
return LocationInput(name: name, coordinate: coordinate, address: nil)
}
private func defaultConstraints() -> DrivingConstraints {
DrivingConstraints(numberOfDrivers: 1, maxHoursPerDriverPerDay: 8.0)
}
private func twoDriverConstraints() -> DrivingConstraints {
DrivingConstraints(numberOfDrivers: 2, maxHoursPerDriverPerDay: 8.0)
}
// MARK: - Haversine Distance (Miles) Tests
@Test("haversineDistanceMiles - same point returns zero")
func haversine_SamePoint_ReturnsZero() {
let coord = CLLocationCoordinate2D(latitude: 40.0, longitude: -100.0)
let distance = TravelEstimator.haversineDistanceMiles(from: coord, to: coord)
#expect(distance == 0.0)
}
@Test("haversineDistanceMiles - LA to SF approximately 350 miles")
func haversine_LAToSF_ApproximatelyCorrect() {
let la = CLLocationCoordinate2D(latitude: 34.0522, longitude: -118.2437)
let sf = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let distance = TravelEstimator.haversineDistanceMiles(from: la, to: sf)
// Known distance is ~347 miles
#expect(distance > 340 && distance < 360, "Expected ~350 miles, got \(distance)")
}
@Test("haversineDistanceMiles - NY to LA approximately 2450 miles")
func haversine_NYToLA_ApproximatelyCorrect() {
let ny = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060)
let la = CLLocationCoordinate2D(latitude: 34.0522, longitude: -118.2437)
let distance = TravelEstimator.haversineDistanceMiles(from: ny, to: la)
// Known distance is ~2450 miles
#expect(distance > 2400 && distance < 2500, "Expected ~2450 miles, got \(distance)")
}
@Test("haversineDistanceMiles - commutative (A to B equals B to A)")
func haversine_Commutative() {
let coord1 = CLLocationCoordinate2D(latitude: 40.0, longitude: -100.0)
let coord2 = CLLocationCoordinate2D(latitude: 35.0, longitude: -90.0)
let distance1 = TravelEstimator.haversineDistanceMiles(from: coord1, to: coord2)
let distance2 = TravelEstimator.haversineDistanceMiles(from: coord2, to: coord1)
#expect(abs(distance1 - distance2) < 0.001)
}
@Test("haversineDistanceMiles - across equator")
func haversine_AcrossEquator() {
let north = CLLocationCoordinate2D(latitude: 10.0, longitude: -80.0)
let south = CLLocationCoordinate2D(latitude: -10.0, longitude: -80.0)
let distance = TravelEstimator.haversineDistanceMiles(from: north, to: south)
// 20 degrees latitude 1380 miles
#expect(distance > 1350 && distance < 1400, "Expected ~1380 miles, got \(distance)")
}
@Test("haversineDistanceMiles - across prime meridian")
func haversine_AcrossPrimeMeridian() {
let west = CLLocationCoordinate2D(latitude: 51.5, longitude: -1.0)
let east = CLLocationCoordinate2D(latitude: 51.5, longitude: 1.0)
let distance = TravelEstimator.haversineDistanceMiles(from: west, to: east)
// 2 degrees longitude at ~51.5° latitude 85 miles
#expect(distance > 80 && distance < 90, "Expected ~85 miles, got \(distance)")
}
@Test("haversineDistanceMiles - near north pole")
func haversine_NearNorthPole() {
let coord1 = CLLocationCoordinate2D(latitude: 89.0, longitude: 0.0)
let coord2 = CLLocationCoordinate2D(latitude: 89.0, longitude: 180.0)
let distance = TravelEstimator.haversineDistanceMiles(from: coord1, to: coord2)
// At 89° latitude, half way around the world is very short
#expect(distance > 0 && distance < 150, "Distance near pole should be short, got \(distance)")
}
@Test("haversineDistanceMiles - Chicago to Denver approximately 920 miles")
func haversine_ChicagoToDenver() {
let chicago = CLLocationCoordinate2D(latitude: 41.8781, longitude: -87.6298)
let denver = CLLocationCoordinate2D(latitude: 39.7392, longitude: -104.9903)
let distance = TravelEstimator.haversineDistanceMiles(from: chicago, to: denver)
// Known distance ~920 miles
#expect(distance > 900 && distance < 940, "Expected ~920 miles, got \(distance)")
}
@Test("haversineDistanceMiles - very short distance (same city)")
func haversine_VeryShortDistance() {
let point1 = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855) // Times Square
let point2 = CLLocationCoordinate2D(latitude: 40.7614, longitude: -73.9776) // Grand Central
let distance = TravelEstimator.haversineDistanceMiles(from: point1, to: point2)
// ~0.5 miles
#expect(distance > 0.4 && distance < 0.6, "Expected ~0.5 miles, got \(distance)")
}
@Test("haversineDistanceMiles - extreme longitude difference")
func haversine_ExtremeLongitudeDifference() {
let west = CLLocationCoordinate2D(latitude: 40.0, longitude: -179.0)
let east = CLLocationCoordinate2D(latitude: 40.0, longitude: 179.0)
let distance = TravelEstimator.haversineDistanceMiles(from: west, to: east)
// 358 degrees the long way, 2 degrees the short way
// At 40° latitude, 2 degrees 105 miles
#expect(distance > 100 && distance < 110, "Expected ~105 miles, got \(distance)")
}
// MARK: - Haversine Distance (Meters) Tests
@Test("haversineDistanceMeters - same point returns zero")
func haversineMeters_SamePoint_ReturnsZero() {
let coord = CLLocationCoordinate2D(latitude: 40.0, longitude: -100.0)
let distance = TravelEstimator.haversineDistanceMeters(from: coord, to: coord)
#expect(distance == 0.0)
}
@Test("haversineDistanceMeters - LA to SF approximately 560 km")
func haversineMeters_LAToSF() {
let la = CLLocationCoordinate2D(latitude: 34.0522, longitude: -118.2437)
let sf = CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)
let distanceKm = TravelEstimator.haversineDistanceMeters(from: la, to: sf) / 1000
#expect(distanceKm > 540 && distanceKm < 580, "Expected ~560 km, got \(distanceKm)")
}
@Test("haversineDistanceMeters - consistency with miles conversion")
func haversineMeters_ConsistentWithMiles() {
let coord1 = CLLocationCoordinate2D(latitude: 40.0, longitude: -100.0)
let coord2 = CLLocationCoordinate2D(latitude: 35.0, longitude: -90.0)
let miles = TravelEstimator.haversineDistanceMiles(from: coord1, to: coord2)
let meters = TravelEstimator.haversineDistanceMeters(from: coord1, to: coord2)
// 1 mile = 1609.34 meters
let milesFromMeters = meters / 1609.34
#expect(abs(miles - milesFromMeters) < 1.0)
}
@Test("haversineDistanceMeters - one kilometer distance")
func haversineMeters_OneKilometer() {
// 1 degree latitude 111 km, so 0.009 degrees 1 km
let coord1 = CLLocationCoordinate2D(latitude: 40.0, longitude: -100.0)
let coord2 = CLLocationCoordinate2D(latitude: 40.009, longitude: -100.0)
let meters = TravelEstimator.haversineDistanceMeters(from: coord1, to: coord2)
#expect(meters > 900 && meters < 1100, "Expected ~1000 meters, got \(meters)")
}
// MARK: - Calculate Distance Miles Tests
@Test("calculateDistanceMiles - with coordinates uses haversine")
func calculateDistance_WithCoordinates_UsesHaversine() {
let stop1 = makeStop(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
let stop2 = makeStop(city: "San Francisco", latitude: 37.7749, longitude: -122.4194)
let distance = TravelEstimator.calculateDistanceMiles(from: stop1, to: stop2)
// Haversine ~350 miles * 1.3 routing factor 455 miles
#expect(distance > 440 && distance < 470, "Expected ~455 miles with routing factor, got \(distance)")
}
@Test("calculateDistanceMiles - without coordinates uses fallback")
func calculateDistance_WithoutCoordinates_UsesFallback() {
let stop1 = makeStop(city: "CityA")
let stop2 = makeStop(city: "CityB")
let distance = TravelEstimator.calculateDistanceMiles(from: stop1, to: stop2)
// Fallback is 300 miles
#expect(distance == 300.0, "Expected fallback of 300 miles, got \(distance)")
}
@Test("calculateDistanceMiles - same city returns zero")
func calculateDistance_SameCity_ReturnsZero() {
let stop1 = makeStop(city: "Chicago")
let stop2 = makeStop(city: "Chicago")
let distance = TravelEstimator.calculateDistanceMiles(from: stop1, to: stop2)
#expect(distance == 0.0)
}
@Test("calculateDistanceMiles - one stop missing coordinates uses fallback")
func calculateDistance_OneMissingCoordinate_UsesFallback() {
let stop1 = makeStop(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
let stop2 = makeStop(city: "San Francisco")
let distance = TravelEstimator.calculateDistanceMiles(from: stop1, to: stop2)
#expect(distance == 300.0, "Expected fallback of 300 miles, got \(distance)")
}
// MARK: - Estimate Fallback Distance Tests
@Test("estimateFallbackDistance - same city returns zero")
func fallbackDistance_SameCity_ReturnsZero() {
let stop1 = makeStop(city: "Denver")
let stop2 = makeStop(city: "Denver")
let distance = TravelEstimator.estimateFallbackDistance(from: stop1, to: stop2)
#expect(distance == 0.0)
}
@Test("estimateFallbackDistance - different cities returns 300")
func fallbackDistance_DifferentCities_Returns300() {
let stop1 = makeStop(city: "Denver")
let stop2 = makeStop(city: "Chicago")
let distance = TravelEstimator.estimateFallbackDistance(from: stop1, to: stop2)
#expect(distance == 300.0)
}
@Test("estimateFallbackDistance - case sensitive city names")
func fallbackDistance_CaseSensitive() {
let stop1 = makeStop(city: "denver")
let stop2 = makeStop(city: "Denver")
let distance = TravelEstimator.estimateFallbackDistance(from: stop1, to: stop2)
// Different case means different cities
#expect(distance == 300.0)
}
// MARK: - Estimate (from Stops) Tests
@Test("estimate stops - returns valid segment for short trip")
func estimateStops_ShortTrip_ReturnsSegment() {
let stop1 = makeStop(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
let stop2 = makeStop(city: "San Diego", latitude: 32.7157, longitude: -117.1611)
let segment = TravelEstimator.estimate(from: stop1, to: stop2, constraints: defaultConstraints())
#expect(segment != nil, "Should return segment for short trip")
#expect(segment!.travelMode == .drive)
#expect(segment!.durationHours < 8.0, "LA to SD should be under 8 hours")
}
@Test("estimate stops - returns nil for extremely long trip")
func estimateStops_ExtremelyLongTrip_ReturnsNil() {
// Create stops 4000 miles apart (> 2 days of driving at 60mph)
let stop1 = makeStop(city: "New York", latitude: 40.7128, longitude: -74.0060)
// Point way out in the Pacific
let stop2 = makeStop(city: "Far Away", latitude: 35.0, longitude: -170.0)
let segment = TravelEstimator.estimate(from: stop1, to: stop2, constraints: defaultConstraints())
#expect(segment == nil, "Should return nil for trip > 2 days of driving")
}
@Test("estimate stops - respects two-driver constraint")
func estimateStops_TwoDrivers_IncreasesCapacity() {
// Trip that exceeds 1-driver limit (16h) but fits 2-driver limit (32h)
// LA to Denver: ~850mi straight line * 1.3 routing = ~1105mi / 60mph = ~18.4 hours
let stop1 = makeStop(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
let stop2 = makeStop(city: "Denver", latitude: 39.7392, longitude: -104.9903)
let oneDriver = TravelEstimator.estimate(from: stop1, to: stop2, constraints: defaultConstraints())
let twoDrivers = TravelEstimator.estimate(from: stop1, to: stop2, constraints: twoDriverConstraints())
// ~18 hours exceeds 1-driver limit (16h max over 2 days) but fits 2-driver (32h)
#expect(oneDriver == nil, "Should fail with one driver - exceeds 16h limit")
#expect(twoDrivers != nil, "Should succeed with two drivers - within 32h limit")
}
@Test("estimate stops - calculates departure and arrival times")
func estimateStops_CalculatesTimes() {
let baseDate = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5))!
let stop1 = makeStop(city: "Los Angeles", latitude: 34.0522, longitude: -118.2437, departureDate: baseDate)
let stop2 = makeStop(city: "San Diego", latitude: 32.7157, longitude: -117.1611)
let segment = TravelEstimator.estimate(from: stop1, to: stop2, constraints: defaultConstraints())
#expect(segment != nil)
#expect(segment!.departureTime > baseDate, "Departure should be after base date (adds 8 hours)")
#expect(segment!.arrivalTime > segment!.departureTime, "Arrival should be after departure")
}
@Test("estimate stops - distance and duration are consistent")
func estimateStops_DistanceDurationConsistent() {
let stop1 = makeStop(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
let stop2 = makeStop(city: "Detroit", latitude: 42.3314, longitude: -83.0458)
let segment = TravelEstimator.estimate(from: stop1, to: stop2, constraints: defaultConstraints())
#expect(segment != nil)
// At 60 mph average, hours = miles / 60
let expectedHours = segment!.distanceMiles / 60.0
#expect(abs(segment!.durationHours - expectedHours) < 0.01)
}
@Test("estimate stops - zero distance same location")
func estimateStops_SameLocation_ZeroDistance() {
let stop1 = makeStop(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
let stop2 = makeStop(city: "Chicago", latitude: 41.8781, longitude: -87.6298)
let segment = TravelEstimator.estimate(from: stop1, to: stop2, constraints: defaultConstraints())
#expect(segment != nil)
#expect(segment!.distanceMiles == 0.0)
#expect(segment!.durationHours == 0.0)
}
// MARK: - Estimate (from LocationInputs) Tests
@Test("estimate locations - returns valid segment")
func estimateLocations_ValidLocations_ReturnsSegment() {
let from = makeLocation(name: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
let to = makeLocation(name: "San Diego", latitude: 32.7157, longitude: -117.1611)
let segment = TravelEstimator.estimate(from: from, to: to, constraints: defaultConstraints())
#expect(segment != nil)
#expect(segment!.fromLocation.name == "Los Angeles")
#expect(segment!.toLocation.name == "San Diego")
}
@Test("estimate locations - returns nil for missing from coordinate")
func estimateLocations_MissingFromCoordinate_ReturnsNil() {
let from = makeLocation(name: "Unknown City")
let to = makeLocation(name: "San Diego", latitude: 32.7157, longitude: -117.1611)
let segment = TravelEstimator.estimate(from: from, to: to, constraints: defaultConstraints())
#expect(segment == nil)
}
@Test("estimate locations - returns nil for missing to coordinate")
func estimateLocations_MissingToCoordinate_ReturnsNil() {
let from = makeLocation(name: "Los Angeles", latitude: 34.0522, longitude: -118.2437)
let to = makeLocation(name: "Unknown City")
let segment = TravelEstimator.estimate(from: from, to: to, constraints: defaultConstraints())
#expect(segment == nil)
}
@Test("estimate locations - returns nil for both missing coordinates")
func estimateLocations_BothMissingCoordinates_ReturnsNil() {
let from = makeLocation(name: "Unknown A")
let to = makeLocation(name: "Unknown B")
let segment = TravelEstimator.estimate(from: from, to: to, constraints: defaultConstraints())
#expect(segment == nil)
}
@Test("estimate locations - applies road routing factor")
func estimateLocations_AppliesRoutingFactor() {
let from = makeLocation(name: "A", latitude: 40.0, longitude: -100.0)
let to = makeLocation(name: "B", latitude: 41.0, longitude: -100.0)
let segment = TravelEstimator.estimate(from: from, to: to, constraints: defaultConstraints())
#expect(segment != nil)
// Straight line distance * 1.3 routing factor
let straightLineMeters = TravelEstimator.haversineDistanceMeters(
from: from.coordinate!, to: to.coordinate!
)
let expectedMeters = straightLineMeters * 1.3
#expect(abs(segment!.distanceMeters - expectedMeters) < 1.0)
}
@Test("estimate locations - returns nil for extremely long trip")
func estimateLocations_ExtremelyLongTrip_ReturnsNil() {
let from = makeLocation(name: "New York", latitude: 40.7128, longitude: -74.0060)
let to = makeLocation(name: "Far Pacific", latitude: 35.0, longitude: -170.0)
let segment = TravelEstimator.estimate(from: from, to: to, constraints: defaultConstraints())
#expect(segment == nil)
}
// MARK: - Calculate Travel Days Tests
@Test("calculateTravelDays - short trip returns single day")
func travelDays_ShortTrip_ReturnsOneDay() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 4.0)
#expect(days.count == 1)
}
@Test("calculateTravelDays - exactly 8 hours returns single day")
func travelDays_EightHours_ReturnsOneDay() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 8.0)
#expect(days.count == 1)
}
@Test("calculateTravelDays - 9 hours returns two days")
func travelDays_NineHours_ReturnsTwoDays() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 9.0)
#expect(days.count == 2)
}
@Test("calculateTravelDays - 16 hours returns two days")
func travelDays_SixteenHours_ReturnsTwoDays() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 16.0)
#expect(days.count == 2)
}
@Test("calculateTravelDays - 17 hours returns three days")
func travelDays_SeventeenHours_ReturnsThreeDays() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 17.0)
#expect(days.count == 3)
}
@Test("calculateTravelDays - zero hours returns single day")
func travelDays_ZeroHours_ReturnsOneDay() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 0.0)
// ceil(0 / 8) = 0, but we always start with one day
#expect(days.count == 1)
}
@Test("calculateTravelDays - days are at start of day")
func travelDays_DaysAreAtStartOfDay() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 14, minute: 30))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 10.0)
#expect(days.count == 2)
let cal = Calendar.current
for day in days {
let hour = cal.component(.hour, from: day)
let minute = cal.component(.minute, from: day)
#expect(hour == 0 && minute == 0, "Day should be at midnight")
}
}
@Test("calculateTravelDays - consecutive days are correct")
func travelDays_ConsecutiveDays() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 5, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 20.0)
#expect(days.count == 3)
let cal = Calendar.current
#expect(cal.component(.day, from: days[0]) == 5)
#expect(cal.component(.day, from: days[1]) == 6)
#expect(cal.component(.day, from: days[2]) == 7)
}
@Test("calculateTravelDays - handles month boundary")
func travelDays_HandleMonthBoundary() {
let departure = Calendar.current.date(from: DateComponents(year: 2026, month: 4, day: 30, hour: 8))!
let days = TravelEstimator.calculateTravelDays(departure: departure, drivingHours: 10.0)
#expect(days.count == 2)
let cal = Calendar.current
#expect(cal.component(.month, from: days[0]) == 4)
#expect(cal.component(.day, from: days[0]) == 30)
#expect(cal.component(.month, from: days[1]) == 5)
#expect(cal.component(.day, from: days[1]) == 1)
}
// MARK: - Driving Constraints Tests
@Test("DrivingConstraints - default values")
func constraints_DefaultValues() {
let constraints = DrivingConstraints.default
#expect(constraints.numberOfDrivers == 1)
#expect(constraints.maxHoursPerDriverPerDay == 8.0)
#expect(constraints.maxDailyDrivingHours == 8.0)
}
@Test("DrivingConstraints - multiple drivers increase daily limit")
func constraints_MultipleDrivers() {
let constraints = DrivingConstraints(numberOfDrivers: 2, maxHoursPerDriverPerDay: 8.0)
#expect(constraints.maxDailyDrivingHours == 16.0)
}
@Test("DrivingConstraints - custom hours per driver")
func constraints_CustomHoursPerDriver() {
let constraints = DrivingConstraints(numberOfDrivers: 1, maxHoursPerDriverPerDay: 10.0)
#expect(constraints.maxDailyDrivingHours == 10.0)
}
@Test("DrivingConstraints - enforces minimum 1 driver")
func constraints_MinimumOneDriver() {
let constraints = DrivingConstraints(numberOfDrivers: 0, maxHoursPerDriverPerDay: 8.0)
#expect(constraints.numberOfDrivers == 1)
}
@Test("DrivingConstraints - enforces minimum 1 hour")
func constraints_MinimumOneHour() {
let constraints = DrivingConstraints(numberOfDrivers: 1, maxHoursPerDriverPerDay: 0.5)
#expect(constraints.maxHoursPerDriverPerDay == 1.0)
}
@Test("DrivingConstraints - from preferences")
func constraints_FromPreferences() {
var prefs = TripPreferences()
prefs.numberOfDrivers = 3
prefs.maxDrivingHoursPerDriver = 6.0
let constraints = DrivingConstraints(from: prefs)
#expect(constraints.numberOfDrivers == 3)
#expect(constraints.maxHoursPerDriverPerDay == 6.0)
#expect(constraints.maxDailyDrivingHours == 18.0)
}
@Test("DrivingConstraints - from preferences with nil hours uses default")
func constraints_FromPreferencesNilHours() {
var prefs = TripPreferences()
prefs.numberOfDrivers = 2
prefs.maxDrivingHoursPerDriver = nil
let constraints = DrivingConstraints(from: prefs)
#expect(constraints.maxHoursPerDriverPerDay == 8.0)
}
}