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:
166
SportsTimeTests/Services/DeepLinkHandlerTests.swift
Normal file
166
SportsTimeTests/Services/DeepLinkHandlerTests.swift
Normal file
@@ -0,0 +1,166 @@
|
||||
//
|
||||
// DeepLinkHandlerTests.swift
|
||||
// SportsTimeTests
|
||||
//
|
||||
// TDD specification tests for DeepLinkHandler URL parsing.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import Foundation
|
||||
@testable import SportsTime
|
||||
|
||||
@Suite("DeepLinkHandler")
|
||||
@MainActor
|
||||
struct DeepLinkHandlerTests {
|
||||
|
||||
private let handler = DeepLinkHandler.shared
|
||||
|
||||
// MARK: - Specification Tests: parseURL
|
||||
|
||||
/// - Expected Behavior: Valid poll URL returns .poll with share code
|
||||
@Test("parseURL: sportstime://poll/{code} returns poll destination")
|
||||
func parseURL_validPollURL() {
|
||||
let url = URL(string: "sportstime://poll/ABC123")!
|
||||
let result = handler.parseURL(url)
|
||||
|
||||
if case .poll(let shareCode) = result {
|
||||
#expect(shareCode == "ABC123")
|
||||
} else {
|
||||
Issue.record("Expected .poll destination")
|
||||
}
|
||||
}
|
||||
|
||||
/// - Expected Behavior: Poll URL normalizes code to uppercase
|
||||
@Test("parseURL: normalizes share code to uppercase")
|
||||
func parseURL_normalizesToUppercase() {
|
||||
let url = URL(string: "sportstime://poll/abc123")!
|
||||
let result = handler.parseURL(url)
|
||||
|
||||
if case .poll(let shareCode) = result {
|
||||
#expect(shareCode == "ABC123")
|
||||
} else {
|
||||
Issue.record("Expected .poll destination")
|
||||
}
|
||||
}
|
||||
|
||||
/// - Expected Behavior: Invalid scheme returns nil
|
||||
@Test("parseURL: wrong scheme returns nil")
|
||||
func parseURL_wrongScheme() {
|
||||
let httpURL = URL(string: "http://poll/ABC123")!
|
||||
#expect(handler.parseURL(httpURL) == nil)
|
||||
|
||||
let httpsURL = URL(string: "https://poll/ABC123")!
|
||||
#expect(handler.parseURL(httpsURL) == nil)
|
||||
}
|
||||
|
||||
/// - Expected Behavior: Empty path returns nil
|
||||
@Test("parseURL: empty path returns nil")
|
||||
func parseURL_emptyPath() {
|
||||
let url = URL(string: "sportstime://")!
|
||||
#expect(handler.parseURL(url) == nil)
|
||||
}
|
||||
|
||||
/// - Expected Behavior: Unknown path returns nil
|
||||
@Test("parseURL: unknown path returns nil")
|
||||
func parseURL_unknownPath() {
|
||||
let url = URL(string: "sportstime://unknown/ABC123")!
|
||||
#expect(handler.parseURL(url) == nil)
|
||||
}
|
||||
|
||||
/// - Expected Behavior: Poll path without code returns nil
|
||||
@Test("parseURL: poll path without code returns nil")
|
||||
func parseURL_pollWithoutCode() {
|
||||
let url = URL(string: "sportstime://poll")!
|
||||
#expect(handler.parseURL(url) == nil)
|
||||
|
||||
let urlWithSlash = URL(string: "sportstime://poll/")!
|
||||
#expect(handler.parseURL(urlWithSlash) == nil)
|
||||
}
|
||||
|
||||
// MARK: - Specification Tests: Share Code Validation
|
||||
|
||||
/// - Expected Behavior: Share code must be exactly 6 characters
|
||||
@Test("parseURL: validates share code length")
|
||||
func parseURL_validateCodeLength() {
|
||||
// Too short
|
||||
let shortURL = URL(string: "sportstime://poll/ABC12")!
|
||||
#expect(handler.parseURL(shortURL) == nil)
|
||||
|
||||
// Too long
|
||||
let longURL = URL(string: "sportstime://poll/ABC1234")!
|
||||
#expect(handler.parseURL(longURL) == nil)
|
||||
|
||||
// Exactly 6 - valid
|
||||
let validURL = URL(string: "sportstime://poll/ABC123")!
|
||||
#expect(handler.parseURL(validURL) != nil)
|
||||
}
|
||||
|
||||
// MARK: - Invariant Tests
|
||||
|
||||
/// - Invariant: Only sportstime:// scheme is valid
|
||||
@Test("Invariant: scheme must be sportstime")
|
||||
func invariant_schemeMustBeSportstime() {
|
||||
let validURL = URL(string: "sportstime://poll/ABC123")!
|
||||
#expect(handler.parseURL(validURL) != nil)
|
||||
|
||||
// Various invalid schemes
|
||||
let schemes = ["http", "https", "file", "ftp", "app"]
|
||||
for scheme in schemes {
|
||||
let url = URL(string: "\(scheme)://poll/ABC123")!
|
||||
#expect(handler.parseURL(url) == nil, "Scheme '\(scheme)' should be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
/// - Invariant: Path must start with recognized destination type
|
||||
@Test("Invariant: path must be recognized destination")
|
||||
func invariant_pathMustBeRecognized() {
|
||||
// Only 'poll' is recognized
|
||||
let pollURL = URL(string: "sportstime://poll/ABC123")!
|
||||
#expect(handler.parseURL(pollURL) != nil)
|
||||
|
||||
// Other paths should fail
|
||||
let otherPaths = ["trip", "game", "stadium", "user", "settings"]
|
||||
for path in otherPaths {
|
||||
let url = URL(string: "sportstime://\(path)/ABC123")!
|
||||
#expect(handler.parseURL(url) == nil, "Path '\(path)' should not be recognized")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - DeepLinkDestination Tests
|
||||
|
||||
@Suite("DeepLinkDestination")
|
||||
@MainActor
|
||||
struct DeepLinkDestinationTests {
|
||||
|
||||
// MARK: - Specification Tests: Equatable
|
||||
|
||||
@Test("Equatable: poll destinations with same code are equal")
|
||||
func equatable_sameCode() {
|
||||
let dest1 = DeepLinkDestination.poll(shareCode: "ABC123")
|
||||
let dest2 = DeepLinkDestination.poll(shareCode: "ABC123")
|
||||
|
||||
#expect(dest1 == dest2)
|
||||
}
|
||||
|
||||
@Test("Equatable: poll destinations with different codes are not equal")
|
||||
func equatable_differentCodes() {
|
||||
let dest1 = DeepLinkDestination.poll(shareCode: "ABC123")
|
||||
let dest2 = DeepLinkDestination.poll(shareCode: "XYZ789")
|
||||
|
||||
#expect(dest1 != dest2)
|
||||
}
|
||||
|
||||
// MARK: - Specification Tests: Properties
|
||||
|
||||
@Test("poll: shareCode returns associated value")
|
||||
func poll_shareCode() {
|
||||
let dest = DeepLinkDestination.poll(shareCode: "TEST99")
|
||||
|
||||
if case .poll(let code) = dest {
|
||||
#expect(code == "TEST99")
|
||||
} else {
|
||||
Issue.record("Expected poll case")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user