// // TripTests.swift // SportsTimeTests // // TDD specification tests for Trip model. // import Testing import Foundation @testable import SportsTime @Suite("Trip") struct TripTests { // MARK: - Test Data private var calendar: Calendar { Calendar.current } private func makePreferences() -> TripPreferences { TripPreferences( planningMode: .dateRange, sports: [.mlb], startDate: Date(), endDate: Date().addingTimeInterval(86400 * 7) ) } private func makeStop( city: String, arrivalDate: Date, departureDate: Date, games: [String] = [] ) -> TripStop { TripStop( stopNumber: 1, city: city, state: "XX", arrivalDate: arrivalDate, departureDate: departureDate, games: games ) } // MARK: - Specification Tests: itineraryDays @Test("itineraryDays: returns one day per calendar day") func itineraryDays_oneDayPerCalendarDay() { let start = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))! let end = calendar.date(from: DateComponents(year: 2026, month: 6, day: 18))! let stop = makeStop(city: "NYC", arrivalDate: start, departureDate: end) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) let days = trip.itineraryDays() // 15th, 16th, 17th (18th is departure day, not an activity day) #expect(days.count == 3) } @Test("itineraryDays: dayNumber starts at 1") func itineraryDays_dayNumberStartsAtOne() { let start = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))! let end = calendar.date(from: DateComponents(year: 2026, month: 6, day: 17))! let stop = makeStop(city: "NYC", arrivalDate: start, departureDate: end) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) let days = trip.itineraryDays() #expect(days.first?.dayNumber == 1) } @Test("itineraryDays: dayNumber increments correctly") func itineraryDays_dayNumberIncrements() { let start = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))! let end = calendar.date(from: DateComponents(year: 2026, month: 6, day: 20))! let stop = makeStop(city: "NYC", arrivalDate: start, departureDate: end) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) let days = trip.itineraryDays() for (index, day) in days.enumerated() { #expect(day.dayNumber == index + 1) } } @Test("itineraryDays: empty for trip with no stops") func itineraryDays_emptyForNoStops() { let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [] ) let days = trip.itineraryDays() #expect(days.isEmpty) } // MARK: - Specification Tests: tripDuration @Test("tripDuration: minimum is 1 day") func tripDuration_minimumIsOne() { let date = Date() let stop = makeStop(city: "NYC", arrivalDate: date, departureDate: date) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) #expect(trip.tripDuration >= 1) } @Test("tripDuration: calculates days between first arrival and last departure") func tripDuration_calculatesCorrectly() { let start = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))! let end = calendar.date(from: DateComponents(year: 2026, month: 6, day: 22))! let stop = makeStop(city: "NYC", arrivalDate: start, departureDate: end) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) #expect(trip.tripDuration == 8) // 15th through 22nd = 8 days } @Test("tripDuration: is 0 for trip with no stops") func tripDuration_zeroForNoStops() { let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [] ) #expect(trip.tripDuration == 0) } // MARK: - Specification Tests: cities @Test("cities: returns deduplicated list preserving order") func cities_deduplicatedPreservingOrder() { let date = Date() let stop1 = makeStop(city: "NYC", arrivalDate: date, departureDate: date) let stop2 = makeStop(city: "Boston", arrivalDate: date, departureDate: date) let stop3 = makeStop(city: "NYC", arrivalDate: date, departureDate: date) let stop4 = makeStop(city: "Chicago", arrivalDate: date, departureDate: date) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop1, stop2, stop3, stop4] ) #expect(trip.cities == ["NYC", "Boston", "Chicago"]) } @Test("cities: empty for trip with no stops") func cities_emptyForNoStops() { let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [] ) #expect(trip.cities.isEmpty) } // MARK: - Specification Tests: displayName @Test("displayName: uses arrow separator between cities") func displayName_arrowSeparator() { let date = Date() let stop1 = makeStop(city: "NYC", arrivalDate: date, departureDate: date) let stop2 = makeStop(city: "Boston", arrivalDate: date, departureDate: date) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop1, stop2] ) #expect(trip.displayName == "NYC → Boston") } @Test("displayName: uses trip name when no cities") func displayName_fallsBackToName() { let trip = Trip( name: "My Trip", preferences: makePreferences(), stops: [] ) #expect(trip.displayName == "My Trip") } // MARK: - Specification Tests: Unit Conversions @Test("totalDistanceMiles: converts meters to miles") func totalDistanceMiles_conversion() { let trip = Trip( name: "Test Trip", preferences: makePreferences(), totalDistanceMeters: 160934.4 // ~100 miles ) #expect(abs(trip.totalDistanceMiles - 100.0) < 0.01) } @Test("totalDrivingHours: converts seconds to hours") func totalDrivingHours_conversion() { let trip = Trip( name: "Test Trip", preferences: makePreferences(), totalDrivingSeconds: 7200 // 2 hours ) #expect(trip.totalDrivingHours == 2.0) } // MARK: - Invariant Tests @Test("Invariant: tripDuration >= 1 when stops exist") func invariant_tripDurationMinimum() { let testDates: [(start: DateComponents, end: DateComponents)] = [ (DateComponents(year: 2026, month: 6, day: 15), DateComponents(year: 2026, month: 6, day: 15)), (DateComponents(year: 2026, month: 6, day: 15), DateComponents(year: 2026, month: 6, day: 16)), (DateComponents(year: 2026, month: 6, day: 15), DateComponents(year: 2026, month: 6, day: 22)), ] for (start, end) in testDates { let startDate = calendar.date(from: start)! let endDate = calendar.date(from: end)! let stop = makeStop(city: "NYC", arrivalDate: startDate, departureDate: endDate) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) #expect(trip.tripDuration >= 1) } } @Test("Invariant: cities has no duplicates") func invariant_citiesNoDuplicates() { let date = Date() // Create stops with duplicate cities let stops = [ makeStop(city: "A", arrivalDate: date, departureDate: date), makeStop(city: "B", arrivalDate: date, departureDate: date), makeStop(city: "A", arrivalDate: date, departureDate: date), makeStop(city: "C", arrivalDate: date, departureDate: date), makeStop(city: "B", arrivalDate: date, departureDate: date), ] let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: stops ) let cities = trip.cities let uniqueCities = Set(cities) #expect(cities.count == uniqueCities.count, "cities should not have duplicates") } @Test("Invariant: itineraryDays dayNumber starts at 1 and increments") func invariant_dayNumberSequence() { let start = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))! let end = calendar.date(from: DateComponents(year: 2026, month: 6, day: 20))! let stop = makeStop(city: "NYC", arrivalDate: start, departureDate: end) let trip = Trip( name: "Test Trip", preferences: makePreferences(), stops: [stop] ) let days = trip.itineraryDays() guard !days.isEmpty else { return } #expect(days.first?.dayNumber == 1) for i in 1..