// // AnySportTests.swift // SportsTimeTests // // TDD specification tests for AnySport protocol default implementations. // import Testing import Foundation import SwiftUI @testable import SportsTime // MARK: - Mock AnySport for Testing /// A mock type that conforms to AnySport for testing the default implementation private struct MockSport: AnySport, Hashable { let sportId: String let displayName: String let iconName: String let color: SwiftUI.Color let seasonMonths: (start: Int, end: Int) var id: String { sportId } init( sportId: String = "mock", displayName: String = "Mock Sport", iconName: String = "sportscourt", color: SwiftUI.Color = .blue, seasonStart: Int, seasonEnd: Int ) { self.sportId = sportId self.displayName = displayName self.iconName = iconName self.color = color self.seasonMonths = (seasonStart, seasonEnd) } static func == (lhs: MockSport, rhs: MockSport) -> Bool { lhs.sportId == rhs.sportId } func hash(into hasher: inout Hasher) { hasher.combine(sportId) } } @Suite("AnySport Protocol") struct AnySportTests { // MARK: - Test Data private var calendar: Calendar { TestClock.calendar } private func date(month: Int) -> Date { calendar.date(from: DateComponents(year: 2026, month: month, day: 15))! } // MARK: - Specification Tests: isInSeason (Normal Range) @Test("isInSeason: normal season (start <= end), month in range returns true") func isInSeason_normalSeason_inRange() { // Season: April (4) to October (10) let sport = MockSport(seasonStart: 4, seasonEnd: 10) #expect(sport.isInSeason(for: date(month: 4)) == true) // April #expect(sport.isInSeason(for: date(month: 7)) == true) // July #expect(sport.isInSeason(for: date(month: 10)) == true) // October } @Test("isInSeason: normal season, month outside range returns false") func isInSeason_normalSeason_outOfRange() { // Season: April (4) to October (10) let sport = MockSport(seasonStart: 4, seasonEnd: 10) #expect(sport.isInSeason(for: date(month: 1)) == false) // January #expect(sport.isInSeason(for: date(month: 3)) == false) // March #expect(sport.isInSeason(for: date(month: 11)) == false) // November #expect(sport.isInSeason(for: date(month: 12)) == false) // December } @Test("isInSeason: normal season boundary - start month is in season") func isInSeason_normalSeason_startBoundary() { let sport = MockSport(seasonStart: 3, seasonEnd: 10) #expect(sport.isInSeason(for: date(month: 3)) == true) #expect(sport.isInSeason(for: date(month: 2)) == false) } @Test("isInSeason: normal season boundary - end month is in season") func isInSeason_normalSeason_endBoundary() { let sport = MockSport(seasonStart: 3, seasonEnd: 10) #expect(sport.isInSeason(for: date(month: 10)) == true) #expect(sport.isInSeason(for: date(month: 11)) == false) } // MARK: - Specification Tests: isInSeason (Wrap-Around) @Test("isInSeason: wrap-around season (start > end), month >= start returns true") func isInSeason_wrapAround_afterStart() { // Season: October (10) to June (6) - wraps around year let sport = MockSport(seasonStart: 10, seasonEnd: 6) #expect(sport.isInSeason(for: date(month: 10)) == true) // October (start) #expect(sport.isInSeason(for: date(month: 11)) == true) // November #expect(sport.isInSeason(for: date(month: 12)) == true) // December } @Test("isInSeason: wrap-around season, month <= end returns true") func isInSeason_wrapAround_beforeEnd() { // Season: October (10) to June (6) - wraps around year let sport = MockSport(seasonStart: 10, seasonEnd: 6) #expect(sport.isInSeason(for: date(month: 1)) == true) // January #expect(sport.isInSeason(for: date(month: 3)) == true) // March #expect(sport.isInSeason(for: date(month: 6)) == true) // June (end) } @Test("isInSeason: wrap-around season, gap months return false") func isInSeason_wrapAround_gap() { // Season: October (10) to June (6) - gap is July, August, September let sport = MockSport(seasonStart: 10, seasonEnd: 6) #expect(sport.isInSeason(for: date(month: 7)) == false) // July #expect(sport.isInSeason(for: date(month: 8)) == false) // August #expect(sport.isInSeason(for: date(month: 9)) == false) // September } @Test("isInSeason: wrap-around season boundary - start month is in season") func isInSeason_wrapAround_startBoundary() { let sport = MockSport(seasonStart: 10, seasonEnd: 4) #expect(sport.isInSeason(for: date(month: 10)) == true) #expect(sport.isInSeason(for: date(month: 9)) == false) } @Test("isInSeason: wrap-around season boundary - end month is in season") func isInSeason_wrapAround_endBoundary() { let sport = MockSport(seasonStart: 10, seasonEnd: 4) #expect(sport.isInSeason(for: date(month: 4)) == true) #expect(sport.isInSeason(for: date(month: 5)) == false) } // MARK: - Specification Tests: Edge Cases @Test("isInSeason: single month season (start == end)") func isInSeason_singleMonth() { // Season is only March let sport = MockSport(seasonStart: 3, seasonEnd: 3) #expect(sport.isInSeason(for: date(month: 3)) == true) #expect(sport.isInSeason(for: date(month: 2)) == false) #expect(sport.isInSeason(for: date(month: 4)) == false) } @Test("isInSeason: full year season (January to December)") func isInSeason_fullYear() { let sport = MockSport(seasonStart: 1, seasonEnd: 12) for month in 1...12 { #expect(sport.isInSeason(for: date(month: month)) == true) } } // MARK: - Invariant Tests @Test("Invariant: isInSeason returns true for exactly the months in range") func invariant_exactlyMonthsInRange() { // Normal season: March to October let normalSport = MockSport(seasonStart: 3, seasonEnd: 10) let expectedInSeason = Set(3...10) for month in 1...12 { let expected = expectedInSeason.contains(month) #expect(normalSport.isInSeason(for: date(month: month)) == expected) } } @Test("Invariant: wrap-around isInSeason returns true for months >= start OR <= end") func invariant_wrapAroundMonths() { // Wrap-around season: October to April let wrapSport = MockSport(seasonStart: 10, seasonEnd: 4) // In season: 10, 11, 12, 1, 2, 3, 4 let expectedInSeason = Set([10, 11, 12, 1, 2, 3, 4]) for month in 1...12 { let expected = expectedInSeason.contains(month) #expect(wrapSport.isInSeason(for: date(month: month)) == expected) } } }