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>
211 lines
7.1 KiB
Swift
211 lines
7.1 KiB
Swift
//
|
|
// EVChargingServiceTests.swift
|
|
// SportsTimeTests
|
|
//
|
|
// TDD specification tests for EVChargingService types.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import SportsTime
|
|
|
|
// MARK: - ChargerType Detection Tests
|
|
|
|
@Suite("ChargerType Detection")
|
|
struct ChargerTypeDetectionTests {
|
|
|
|
// MARK: - Test Helper
|
|
|
|
// Note: detectChargerType and estimateChargeTime are private in EVChargingService
|
|
// These tests document expected behavior for the ChargerType enum
|
|
|
|
// MARK: - Specification Tests: ChargerType Cases
|
|
|
|
/// - Expected Behavior: ChargerType has three cases
|
|
@Test("ChargerType: has supercharger case")
|
|
func chargerType_supercharger() {
|
|
let type = ChargerType.supercharger
|
|
#expect(type == .supercharger)
|
|
}
|
|
|
|
@Test("ChargerType: has dcFast case")
|
|
func chargerType_dcFast() {
|
|
let type = ChargerType.dcFast
|
|
#expect(type == .dcFast)
|
|
}
|
|
|
|
@Test("ChargerType: has level2 case")
|
|
func chargerType_level2() {
|
|
let type = ChargerType.level2
|
|
#expect(type == .level2)
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: All charger types are distinct
|
|
@Test("Invariant: all charger types are distinct")
|
|
func invariant_distinctTypes() {
|
|
let types: [ChargerType] = [.supercharger, .dcFast, .level2]
|
|
let uniqueTypes = Set(types)
|
|
#expect(types.count == uniqueTypes.count)
|
|
}
|
|
}
|
|
|
|
// MARK: - EVChargingStop Tests
|
|
|
|
@Suite("EVChargingStop")
|
|
struct EVChargingStopTests {
|
|
|
|
// MARK: - Test Data
|
|
|
|
private func makeStop(
|
|
name: String = "Tesla Supercharger",
|
|
chargerType: ChargerType = .supercharger,
|
|
estimatedChargeTime: TimeInterval = 25 * 60
|
|
) -> EVChargingStop {
|
|
EVChargingStop(
|
|
name: name,
|
|
location: LocationInput(name: name, coordinate: nil, address: nil),
|
|
chargerType: chargerType,
|
|
estimatedChargeTime: estimatedChargeTime
|
|
)
|
|
}
|
|
|
|
// MARK: - Specification Tests: Properties
|
|
|
|
@Test("EVChargingStop: stores name")
|
|
func evChargingStop_name() {
|
|
let stop = makeStop(name: "Test Charger")
|
|
#expect(stop.name == "Test Charger")
|
|
}
|
|
|
|
@Test("EVChargingStop: stores chargerType")
|
|
func evChargingStop_chargerType() {
|
|
let stop = makeStop(chargerType: .dcFast)
|
|
#expect(stop.chargerType == .dcFast)
|
|
}
|
|
|
|
@Test("EVChargingStop: stores estimatedChargeTime")
|
|
func evChargingStop_estimatedChargeTime() {
|
|
let stop = makeStop(estimatedChargeTime: 30 * 60)
|
|
#expect(stop.estimatedChargeTime == 30 * 60)
|
|
}
|
|
|
|
@Test("EVChargingStop: stores location")
|
|
func evChargingStop_location() {
|
|
let stop = makeStop(name: "Test Location")
|
|
#expect(stop.location.name == "Test Location")
|
|
}
|
|
|
|
// MARK: - Specification Tests: Expected Charge Times
|
|
|
|
/// - Expected Behavior: Supercharger takes ~25 minutes
|
|
@Test("Expected charge time: supercharger ~25 minutes")
|
|
func expectedChargeTime_supercharger() {
|
|
// Based on EVChargingService.estimateChargeTime implementation
|
|
let expectedTime: TimeInterval = 25 * 60
|
|
let stop = makeStop(chargerType: .supercharger, estimatedChargeTime: expectedTime)
|
|
#expect(stop.estimatedChargeTime == 25 * 60)
|
|
}
|
|
|
|
/// - Expected Behavior: DC Fast takes ~30 minutes
|
|
@Test("Expected charge time: dcFast ~30 minutes")
|
|
func expectedChargeTime_dcFast() {
|
|
let expectedTime: TimeInterval = 30 * 60
|
|
let stop = makeStop(chargerType: .dcFast, estimatedChargeTime: expectedTime)
|
|
#expect(stop.estimatedChargeTime == 30 * 60)
|
|
}
|
|
|
|
/// - Expected Behavior: Level 2 takes ~2 hours
|
|
@Test("Expected charge time: level2 ~2 hours")
|
|
func expectedChargeTime_level2() {
|
|
let expectedTime: TimeInterval = 120 * 60
|
|
let stop = makeStop(chargerType: .level2, estimatedChargeTime: expectedTime)
|
|
#expect(stop.estimatedChargeTime == 120 * 60)
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: Each stop has unique id
|
|
@Test("Invariant: each stop has unique id")
|
|
func invariant_uniqueId() {
|
|
let stop1 = makeStop()
|
|
let stop2 = makeStop()
|
|
#expect(stop1.id != stop2.id)
|
|
}
|
|
}
|
|
|
|
// MARK: - Charger Detection Behavior Tests
|
|
|
|
@Suite("Charger Detection Behavior")
|
|
struct ChargerDetectionBehaviorTests {
|
|
|
|
// These tests document the expected name → charger type mapping
|
|
// based on the detectChargerType implementation
|
|
|
|
// MARK: - Specification Tests: Name Detection Rules
|
|
|
|
/// - Expected Behavior: "Tesla" or "Supercharger" → .supercharger
|
|
@Test("Detection rule: Tesla names should map to supercharger")
|
|
func detectionRule_tesla() {
|
|
// Expected names that should return .supercharger:
|
|
// - "Tesla Supercharger"
|
|
// - "Supercharger"
|
|
// - "Tesla Station"
|
|
let teslaNames = ["Tesla Supercharger", "Supercharger", "Tesla Station"]
|
|
for name in teslaNames {
|
|
let lowercased = name.lowercased()
|
|
let isTesla = lowercased.contains("tesla") || lowercased.contains("supercharger")
|
|
#expect(isTesla, "'\(name)' should be detected as Tesla/Supercharger")
|
|
}
|
|
}
|
|
|
|
/// - Expected Behavior: DC Fast keywords → .dcFast
|
|
@Test("Detection rule: DC Fast keywords should map to dcFast")
|
|
func detectionRule_dcFast() {
|
|
// Expected names that should return .dcFast:
|
|
// - "DC Fast Charging"
|
|
// - "DCFC Station"
|
|
// - "CCS Charger"
|
|
// - "CHAdeMO"
|
|
// - "Electrify America"
|
|
// - "EVgo"
|
|
let dcFastKeywords = ["dc fast", "dcfc", "ccs", "chademo", "electrify america", "evgo"]
|
|
let testNames = ["DC Fast Charging", "DCFC Station", "CCS Charger", "CHAdeMO", "Electrify America", "EVgo Station"]
|
|
|
|
for name in testNames {
|
|
let lowercased = name.lowercased()
|
|
let isDCFast = dcFastKeywords.contains { lowercased.contains($0) }
|
|
#expect(isDCFast, "'\(name)' should be detected as DC Fast")
|
|
}
|
|
}
|
|
|
|
/// - Expected Behavior: Other names → .level2 (default)
|
|
@Test("Detection rule: Unknown names default to level2")
|
|
func detectionRule_level2Default() {
|
|
// Names without specific keywords should default to Level 2
|
|
let genericNames = ["ChargePoint", "Blink", "Generic EV Charger", "Unknown Station"]
|
|
|
|
for name in genericNames {
|
|
let lowercased = name.lowercased()
|
|
let isTesla = lowercased.contains("tesla") || lowercased.contains("supercharger")
|
|
let isDCFast = ["dc fast", "dcfc", "ccs", "chademo", "electrify america", "evgo"]
|
|
.contains { lowercased.contains($0) }
|
|
|
|
#expect(!isTesla && !isDCFast, "'\(name)' should not match Tesla or DC Fast")
|
|
}
|
|
}
|
|
|
|
// MARK: - Invariant Tests
|
|
|
|
/// - Invariant: Detection is case-insensitive
|
|
@Test("Invariant: detection is case-insensitive")
|
|
func invariant_caseInsensitive() {
|
|
let variations = ["TESLA", "Tesla", "tesla", "TeSLa"]
|
|
for name in variations {
|
|
let isTesla = name.lowercased().contains("tesla")
|
|
#expect(isTesla, "'\(name)' should match 'tesla' case-insensitively")
|
|
}
|
|
}
|
|
}
|