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:
194
SportsTimeTests/Domain/AnySportTests.swift
Normal file
194
SportsTimeTests/Domain/AnySportTests.swift
Normal file
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user