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>
194 lines
6.3 KiB
Swift
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)
|
|
}
|
|
}
|