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:
@@ -2,112 +2,181 @@
|
||||
// DynamicSportTests.swift
|
||||
// SportsTimeTests
|
||||
//
|
||||
// TDD specification tests for DynamicSport model.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
@testable import SportsTime
|
||||
|
||||
@Suite("DynamicSport")
|
||||
struct DynamicSportTests {
|
||||
|
||||
@Test("DynamicSport conforms to AnySport protocol")
|
||||
func dynamicSportConformsToAnySport() {
|
||||
let xfl = DynamicSport(
|
||||
id: "xfl",
|
||||
abbreviation: "XFL",
|
||||
displayName: "XFL Football",
|
||||
iconName: "football.fill",
|
||||
colorHex: "#E31837",
|
||||
seasonStartMonth: 2,
|
||||
seasonEndMonth: 5
|
||||
)
|
||||
// MARK: - Test Data
|
||||
|
||||
let sport: any AnySport = xfl
|
||||
#expect(sport.sportId == "xfl")
|
||||
#expect(sport.displayName == "XFL Football")
|
||||
#expect(sport.iconName == "football.fill")
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@Test("DynamicSport color parses from hex")
|
||||
func dynamicSportColorParsesFromHex() {
|
||||
let sport = DynamicSport(
|
||||
id: "test",
|
||||
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: "#FF0000",
|
||||
seasonStartMonth: 1,
|
||||
seasonEndMonth: 12
|
||||
colorHex: "#00FF00",
|
||||
seasonStart: 4,
|
||||
seasonEnd: 9
|
||||
)
|
||||
|
||||
// Color should be red
|
||||
#expect(sport.color != Color.clear)
|
||||
}
|
||||
|
||||
@Test("DynamicSport isInSeason works correctly")
|
||||
func dynamicSportIsInSeason() {
|
||||
let xfl = DynamicSport(
|
||||
id: "xfl",
|
||||
abbreviation: "XFL",
|
||||
displayName: "XFL Football",
|
||||
iconName: "football.fill",
|
||||
colorHex: "#E31837",
|
||||
seasonStartMonth: 2,
|
||||
seasonEndMonth: 5
|
||||
)
|
||||
|
||||
// March is in XFL season (Feb-May)
|
||||
let march = Calendar.current.date(from: DateComponents(year: 2026, month: 3, day: 15))!
|
||||
#expect(xfl.isInSeason(for: march))
|
||||
|
||||
// September is not in XFL season
|
||||
let september = Calendar.current.date(from: DateComponents(year: 2026, month: 9, day: 15))!
|
||||
#expect(!xfl.isInSeason(for: september))
|
||||
}
|
||||
|
||||
@Test("DynamicSport is Hashable")
|
||||
func dynamicSportIsHashable() {
|
||||
let sport1 = DynamicSport(
|
||||
id: "xfl",
|
||||
abbreviation: "XFL",
|
||||
displayName: "XFL Football",
|
||||
iconName: "football.fill",
|
||||
colorHex: "#E31837",
|
||||
seasonStartMonth: 2,
|
||||
seasonEndMonth: 5
|
||||
)
|
||||
|
||||
let sport2 = DynamicSport(
|
||||
id: "xfl",
|
||||
abbreviation: "XFL",
|
||||
displayName: "XFL Football",
|
||||
iconName: "football.fill",
|
||||
colorHex: "#E31837",
|
||||
seasonStartMonth: 2,
|
||||
seasonEndMonth: 5
|
||||
)
|
||||
|
||||
let set: Set<DynamicSport> = [sport1, sport2]
|
||||
#expect(set.count == 1)
|
||||
}
|
||||
|
||||
@Test("DynamicSport is Codable")
|
||||
func dynamicSportIsCodable() throws {
|
||||
let original = DynamicSport(
|
||||
id: "xfl",
|
||||
abbreviation: "XFL",
|
||||
displayName: "XFL Football",
|
||||
iconName: "football.fill",
|
||||
colorHex: "#E31837",
|
||||
seasonStartMonth: 2,
|
||||
seasonEndMonth: 5
|
||||
)
|
||||
|
||||
let encoded = try JSONEncoder().encode(original)
|
||||
let encoded = try JSONEncoder().encode(sport)
|
||||
let decoded = try JSONDecoder().decode(DynamicSport.self, from: encoded)
|
||||
|
||||
#expect(decoded.id == original.id)
|
||||
#expect(decoded.abbreviation == original.abbreviation)
|
||||
#expect(decoded.displayName == original.displayName)
|
||||
#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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user