Library detail screens now lead with the user's recorded machine settings (per-split when they disagree, empty-state card for machine-based entries) and append the weight progression chart. Starter seeds mark machine exercises with an empty machineSettings list so the settings UI lights up before first use. The figure rig gains a frontal body profile for face-on machines, props that can ride mid joints (knees/elbows), and an alternating four-frame Bird Dog loop. Claude-Session: https://claude.ai/code/session_01LEoff8bXGBS83tK1c55Mf7
79 lines
3.8 KiB
Swift
79 lines
3.8 KiB
Swift
import Foundation
|
|
import SwiftData
|
|
import Testing
|
|
@testable import Workouts
|
|
|
|
/// Exercises the exact fetches `ExerciseLibraryDetailView` builds its machine-settings
|
|
/// card and progression-chart gate from, against an in-memory store — locking the
|
|
/// predicate shapes to the cache schema.
|
|
@MainActor
|
|
struct ExerciseLibraryQueryTests {
|
|
|
|
private func makeContext() throws -> ModelContext {
|
|
let schema = Schema([Split.self, Exercise.self, Workout.self, WorkoutLog.self])
|
|
// cloudKitDatabase: .none matches WorkoutsModelContainer — see SeedLibraryTests.
|
|
let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true, cloudKitDatabase: .none)
|
|
let container = try ModelContainer(for: schema, configurations: [config])
|
|
return ModelContext(container)
|
|
}
|
|
|
|
@Test func machineSettingsFetchFindsRecordedSettings() throws {
|
|
let context = try makeContext()
|
|
let split = Split(id: "S1", name: "Upper Body", color: "indigo",
|
|
systemImage: "dumbbell.fill", order: 0,
|
|
createdAt: .now, updatedAt: .now, jsonRelativePath: "Splits/S1.json")
|
|
let exercise = Exercise(id: "E1", name: "Chest Press", order: 0, sets: 3, reps: 10,
|
|
weight: 100, loadType: LoadType.weight.rawValue,
|
|
durationTotalSeconds: 0,
|
|
machineSettings: [MachineSetting(name: "Seat Height", value: "4")])
|
|
exercise.split = split
|
|
context.insert(split)
|
|
context.insert(exercise)
|
|
try context.save()
|
|
|
|
let name = "Chest Press"
|
|
let fetched = try context.fetch(FetchDescriptor<Exercise>(
|
|
predicate: #Predicate { $0.name == name }
|
|
))
|
|
#expect(fetched.count == 1)
|
|
#expect(fetched.first?.machineSettings == [MachineSetting(name: "Seat Height", value: "4")])
|
|
#expect(fetched.first?.split?.name == "Upper Body")
|
|
}
|
|
|
|
@Test func weightedLogFetchGatesChart() throws {
|
|
let context = try makeContext()
|
|
let workout = Workout(id: "W1", splitID: nil, splitName: "Upper Body",
|
|
start: .now, end: .now, statusRaw: WorkoutStatus.completed.rawValue,
|
|
createdAt: .now, updatedAt: .now,
|
|
jsonRelativePath: "Workouts/2026/07/W1.json")
|
|
let weighted = WorkoutLog(id: "L1", exerciseName: "Chest Press", order: 0, sets: 3,
|
|
reps: 10, weight: 100, loadType: LoadType.weight.rawValue,
|
|
durationTotalSeconds: 0, currentStateIndex: 3,
|
|
statusRaw: WorkoutStatus.completed.rawValue, notes: nil, date: .now)
|
|
let bodyweight = WorkoutLog(id: "L2", exerciseName: "Plank", order: 1, sets: 3,
|
|
reps: 0, weight: 0, loadType: LoadType.duration.rawValue,
|
|
durationTotalSeconds: 60, currentStateIndex: 3,
|
|
statusRaw: WorkoutStatus.completed.rawValue, notes: nil, date: .now)
|
|
weighted.workout = workout
|
|
bodyweight.workout = workout
|
|
context.insert(workout)
|
|
context.insert(weighted)
|
|
context.insert(bodyweight)
|
|
try context.save()
|
|
|
|
let completedRaw = WorkoutStatus.completed.rawValue
|
|
|
|
let chestPress = "Chest Press"
|
|
let chestLogs = try context.fetch(FetchDescriptor<WorkoutLog>(
|
|
predicate: #Predicate { $0.exerciseName == chestPress && $0.statusRaw == completedRaw && $0.weight > 0 }
|
|
))
|
|
#expect(chestLogs.count == 1)
|
|
|
|
let plank = "Plank"
|
|
let plankLogs = try context.fetch(FetchDescriptor<WorkoutLog>(
|
|
predicate: #Predicate { $0.exerciseName == plank && $0.statusRaw == completedRaw && $0.weight > 0 }
|
|
))
|
|
#expect(plankLogs.isEmpty)
|
|
}
|
|
}
|