// // SportTests.swift // SportsTimeTests // // TDD specification tests for Sport enum. // import Testing import Foundation @testable import SportsTime @Suite("Sport") struct SportTests { // MARK: - Specification Tests: Season Months @Test("MLB season: March (3) to October (10)") func mlb_seasonMonths() { let (start, end) = Sport.mlb.seasonMonths #expect(start == 3) #expect(end == 10) } @Test("NBA season: October (10) to June (6) - wraps around year") func nba_seasonMonths() { let (start, end) = Sport.nba.seasonMonths #expect(start == 10) #expect(end == 6) } @Test("NHL season: October (10) to June (6) - wraps around year") func nhl_seasonMonths() { let (start, end) = Sport.nhl.seasonMonths #expect(start == 10) #expect(end == 6) } @Test("NFL season: September (9) to February (2) - wraps around year") func nfl_seasonMonths() { let (start, end) = Sport.nfl.seasonMonths #expect(start == 9) #expect(end == 2) } @Test("MLS season: February (2) to December (12)") func mls_seasonMonths() { let (start, end) = Sport.mls.seasonMonths #expect(start == 2) #expect(end == 12) } @Test("WNBA season: May (5) to October (10)") func wnba_seasonMonths() { let (start, end) = Sport.wnba.seasonMonths #expect(start == 5) #expect(end == 10) } @Test("NWSL season: March (3) to November (11)") func nwsl_seasonMonths() { let (start, end) = Sport.nwsl.seasonMonths #expect(start == 3) #expect(end == 11) } // MARK: - Specification Tests: isInSeason (Normal Range) @Test("MLB: isInSeason returns true for months 3-10") func mlb_isInSeason_normalRange() { let calendar = TestClock.calendar // In season: March through October for month in 3...10 { let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! #expect(Sport.mlb.isInSeason(for: date), "MLB should be in season in month \(month)") } // Out of season: January, February, November, December for month in [1, 2, 11, 12] { let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! #expect(!Sport.mlb.isInSeason(for: date), "MLB should NOT be in season in month \(month)") } } // MARK: - Specification Tests: isInSeason (Wrap-Around) @Test("NBA: isInSeason returns true for months 10-12 and 1-6 (wrap-around)") func nba_isInSeason_wrapAround() { let calendar = TestClock.calendar // In season: October through June (wraps) let inSeasonMonths = [10, 11, 12, 1, 2, 3, 4, 5, 6] for month in inSeasonMonths { let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! #expect(Sport.nba.isInSeason(for: date), "NBA should be in season in month \(month)") } // Out of season: July, August, September for month in [7, 8, 9] { let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! #expect(!Sport.nba.isInSeason(for: date), "NBA should NOT be in season in month \(month)") } } @Test("NFL: isInSeason returns true for months 9-12 and 1-2 (wrap-around)") func nfl_isInSeason_wrapAround() { let calendar = TestClock.calendar // In season: September through February (wraps) let inSeasonMonths = [9, 10, 11, 12, 1, 2] for month in inSeasonMonths { let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! #expect(Sport.nfl.isInSeason(for: date), "NFL should be in season in month \(month)") } // Out of season: March through August for month in 3...8 { let date = calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! #expect(!Sport.nfl.isInSeason(for: date), "NFL should NOT be in season in month \(month)") } } // MARK: - Specification Tests: Boundary Values @Test("isInSeason boundary: first and last day of season month") func isInSeason_boundaryDays() { // Production isInSeason uses Calendar.current to extract month. // Use noon (hour: 12) so the date stays in the correct calendar day // regardless of system timezone across the US. // MLB: First day of March (in season) let marchFirst = TestFixtures.date(year: 2026, month: 3, day: 1, hour: 12) #expect(Sport.mlb.isInSeason(for: marchFirst)) // MLB: Last day of October (in season) let octLast = TestFixtures.date(year: 2026, month: 10, day: 31, hour: 12) #expect(Sport.mlb.isInSeason(for: octLast)) // MLB: First day of November (out of season) let novFirst = TestFixtures.date(year: 2026, month: 11, day: 1, hour: 12) #expect(!Sport.mlb.isInSeason(for: novFirst)) // MLB: Last day of February (out of season) let febLast = TestFixtures.date(year: 2026, month: 2, day: 28, hour: 12) #expect(!Sport.mlb.isInSeason(for: febLast)) } // MARK: - Specification Tests: Supported Sports @Test("supported returns all 7 sports") func supported_returnsAllSports() { let supported = Sport.supported #expect(supported.count == 7) #expect(supported.contains(.mlb)) #expect(supported.contains(.nba)) #expect(supported.contains(.nhl)) #expect(supported.contains(.nfl)) #expect(supported.contains(.mls)) #expect(supported.contains(.wnba)) #expect(supported.contains(.nwsl)) } @Test("CaseIterable matches supported") func caseIterable_matchesSupported() { let allCases = Set(Sport.allCases) let supported = Set(Sport.supported) #expect(allCases == supported) } // MARK: - Invariant Tests @Test("Invariant: seasonMonths values are always 1-12") func invariant_seasonMonthsValidRange() { for sport in Sport.allCases { let (start, end) = sport.seasonMonths #expect(start >= 1 && start <= 12, "\(sport) start month must be 1-12") #expect(end >= 1 && end <= 12, "\(sport) end month must be 1-12") } } @Test("Invariant: each sport has unique displayName") func invariant_uniqueDisplayNames() { var displayNames: Set = [] for sport in Sport.allCases { #expect(!displayNames.contains(sport.displayName), "Duplicate displayName: \(sport.displayName)") displayNames.insert(sport.displayName) } } // MARK: - Property Tests @Test("Property: id equals rawValue") func property_idEqualsRawValue() { for sport in Sport.allCases { #expect(sport.id == sport.rawValue) } } @Test("Property: sportId equals rawValue (AnySport conformance)") func property_sportIdEqualsRawValue() { for sport in Sport.allCases { #expect(sport.sportId == sport.rawValue) } } }