Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions Sources/BetterFit/Models/Recovery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ public struct BodyMapRecovery: Codable, Equatable {
self.lastUpdated = lastUpdated
}

/// Update recovery status after a workout
/// Update each targeted body region once per recorded workout.
public mutating func recordWorkout(_ workout: Workout) {
let muscleGroups = workout.exercises.flatMap { $0.exercise.muscleGroups }
let workedRegions = Set(workout.exercises.flatMap { workoutExercise in
workoutExercise.exercise.muscleGroups.map { group in
BodyRegion(rawValue: group.bodyMapRegion) ?? .other
}
})

for group in muscleGroups {
let region = BodyRegion(rawValue: group.bodyMapRegion) ?? .other
for region in workedRegions {
let currentStatus = regions[region] ?? .recovered

// Mark as worked
regions[region] = currentStatus.afterWorkout()
}

Expand Down
2 changes: 2 additions & 0 deletions Tests/BetterFitTests/IntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ final class IntegrationTests: XCTestCase {

let streak = betterFit.socialManager.getCurrentStreak()
XCTAssertEqual(streak, 1)

XCTAssertEqual(betterFit.bodyMapManager.getRecoveryStatus(for: .legs), .fatigued)
}

func testTemplateToWorkoutIntegration() {
Expand Down
5 changes: 2 additions & 3 deletions Tests/BetterFitTests/RecoveryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ final class RecoveryTests: XCTestCase {
manager.recordWorkout(workout)

let status = manager.getRecoveryStatus(for: .legs)
// Squat works both quads and glutes, which both map to legs region
// So it gets hit twice: recovered -> fatigued -> sore
XCTAssertEqual(status, .sore)
// A compound exercise updates its body region once per workout.
XCTAssertEqual(status, .fatigued)
}
}
80 changes: 80 additions & 0 deletions Tests/BetterFitTests/WorkoutRegionRecoveryTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Foundation
import Testing

import BetterFit

struct WorkoutRegionRecoveryTests {

// MARK: - Recording

@Test(arguments: [
[.quads, .glutes],
[.quads, .quads],
[.hamstrings, .glutes, .calves]
] as [[MuscleGroup]])
func compoundExerciseFatiguesRegionOnce(_ muscleGroups: [MuscleGroup]) {
var recovery = BodyMapRecovery()

recovery.recordWorkout(workout([muscleGroups]))

#expect(recovery.regions == [.legs: .fatigued])
}

@Test
func multipleExercisesFatigueEachTargetedRegionOnce() {
var recovery = BodyMapRecovery(regions: [.chest: .slightlyFatigued])

recovery.recordWorkout(workout([
[.quads, .glutes], [.hamstrings, .calves],
[.biceps, .triceps], [.forearms], [.back, .lats]
]))

#expect(recovery.regions == [
.legs: .fatigued, .arms: .fatigued, .back: .fatigued,
.chest: .slightlyFatigued
])
}

@Test
func separateWorkoutsStillEscalateFatigue() {
var recovery = BodyMapRecovery()

recovery.recordWorkout(workout([[.quads, .glutes]]))
#expect(recovery.regions[.legs] == .fatigued)

recovery.recordWorkout(workout([[.hamstrings, .calves]]))
#expect(recovery.regions[.legs] == .sore)
}

@Test(arguments: [RecoveryStatus.slightlyFatigued, .fatigued, .sore])
func existingFatigueStillEscalates(_ status: RecoveryStatus) {
var recovery = BodyMapRecovery(regions: [.arms: status])

recovery.recordWorkout(workout([[.biceps, .triceps], [.forearms]]))

#expect(recovery.regions == [.arms: .sore])
}

@Test(arguments: [[], [[]]] as [[[MuscleGroup]]])
func workoutsWithoutTargetMusclesLeaveRegionsUnchanged(_ groups: [[MuscleGroup]]) {
let regions: [BodyRegion: RecoveryStatus] = [.chest: .fatigued, .legs: .sore]
var recovery = BodyMapRecovery(regions: regions)

recovery.recordWorkout(workout(groups))

#expect(recovery.regions == regions)
}

// MARK: - Fixtures

private func workout(_ groups: [[MuscleGroup]]) -> Workout {
Workout(
name: "Training session",
exercises: groups.map { muscles in
WorkoutExercise(exercise: Exercise(
name: "Exercise", equipmentRequired: .bodyweight, muscleGroups: muscles
))
}
)
}
}
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ public struct BodyMapRecovery: Codable, Equatable
- `lastUpdated: Date`

#### Methods
- `recordWorkout(_:)` - Update recovery after workout
- `recordWorkout(_:)` - Update each targeted body region once per recorded workout. Multiple muscles or exercises in the same region share one fatigue transition; separate recorded workouts can increase fatigue further.
- `updateRecovery()` - Update based on time elapsed

### `RecoveryStatus`
Expand Down
Loading