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>
195 lines
6.9 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|