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>
This commit is contained in:
213
SportsTimeTests/Domain/TripStopTests.swift
Normal file
213
SportsTimeTests/Domain/TripStopTests.swift
Normal file
@@ -0,0 +1,213 @@
|
||||
//
|
||||
// TripStopTests.swift
|
||||
// SportsTimeTests
|
||||
//
|
||||
// TDD specification tests for TripStop model.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import SportsTime
|
||||
|
||||
@Suite("TripStop")
|
||||
struct TripStopTests {
|
||||
|
||||
// MARK: - Test Data
|
||||
|
||||
private func makeStop(
|
||||
arrivalDate: Date,
|
||||
departureDate: Date,
|
||||
games: [String] = []
|
||||
) -> TripStop {
|
||||
TripStop(
|
||||
stopNumber: 1,
|
||||
city: "New York",
|
||||
state: "NY",
|
||||
arrivalDate: arrivalDate,
|
||||
departureDate: departureDate,
|
||||
games: games
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Specification Tests: stayDuration
|
||||
|
||||
@Test("stayDuration: same day arrival and departure returns 1")
|
||||
func stayDuration_sameDay() {
|
||||
let calendar = Calendar.current
|
||||
let arrivalDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
let departureDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
|
||||
let stop = makeStop(arrivalDate: arrivalDate, departureDate: departureDate)
|
||||
|
||||
#expect(stop.stayDuration == 1)
|
||||
}
|
||||
|
||||
@Test("stayDuration: 2-day stay returns 2")
|
||||
func stayDuration_twoDays() {
|
||||
let calendar = Calendar.current
|
||||
let arrivalDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
let departureDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 16))!
|
||||
|
||||
let stop = makeStop(arrivalDate: arrivalDate, departureDate: departureDate)
|
||||
|
||||
#expect(stop.stayDuration == 1, "1 day between 15th and 16th")
|
||||
}
|
||||
|
||||
@Test("stayDuration: week-long stay")
|
||||
func stayDuration_weekLong() {
|
||||
let calendar = Calendar.current
|
||||
let arrivalDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
let departureDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 22))!
|
||||
|
||||
let stop = makeStop(arrivalDate: arrivalDate, departureDate: departureDate)
|
||||
|
||||
#expect(stop.stayDuration == 7, "7 days between 15th and 22nd")
|
||||
}
|
||||
|
||||
@Test("stayDuration: minimum is 1 even if dates are reversed")
|
||||
func stayDuration_minimumIsOne() {
|
||||
let calendar = Calendar.current
|
||||
let arrivalDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 20))!
|
||||
let departureDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
|
||||
let stop = makeStop(arrivalDate: arrivalDate, departureDate: departureDate)
|
||||
|
||||
#expect(stop.stayDuration >= 1, "stayDuration should never be less than 1")
|
||||
}
|
||||
|
||||
// MARK: - Specification Tests: hasGames
|
||||
|
||||
@Test("hasGames: true when games array is non-empty")
|
||||
func hasGames_true() {
|
||||
let now = Date()
|
||||
let stop = makeStop(arrivalDate: now, departureDate: now, games: ["game1", "game2"])
|
||||
|
||||
#expect(stop.hasGames == true)
|
||||
}
|
||||
|
||||
@Test("hasGames: false when games array is empty")
|
||||
func hasGames_false() {
|
||||
let now = Date()
|
||||
let stop = makeStop(arrivalDate: now, departureDate: now, games: [])
|
||||
|
||||
#expect(stop.hasGames == false)
|
||||
}
|
||||
|
||||
// MARK: - Specification Tests: formattedDateRange
|
||||
|
||||
@Test("formattedDateRange: single date for 1-day stay")
|
||||
func formattedDateRange_singleDay() {
|
||||
let calendar = Calendar.current
|
||||
let date = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
|
||||
let stop = makeStop(arrivalDate: date, departureDate: date)
|
||||
|
||||
// Should show just "Jun 15"
|
||||
#expect(stop.formattedDateRange == "Jun 15")
|
||||
}
|
||||
|
||||
@Test("formattedDateRange: range for multi-day stay")
|
||||
func formattedDateRange_multiDay() {
|
||||
let calendar = Calendar.current
|
||||
let arrival = calendar.date(from: DateComponents(year: 2026, month: 6, day: 15))!
|
||||
let departure = calendar.date(from: DateComponents(year: 2026, month: 6, day: 18))!
|
||||
|
||||
let stop = makeStop(arrivalDate: arrival, departureDate: departure)
|
||||
|
||||
// Should show "Jun 15 - Jun 18"
|
||||
#expect(stop.formattedDateRange == "Jun 15 - Jun 18")
|
||||
}
|
||||
|
||||
// MARK: - Specification Tests: locationDescription
|
||||
|
||||
@Test("locationDescription: combines city and state")
|
||||
func locationDescription_format() {
|
||||
let stop = TripStop(
|
||||
stopNumber: 1,
|
||||
city: "Boston",
|
||||
state: "MA",
|
||||
arrivalDate: Date(),
|
||||
departureDate: Date()
|
||||
)
|
||||
|
||||
#expect(stop.locationDescription == "Boston, MA")
|
||||
}
|
||||
|
||||
// MARK: - Invariant Tests
|
||||
|
||||
@Test("Invariant: stayDuration >= 1")
|
||||
func invariant_stayDurationAtLeastOne() {
|
||||
let calendar = Calendar.current
|
||||
|
||||
// Test various date combinations
|
||||
let testCases: [(arrival: DateComponents, departure: 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)),
|
||||
(DateComponents(year: 2026, month: 12, day: 31), DateComponents(year: 2027, month: 1, day: 2)),
|
||||
]
|
||||
|
||||
for (arrival, departure) in testCases {
|
||||
let arrivalDate = calendar.date(from: arrival)!
|
||||
let departureDate = calendar.date(from: departure)!
|
||||
let stop = makeStop(arrivalDate: arrivalDate, departureDate: departureDate)
|
||||
|
||||
#expect(stop.stayDuration >= 1, "stayDuration should be at least 1")
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Invariant: hasGames equals !games.isEmpty")
|
||||
func invariant_hasGamesConsistent() {
|
||||
let now = Date()
|
||||
|
||||
let stopWithGames = makeStop(arrivalDate: now, departureDate: now, games: ["game1"])
|
||||
#expect(stopWithGames.hasGames == !stopWithGames.games.isEmpty)
|
||||
|
||||
let stopWithoutGames = makeStop(arrivalDate: now, departureDate: now, games: [])
|
||||
#expect(stopWithoutGames.hasGames == !stopWithoutGames.games.isEmpty)
|
||||
}
|
||||
|
||||
// MARK: - Property Tests
|
||||
|
||||
@Test("Property: isRestDay defaults to false")
|
||||
func property_isRestDayDefault() {
|
||||
let now = Date()
|
||||
let stop = makeStop(arrivalDate: now, departureDate: now)
|
||||
|
||||
#expect(stop.isRestDay == false)
|
||||
}
|
||||
|
||||
@Test("Property: isRestDay can be set to true")
|
||||
func property_isRestDayTrue() {
|
||||
let stop = TripStop(
|
||||
stopNumber: 1,
|
||||
city: "City",
|
||||
state: "ST",
|
||||
arrivalDate: Date(),
|
||||
departureDate: Date(),
|
||||
isRestDay: true
|
||||
)
|
||||
|
||||
#expect(stop.isRestDay == true)
|
||||
}
|
||||
|
||||
@Test("Property: optional fields can be nil")
|
||||
func property_optionalFieldsNil() {
|
||||
let stop = TripStop(
|
||||
stopNumber: 1,
|
||||
city: "City",
|
||||
state: "ST",
|
||||
coordinate: nil,
|
||||
arrivalDate: Date(),
|
||||
departureDate: Date(),
|
||||
stadium: nil,
|
||||
lodging: nil,
|
||||
notes: nil
|
||||
)
|
||||
|
||||
#expect(stop.coordinate == nil)
|
||||
#expect(stop.stadium == nil)
|
||||
#expect(stop.lodging == nil)
|
||||
#expect(stop.notes == nil)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user