Files
Sportstime/SportsTimeTests/Domain/DynamicSportTests.swift
2026-02-18 13:00:15 -06:00

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 { TestClock.calendar }
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)
}
}