// // AchievementDefinitionsTests.swift // SportsTimeTests // // TDD specification tests for AchievementRegistry and related models. // import Testing import SwiftUI @testable import SportsTime @Suite("AchievementCategory") struct AchievementCategoryTests { @Test("Property: all cases have displayName") func property_allHaveDisplayName() { for category in AchievementCategory.allCases { #expect(!category.displayName.isEmpty) } } @Test("displayName: count returns 'Milestones'") func displayName_count() { #expect(AchievementCategory.count.displayName == "Milestones") } @Test("displayName: division returns 'Divisions'") func displayName_division() { #expect(AchievementCategory.division.displayName == "Divisions") } } // MARK: - AchievementDefinition Tests @Suite("AchievementDefinition") struct AchievementDefinitionTests { @Test("equality: based on id only") func equality_basedOnId() { let def1 = AchievementDefinition( id: "test_achievement", name: "Name A", description: "Description A", category: .count, iconName: "icon.a", iconColor: .blue, requirement: .visitCount(5) ) let def2 = AchievementDefinition( id: "test_achievement", name: "Different Name", description: "Different Description", category: .division, iconName: "icon.b", iconColor: .red, requirement: .visitCount(10) ) #expect(def1 == def2, "Achievements with same id should be equal") } @Test("inequality: different ids") func inequality_differentIds() { let def1 = AchievementDefinition( id: "achievement_1", name: "Same Name", description: "Same Description", category: .count, iconName: "icon", iconColor: .blue, requirement: .visitCount(5) ) let def2 = AchievementDefinition( id: "achievement_2", name: "Same Name", description: "Same Description", category: .count, iconName: "icon", iconColor: .blue, requirement: .visitCount(5) ) #expect(def1 != def2, "Achievements with different ids should not be equal") } @Test("Property: divisionId can be set") func property_divisionId() { let def = AchievementDefinition( id: "test_div_ach", name: "Division Achievement", description: "Complete a division", category: .division, sport: .mlb, iconName: "baseball.fill", iconColor: .red, requirement: .completeDivision("mlb_al_east"), divisionId: "mlb_al_east" ) #expect(def.divisionId == "mlb_al_east") } @Test("Property: conferenceId can be set") func property_conferenceId() { let def = AchievementDefinition( id: "test_conf_ach", name: "Conference Achievement", description: "Complete a conference", category: .conference, sport: .mlb, iconName: "baseball.fill", iconColor: .red, requirement: .completeConference("mlb_al"), conferenceId: "mlb_al" ) #expect(def.conferenceId == "mlb_al") } } // MARK: - AchievementRegistry Tests @Suite("AchievementRegistry") struct AchievementRegistryTests { // MARK: - Specification Tests: all @Test("all: is sorted by sortOrder ascending") func all_sortedBySortOrder() { let achievements = AchievementRegistry.all for i in 1..= achievements[i - 1].sortOrder, "Achievement at index \(i) should have sortOrder >= previous" ) } } @Test("all: contains count achievements") func all_containsCountAchievements() { let countAchievements = AchievementRegistry.all.filter { $0.category == .count } #expect(countAchievements.count >= 5, "Should have multiple count achievements") } @Test("all: contains division achievements") func all_containsDivisionAchievements() { let divisionAchievements = AchievementRegistry.all.filter { $0.category == .division } #expect(divisionAchievements.count >= 16, "Should have division achievements for MLB, NBA, NHL") } // MARK: - Specification Tests: achievement(byId:) @Test("achievement(byId:): finds existing achievement") func achievementById_found() { let achievement = AchievementRegistry.achievement(byId: "first_visit") #expect(achievement != nil) #expect(achievement?.name == "First Pitch") } @Test("achievement(byId:): returns nil for unknown ID") func achievementById_notFound() { let achievement = AchievementRegistry.achievement(byId: "nonexistent_achievement") #expect(achievement == nil) } // MARK: - Specification Tests: achievements(forCategory:) @Test("achievements(forCategory:): filters by exact category") func achievementsForCategory_filters() { let countAchievements = AchievementRegistry.achievements(forCategory: .count) for achievement in countAchievements { #expect(achievement.category == .count) } } @Test("achievements(forCategory:): returns all division achievements") func achievementsForCategory_division() { let divisionAchievements = AchievementRegistry.achievements(forCategory: .division) #expect(!divisionAchievements.isEmpty) for achievement in divisionAchievements { #expect(achievement.category == .division) } } // MARK: - Specification Tests: achievements(forSport:) @Test("achievements(forSport:): includes sport-specific achievements") func achievementsForSport_includesSportSpecific() { let mlbAchievements = AchievementRegistry.achievements(forSport: .mlb) let sportSpecific = mlbAchievements.filter { $0.sport == .mlb } #expect(!sportSpecific.isEmpty, "Should include MLB-specific achievements") } @Test("achievements(forSport:): includes cross-sport achievements") func achievementsForSport_includesCrossSport() { let mlbAchievements = AchievementRegistry.achievements(forSport: .mlb) let crossSport = mlbAchievements.filter { $0.sport == nil } #expect(!crossSport.isEmpty, "Should include cross-sport achievements (sport == nil)") } // MARK: - Specification Tests: divisionAchievements(forSport:) @Test("divisionAchievements(forSport:): filters to division category AND sport") func divisionAchievementsForSport_filters() { let mlbDivisionAchievements = AchievementRegistry.divisionAchievements(forSport: .mlb) #expect(mlbDivisionAchievements.count == 6, "MLB has 6 divisions") for achievement in mlbDivisionAchievements { #expect(achievement.category == .division) #expect(achievement.sport == .mlb) } } @Test("divisionAchievements(forSport:): NBA has 6 divisions") func divisionAchievementsForSport_nba() { let nbaDivisionAchievements = AchievementRegistry.divisionAchievements(forSport: .nba) #expect(nbaDivisionAchievements.count == 6) } @Test("divisionAchievements(forSport:): NHL has 4 divisions") func divisionAchievementsForSport_nhl() { let nhlDivisionAchievements = AchievementRegistry.divisionAchievements(forSport: .nhl) #expect(nhlDivisionAchievements.count == 4) } // MARK: - Specification Tests: conferenceAchievements(forSport:) @Test("conferenceAchievements(forSport:): filters to conference category AND sport") func conferenceAchievementsForSport_filters() { let mlbConferenceAchievements = AchievementRegistry.conferenceAchievements(forSport: .mlb) #expect(mlbConferenceAchievements.count == 2, "MLB has 2 leagues (AL, NL)") for achievement in mlbConferenceAchievements { #expect(achievement.category == .conference) #expect(achievement.sport == .mlb) } } // MARK: - Invariant Tests @Test("Invariant: all achievement IDs are unique") func invariant_uniqueIds() { let ids = AchievementRegistry.all.map { $0.id } let uniqueIds = Set(ids) #expect(ids.count == uniqueIds.count, "All achievement IDs should be unique") } @Test("Invariant: division achievements have non-nil divisionId") func invariant_divisionAchievementsHaveDivisionId() { let divisionAchievements = AchievementRegistry.achievements(forCategory: .division) for achievement in divisionAchievements { #expect( achievement.divisionId != nil, "Division achievement \(achievement.id) should have divisionId" ) } } @Test("Invariant: conference achievements have non-nil conferenceId") func invariant_conferenceAchievementsHaveConferenceId() { let conferenceAchievements = AchievementRegistry.achievements(forCategory: .conference) for achievement in conferenceAchievements { #expect( achievement.conferenceId != nil, "Conference achievement \(achievement.id) should have conferenceId" ) } } @Test("Invariant: all achievements have non-empty name") func invariant_allHaveNames() { for achievement in AchievementRegistry.all { #expect(!achievement.name.isEmpty, "Achievement \(achievement.id) should have a name") } } @Test("Invariant: all achievements have non-empty description") func invariant_allHaveDescriptions() { for achievement in AchievementRegistry.all { #expect(!achievement.description.isEmpty, "Achievement \(achievement.id) should have a description") } } @Test("Invariant: all achievements have non-empty iconName") func invariant_allHaveIconNames() { for achievement in AchievementRegistry.all { #expect(!achievement.iconName.isEmpty, "Achievement \(achievement.id) should have an iconName") } } } // MARK: - AchievementRequirement Tests @Suite("AchievementRequirement") struct AchievementRequirementTests { @Test("Property: visitCount requirement stores count") func visitCount_storesValue() { let requirement = AchievementRequirement.visitCount(10) if case .visitCount(let count) = requirement { #expect(count == 10) } else { Issue.record("Should be visitCount case") } } @Test("Property: completeDivision requirement stores division ID") func completeDivision_storesId() { let requirement = AchievementRequirement.completeDivision("mlb_al_east") if case .completeDivision(let divisionId) = requirement { #expect(divisionId == "mlb_al_east") } else { Issue.record("Should be completeDivision case") } } @Test("Property: visitsInDays requirement stores both values") func visitsInDays_storesValues() { let requirement = AchievementRequirement.visitsInDays(5, days: 7) if case .visitsInDays(let visits, let days) = requirement { #expect(visits == 5) #expect(days == 7) } else { Issue.record("Should be visitsInDays case") } } @Test("hashable: same requirements are equal") func hashable_equality() { let req1 = AchievementRequirement.visitCount(10) let req2 = AchievementRequirement.visitCount(10) #expect(req1 == req2) } @Test("hashable: different requirements are not equal") func hashable_inequality() { let req1 = AchievementRequirement.visitCount(10) let req2 = AchievementRequirement.visitCount(20) #expect(req1 != req2) } }