Files
Sportstime/SportsTimeTests/Domain/AnySportTests.swift
Trey t 8162b4a029 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>
2026-01-16 14:07:41 -06:00

195 lines
6.9 KiB
Swift

//
// 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 { Calendar.current }
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)
}
}
}