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>
275 lines
9.0 KiB
Swift
275 lines
9.0 KiB
Swift
//
|
|
// ShareableContentTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
// TDD specification tests for ShareableContent types.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
import SwiftUI
|
|
@testable import SportsTime
|
|
|
|
// MARK: - ShareCardType Tests
|
|
|
|
@Suite("ShareCardType")
|
|
struct ShareCardTypeTests {
|
|
|
|
// MARK: - Specification Tests: CaseIterable
|
|
|
|
/// - Expected Behavior: Includes all expected card types
|
|
@Test("allCases: includes all card types")
|
|
func allCases_includesAll() {
|
|
let allTypes = ShareCardType.allCases
|
|
|
|
#expect(allTypes.contains(.tripSummary))
|
|
#expect(allTypes.contains(.achievementSpotlight))
|
|
#expect(allTypes.contains(.achievementCollection))
|
|
#expect(allTypes.contains(.achievementMilestone))
|
|
#expect(allTypes.contains(.achievementContext))
|
|
#expect(allTypes.contains(.stadiumProgress))
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: Each type has a unique rawValue
|
|
@Test("Invariant: unique rawValues")
|
|
func invariant_uniqueRawValues() {
|
|
let allTypes = ShareCardType.allCases
|
|
let rawValues = allTypes.map { $0.rawValue }
|
|
let uniqueRawValues = Set(rawValues)
|
|
|
|
#expect(rawValues.count == uniqueRawValues.count)
|
|
}
|
|
|
|
/// - Invariant: Count matches expected number
|
|
@Test("Invariant: correct count")
|
|
func invariant_correctCount() {
|
|
#expect(ShareCardType.allCases.count == 6)
|
|
}
|
|
}
|
|
|
|
// MARK: - ShareTheme Tests
|
|
|
|
@Suite("ShareTheme")
|
|
struct ShareThemeTests {
|
|
|
|
// MARK: - Specification Tests: Static Themes
|
|
|
|
/// - Expected Behavior: All preset themes are accessible
|
|
@Test("Static themes: all presets exist")
|
|
func staticThemes_allExist() {
|
|
// Access each theme to ensure they exist
|
|
let _ = ShareTheme.dark
|
|
let _ = ShareTheme.light
|
|
let _ = ShareTheme.midnight
|
|
let _ = ShareTheme.forest
|
|
let _ = ShareTheme.sunset
|
|
let _ = ShareTheme.berry
|
|
let _ = ShareTheme.ocean
|
|
let _ = ShareTheme.slate
|
|
|
|
#expect(true) // If we got here, all themes exist
|
|
}
|
|
|
|
/// - Expected Behavior: all array contains all themes
|
|
@Test("all: contains all preset themes")
|
|
func all_containsAllThemes() {
|
|
let all = ShareTheme.all
|
|
|
|
#expect(all.count == 8)
|
|
#expect(all.contains(where: { $0.id == "dark" }))
|
|
#expect(all.contains(where: { $0.id == "light" }))
|
|
#expect(all.contains(where: { $0.id == "midnight" }))
|
|
#expect(all.contains(where: { $0.id == "forest" }))
|
|
#expect(all.contains(where: { $0.id == "sunset" }))
|
|
#expect(all.contains(where: { $0.id == "berry" }))
|
|
#expect(all.contains(where: { $0.id == "ocean" }))
|
|
#expect(all.contains(where: { $0.id == "slate" }))
|
|
}
|
|
|
|
// MARK: - Specification Tests: theme(byId:)
|
|
|
|
/// - Expected Behavior: Returns matching theme by id
|
|
@Test("theme(byId:): returns matching theme")
|
|
func themeById_returnsMatching() {
|
|
let dark = ShareTheme.theme(byId: "dark")
|
|
let light = ShareTheme.theme(byId: "light")
|
|
|
|
#expect(dark.id == "dark")
|
|
#expect(light.id == "light")
|
|
}
|
|
|
|
/// - Expected Behavior: Returns dark theme for unknown id
|
|
@Test("theme(byId:): returns dark for unknown id")
|
|
func themeById_unknownReturnsDark() {
|
|
let unknown = ShareTheme.theme(byId: "nonexistent")
|
|
#expect(unknown.id == "dark")
|
|
}
|
|
|
|
/// - Expected Behavior: Finds all valid themes by id
|
|
@Test("theme(byId:): finds all valid themes")
|
|
func themeById_findsAllValid() {
|
|
let ids = ["dark", "light", "midnight", "forest", "sunset", "berry", "ocean", "slate"]
|
|
|
|
for id in ids {
|
|
let theme = ShareTheme.theme(byId: id)
|
|
#expect(theme.id == id)
|
|
}
|
|
}
|
|
|
|
// MARK: - Specification Tests: Properties
|
|
|
|
/// - Expected Behavior: Each theme has required properties
|
|
@Test("Properties: themes have all required fields")
|
|
func properties_allRequired() {
|
|
for theme in ShareTheme.all {
|
|
#expect(!theme.id.isEmpty)
|
|
#expect(!theme.name.isEmpty)
|
|
#expect(theme.gradientColors.count >= 2)
|
|
// Colors exist (can't easily test Color values)
|
|
}
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: All themes have unique ids
|
|
@Test("Invariant: unique theme ids")
|
|
func invariant_uniqueIds() {
|
|
let ids = ShareTheme.all.map { $0.id }
|
|
let uniqueIds = Set(ids)
|
|
|
|
#expect(ids.count == uniqueIds.count)
|
|
}
|
|
|
|
/// - Invariant: All themes are Hashable and Identifiable
|
|
@Test("Invariant: themes are Hashable")
|
|
func invariant_hashable() {
|
|
var themeSet: Set<ShareTheme> = []
|
|
|
|
for theme in ShareTheme.all {
|
|
themeSet.insert(theme)
|
|
}
|
|
|
|
#expect(themeSet.count == ShareTheme.all.count)
|
|
}
|
|
}
|
|
|
|
// MARK: - ShareError Tests
|
|
|
|
@Suite("ShareError")
|
|
struct ShareErrorTests {
|
|
|
|
// MARK: - Specification Tests: errorDescription
|
|
|
|
/// - Expected Behavior: renderingFailed explains render failure
|
|
@Test("errorDescription: renderingFailed mentions render")
|
|
func errorDescription_renderingFailed() {
|
|
let error = ShareError.renderingFailed
|
|
#expect(error.errorDescription != nil)
|
|
#expect(error.errorDescription!.lowercased().contains("render") || error.errorDescription!.lowercased().contains("failed"))
|
|
}
|
|
|
|
/// - Expected Behavior: mapSnapshotFailed explains snapshot failure
|
|
@Test("errorDescription: mapSnapshotFailed mentions map")
|
|
func errorDescription_mapSnapshotFailed() {
|
|
let error = ShareError.mapSnapshotFailed
|
|
#expect(error.errorDescription != nil)
|
|
#expect(error.errorDescription!.lowercased().contains("map") || error.errorDescription!.lowercased().contains("snapshot"))
|
|
}
|
|
|
|
/// - Expected Behavior: instagramNotInstalled explains Instagram requirement
|
|
@Test("errorDescription: instagramNotInstalled mentions Instagram")
|
|
func errorDescription_instagramNotInstalled() {
|
|
let error = ShareError.instagramNotInstalled
|
|
#expect(error.errorDescription != nil)
|
|
#expect(error.errorDescription!.lowercased().contains("instagram"))
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: All errors have non-empty descriptions
|
|
@Test("Invariant: all errors have descriptions")
|
|
func invariant_allHaveDescriptions() {
|
|
let errors: [ShareError] = [
|
|
.renderingFailed,
|
|
.mapSnapshotFailed,
|
|
.instagramNotInstalled
|
|
]
|
|
|
|
for error in errors {
|
|
#expect(error.errorDescription != nil)
|
|
#expect(!error.errorDescription!.isEmpty)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - ShareCardDimensions Tests
|
|
|
|
@Suite("ShareCardDimensions")
|
|
struct ShareCardDimensionsTests {
|
|
|
|
// MARK: - Specification Tests: Static Constants
|
|
|
|
/// - Expected Behavior: Card size is standard Instagram story size
|
|
@Test("cardSize: is 1080x1920")
|
|
func cardSize_instagramStory() {
|
|
#expect(ShareCardDimensions.cardSize.width == 1080)
|
|
#expect(ShareCardDimensions.cardSize.height == 1920)
|
|
}
|
|
|
|
/// - Expected Behavior: Map snapshot size fits within card with padding
|
|
@Test("mapSnapshotSize: has reasonable dimensions")
|
|
func mapSnapshotSize_reasonable() {
|
|
#expect(ShareCardDimensions.mapSnapshotSize.width == 960)
|
|
#expect(ShareCardDimensions.mapSnapshotSize.height == 480)
|
|
}
|
|
|
|
/// - Expected Behavior: Route map size fits within card with padding
|
|
@Test("routeMapSize: has reasonable dimensions")
|
|
func routeMapSize_reasonable() {
|
|
#expect(ShareCardDimensions.routeMapSize.width == 960)
|
|
#expect(ShareCardDimensions.routeMapSize.height == 576)
|
|
}
|
|
|
|
/// - Expected Behavior: Padding value is positive
|
|
@Test("padding: is positive")
|
|
func padding_positive() {
|
|
#expect(ShareCardDimensions.padding == 60)
|
|
#expect(ShareCardDimensions.padding > 0)
|
|
}
|
|
|
|
/// - Expected Behavior: Header height is positive
|
|
@Test("headerHeight: is positive")
|
|
func headerHeight_positive() {
|
|
#expect(ShareCardDimensions.headerHeight == 120)
|
|
#expect(ShareCardDimensions.headerHeight > 0)
|
|
}
|
|
|
|
/// - Expected Behavior: Footer height is positive
|
|
@Test("footerHeight: is positive")
|
|
func footerHeight_positive() {
|
|
#expect(ShareCardDimensions.footerHeight == 100)
|
|
#expect(ShareCardDimensions.footerHeight > 0)
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: Card aspect ratio is 9:16 (portrait)
|
|
@Test("Invariant: card is portrait aspect ratio")
|
|
func invariant_portraitAspectRatio() {
|
|
let aspectRatio = ShareCardDimensions.cardSize.width / ShareCardDimensions.cardSize.height
|
|
// 9:16 = 0.5625
|
|
#expect(abs(aspectRatio - 0.5625) < 0.001)
|
|
}
|
|
|
|
/// - Invariant: Map sizes fit within card with padding
|
|
@Test("Invariant: maps fit within card")
|
|
func invariant_mapsFitWithinCard() {
|
|
let availableWidth = ShareCardDimensions.cardSize.width - (ShareCardDimensions.padding * 2)
|
|
|
|
#expect(ShareCardDimensions.mapSnapshotSize.width <= availableWidth)
|
|
#expect(ShareCardDimensions.routeMapSize.width <= availableWidth)
|
|
}
|
|
}
|