Files
Sportstime/SportsTimeTests/Domain/TravelSegmentTests.swift
Trey t 8162b4a029 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>
2026-01-16 14:07:41 -06:00

194 lines
6.3 KiB
Swift

//
// TravelSegmentTests.swift
// SportsTimeTests
//
// TDD specification tests for TravelSegment model.
//
import Testing
@testable import SportsTime
@Suite("TravelSegment")
struct TravelSegmentTests {
// MARK: - Test Data
private func makeSegment(
distanceMeters: Double,
durationSeconds: Double
) -> TravelSegment {
TravelSegment(
fromLocation: LocationInput(name: "A"),
toLocation: LocationInput(name: "B"),
travelMode: .drive,
distanceMeters: distanceMeters,
durationSeconds: durationSeconds
)
}
// MARK: - Specification Tests: Unit Conversions
@Test("distanceMiles: converts meters to miles correctly")
func distanceMiles_conversion() {
// 1 mile = 1609.344 meters
// So 1609.344 meters should be ~1 mile
let segment = makeSegment(distanceMeters: 1609.344, durationSeconds: 3600)
#expect(abs(segment.distanceMiles - 1.0) < 0.001, "1609.344 meters should be ~1 mile")
}
@Test("distanceMiles: 100 miles")
func distanceMiles_100miles() {
let metersIn100Miles = 160934.4
let segment = makeSegment(distanceMeters: metersIn100Miles, durationSeconds: 3600)
#expect(abs(segment.distanceMiles - 100.0) < 0.01)
}
@Test("distanceMiles: zero meters is zero miles")
func distanceMiles_zero() {
let segment = makeSegment(distanceMeters: 0, durationSeconds: 3600)
#expect(segment.distanceMiles == 0)
}
@Test("durationHours: converts seconds to hours correctly")
func durationHours_conversion() {
// 3600 seconds = 1 hour
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)
#expect(segment.durationHours == 1.0)
}
@Test("durationHours: 2.5 hours")
func durationHours_twoAndHalf() {
// 2.5 hours = 9000 seconds
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 9000)
#expect(segment.durationHours == 2.5)
}
@Test("durationHours: zero seconds is zero hours")
func durationHours_zero() {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 0)
#expect(segment.durationHours == 0)
}
// MARK: - Specification Tests: Aliases
@Test("estimatedDrivingHours is alias for durationHours")
func estimatedDrivingHours_alias() {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 7200)
#expect(segment.estimatedDrivingHours == segment.durationHours)
#expect(segment.estimatedDrivingHours == 2.0)
}
@Test("estimatedDistanceMiles is alias for distanceMiles")
func estimatedDistanceMiles_alias() {
let segment = makeSegment(distanceMeters: 160934.4, durationSeconds: 3600)
#expect(segment.estimatedDistanceMiles == segment.distanceMiles)
#expect(abs(segment.estimatedDistanceMiles - 100.0) < 0.01)
}
// MARK: - Specification Tests: formattedDuration
@Test("formattedDuration: shows hours and minutes when both present")
func formattedDuration_hoursAndMinutes() {
// 2h 30m = 9000 seconds
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 9000)
#expect(segment.formattedDuration == "2h 30m")
}
@Test("formattedDuration: shows only hours when minutes are zero")
func formattedDuration_onlyHours() {
// 3h = 10800 seconds
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 10800)
#expect(segment.formattedDuration == "3h")
}
@Test("formattedDuration: shows only minutes when hours are zero")
func formattedDuration_onlyMinutes() {
// 45m = 2700 seconds
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 2700)
#expect(segment.formattedDuration == "45m")
}
@Test("formattedDuration: shows 0m for zero duration")
func formattedDuration_zero() {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 0)
#expect(segment.formattedDuration == "0m")
}
// MARK: - Specification Tests: formattedDistance
@Test("formattedDistance: shows miles rounded to integer")
func formattedDistance_rounded() {
// 100.7 miles
let segment = makeSegment(distanceMeters: 162115.4, durationSeconds: 3600)
#expect(segment.formattedDistance == "101 mi")
}
// MARK: - Invariant Tests
@Test("Invariant: distanceMiles positive when meters positive")
func invariant_distanceMilesPositive() {
let testMeters: [Double] = [1, 100, 1000, 100000, 1000000]
for meters in testMeters {
let segment = makeSegment(distanceMeters: meters, durationSeconds: 3600)
#expect(segment.distanceMiles > 0, "distanceMiles should be positive for \(meters) meters")
}
}
@Test("Invariant: durationHours positive when seconds positive")
func invariant_durationHoursPositive() {
let testSeconds: [Double] = [1, 60, 3600, 7200, 36000]
for seconds in testSeconds {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: seconds)
#expect(segment.durationHours > 0, "durationHours should be positive for \(seconds) seconds")
}
}
@Test("Invariant: conversion factors are consistent")
func invariant_conversionFactorsConsistent() {
// 0.000621371 miles per meter
let meters: Double = 1000
let segment = makeSegment(distanceMeters: meters, durationSeconds: 3600)
let expectedMiles = meters * 0.000621371
#expect(segment.distanceMiles == expectedMiles)
}
// MARK: - Property Tests
@Test("Property: scenicScore defaults to 0.5")
func property_scenicScoreDefault() {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)
#expect(segment.scenicScore == 0.5)
}
@Test("Property: evChargingStops defaults to empty")
func property_evChargingStopsDefault() {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)
#expect(segment.evChargingStops.isEmpty)
}
@Test("Property: routePolyline defaults to nil")
func property_routePolylineDefault() {
let segment = makeSegment(distanceMeters: 1000, durationSeconds: 3600)
#expect(segment.routePolyline == nil)
}
}