import Foundation import Testing @testable import Workouts_Watch_App /// Locks the small pure helpers the watch UI leans on: the run-flow duration label, the /// weight-unit formatter, the workout-volume roll-up the watch attaches to finished sessions, /// and the heart-rate zone bucketing the live session folds sensor time into. All pure — no /// SwiftUI host, no `HKWorkoutSession`. struct WatchHelpersTests { // MARK: - ExerciseProgressView.durationLabel (run-flow "m/s" footer) @Test func durationLabelFormatsMinutesAndSeconds() { #expect(ExerciseProgressView.durationLabel(90) == "1m 30s") #expect(ExerciseProgressView.durationLabel(605) == "10m 5s") } @Test func durationLabelDropsWholeSecondsAndWholeMinutes() { #expect(ExerciseProgressView.durationLabel(60) == "1 min") #expect(ExerciseProgressView.durationLabel(120) == "2 min") #expect(ExerciseProgressView.durationLabel(45) == "45 sec") #expect(ExerciseProgressView.durationLabel(0) == "0 sec") } // MARK: - WeightUnit.format (watch row / picker subtitle) @Test func weightUnitFormatsWithAbbreviation() { #expect(WeightUnit.lb.format(135) == "135 lb") #expect(WeightUnit.kg.format(60) == "60 kg") #expect(WeightUnit.lb.format(0) == "0 lb") } // MARK: - WorkoutVolume.total (metric the watch fills on finish) private static let ts = Date(timeIntervalSince1970: 1_700_000_000) private func log(id: String, sets: Int, reps: Int, weight: Double, loadType: LoadType) -> WorkoutLogDocument { WorkoutLogDocument( id: id, exerciseName: "Ex-\(id)", order: 0, sets: sets, reps: reps, weight: weight, loadType: loadType.rawValue, durationSeconds: 0, currentStateIndex: 0, status: WorkoutStatus.completed.rawValue, notes: nil, date: Self.ts ) } @Test func workoutVolumeSumsSetsRepsWeightAcrossWeightedLogs() { let logs = [ log(id: "A", sets: 4, reps: 10, weight: 135, loadType: .weight), // 5400 log(id: "B", sets: 3, reps: 8, weight: 100, loadType: .weight), // 2400 ] #expect(WorkoutVolume.total(logs) == 7800) } @Test func workoutVolumeExcludesNonWeightedLogs() { let logs = [ log(id: "A", sets: 4, reps: 10, weight: 135, loadType: .weight), // 5400 log(id: "B", sets: 3, reps: 30, weight: 999, loadType: .duration), // excluded (timed) log(id: "C", sets: 2, reps: 5, weight: 50, loadType: .none), // excluded (no load) ] #expect(WorkoutVolume.total(logs) == 5400) } @Test func workoutVolumeOfNoLogsIsZero() { #expect(WorkoutVolume.total([]) == 0) } // MARK: - WorkoutSessionManager.zoneIndex (HR zone bucketing) @Test func zoneIndexBucketsByFractionOfMaxHeartRate() { let maxHR = 200.0 // thresholds land on 120 / 140 / 160 / 180 bpm #expect(WorkoutSessionManager.zoneIndex(for: 100, maxHR: maxHR) == 0) // 50% → zone 0 #expect(WorkoutSessionManager.zoneIndex(for: 119, maxHR: maxHR) == 0) // <60% → zone 0 #expect(WorkoutSessionManager.zoneIndex(for: 120, maxHR: maxHR) == 1) // 60% → zone 1 #expect(WorkoutSessionManager.zoneIndex(for: 140, maxHR: maxHR) == 2) // 70% → zone 2 #expect(WorkoutSessionManager.zoneIndex(for: 160, maxHR: maxHR) == 3) // 80% → zone 3 #expect(WorkoutSessionManager.zoneIndex(for: 180, maxHR: maxHR) == 4) // 90% → zone 4 #expect(WorkoutSessionManager.zoneIndex(for: 220, maxHR: maxHR) == 4) // over max → still 4 } }