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>
183 lines
5.7 KiB
Swift
183 lines
5.7 KiB
Swift
//
|
|
// DynamicSportTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
// TDD specification tests for DynamicSport model.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
import SwiftUI
|
|
@testable import SportsTime
|
|
|
|
@Suite("DynamicSport")
|
|
struct DynamicSportTests {
|
|
|
|
// MARK: - Test Data
|
|
|
|
private func makeDynamicSport(
|
|
id: String = "xfl",
|
|
abbreviation: String = "XFL",
|
|
displayName: String = "XFL Football",
|
|
iconName: String = "football.fill",
|
|
colorHex: String = "#FF0000",
|
|
seasonStart: Int = 2,
|
|
seasonEnd: Int = 5
|
|
) -> DynamicSport {
|
|
DynamicSport(
|
|
id: id,
|
|
abbreviation: abbreviation,
|
|
displayName: displayName,
|
|
iconName: iconName,
|
|
colorHex: colorHex,
|
|
seasonStartMonth: seasonStart,
|
|
seasonEndMonth: seasonEnd
|
|
)
|
|
}
|
|
|
|
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: AnySport Conformance
|
|
|
|
@Test("sportId: returns id")
|
|
func sportId_returnsId() {
|
|
let sport = makeDynamicSport(id: "test_sport")
|
|
|
|
#expect(sport.sportId == "test_sport")
|
|
}
|
|
|
|
@Test("displayName: returns displayName property")
|
|
func displayName_returnsProperty() {
|
|
let sport = makeDynamicSport(displayName: "Test Sport Name")
|
|
|
|
#expect(sport.displayName == "Test Sport Name")
|
|
}
|
|
|
|
@Test("iconName: returns iconName property")
|
|
func iconName_returnsProperty() {
|
|
let sport = makeDynamicSport(iconName: "custom.icon")
|
|
|
|
#expect(sport.iconName == "custom.icon")
|
|
}
|
|
|
|
@Test("seasonMonths: returns (seasonStartMonth, seasonEndMonth)")
|
|
func seasonMonths_returnsTuple() {
|
|
let sport = makeDynamicSport(seasonStart: 3, seasonEnd: 10)
|
|
|
|
let (start, end) = sport.seasonMonths
|
|
#expect(start == 3)
|
|
#expect(end == 10)
|
|
}
|
|
|
|
@Test("color: converts colorHex to Color")
|
|
func color_convertsHex() {
|
|
let sport = makeDynamicSport(colorHex: "#FF0000")
|
|
|
|
// We can't directly compare Colors, but we can verify it doesn't crash
|
|
// and returns some color value
|
|
let _ = sport.color
|
|
}
|
|
|
|
// MARK: - Specification Tests: isInSeason (via AnySport)
|
|
|
|
@Test("isInSeason: uses default AnySport implementation for normal season")
|
|
func isInSeason_normalSeason() {
|
|
// Season: February to May (normal range)
|
|
let sport = makeDynamicSport(seasonStart: 2, seasonEnd: 5)
|
|
|
|
#expect(sport.isInSeason(for: date(month: 2)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 3)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 5)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 1)) == false)
|
|
#expect(sport.isInSeason(for: date(month: 6)) == false)
|
|
}
|
|
|
|
@Test("isInSeason: uses default AnySport implementation for wrap-around season")
|
|
func isInSeason_wrapAroundSeason() {
|
|
// Season: October to April (wraps around)
|
|
let sport = makeDynamicSport(seasonStart: 10, seasonEnd: 4)
|
|
|
|
#expect(sport.isInSeason(for: date(month: 10)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 12)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 1)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 4)) == true)
|
|
#expect(sport.isInSeason(for: date(month: 6)) == false)
|
|
#expect(sport.isInSeason(for: date(month: 8)) == false)
|
|
}
|
|
|
|
// MARK: - Specification Tests: Identifiable Conformance
|
|
|
|
@Test("id: returns id property")
|
|
func id_returnsIdProperty() {
|
|
let sport = makeDynamicSport(id: "unique_id")
|
|
|
|
#expect(sport.id == "unique_id")
|
|
}
|
|
|
|
// MARK: - Specification Tests: Hashable Conformance
|
|
|
|
@Test("hashable: same values are equal")
|
|
func hashable_equality() {
|
|
let sport1 = makeDynamicSport(id: "test")
|
|
let sport2 = makeDynamicSport(id: "test")
|
|
|
|
#expect(sport1 == sport2)
|
|
}
|
|
|
|
@Test("hashable: different ids are not equal")
|
|
func hashable_inequality() {
|
|
let sport1 = makeDynamicSport(id: "test1")
|
|
let sport2 = makeDynamicSport(id: "test2")
|
|
|
|
#expect(sport1 != sport2)
|
|
}
|
|
|
|
// MARK: - Specification Tests: Codable Conformance
|
|
|
|
@Test("codable: encodes and decodes correctly")
|
|
func codable_roundTrip() throws {
|
|
let sport = makeDynamicSport(
|
|
id: "test_sport",
|
|
abbreviation: "TST",
|
|
displayName: "Test Sport",
|
|
iconName: "star.fill",
|
|
colorHex: "#00FF00",
|
|
seasonStart: 4,
|
|
seasonEnd: 9
|
|
)
|
|
|
|
let encoded = try JSONEncoder().encode(sport)
|
|
let decoded = try JSONDecoder().decode(DynamicSport.self, from: encoded)
|
|
|
|
#expect(decoded.id == sport.id)
|
|
#expect(decoded.abbreviation == sport.abbreviation)
|
|
#expect(decoded.displayName == sport.displayName)
|
|
#expect(decoded.iconName == sport.iconName)
|
|
#expect(decoded.colorHex == sport.colorHex)
|
|
#expect(decoded.seasonStartMonth == sport.seasonStartMonth)
|
|
#expect(decoded.seasonEndMonth == sport.seasonEndMonth)
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
@Test("Invariant: sportId == id")
|
|
func invariant_sportIdEqualsId() {
|
|
let sport = makeDynamicSport(id: "any_id")
|
|
|
|
#expect(sport.sportId == sport.id)
|
|
}
|
|
|
|
@Test("Invariant: seasonMonths matches individual properties")
|
|
func invariant_seasonMonthsMatchesProperties() {
|
|
let sport = makeDynamicSport(seasonStart: 5, seasonEnd: 11)
|
|
|
|
let (start, end) = sport.seasonMonths
|
|
#expect(start == sport.seasonStartMonth)
|
|
#expect(end == sport.seasonEndMonth)
|
|
}
|
|
}
|