// // 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) } }