The UX redesign's first landing (spec in UX-REDESIGN.md): ContentView becomes a Today / Progress / Settings TabView, "Routine" replaces "Split" in every user-facing string and view name (code-level types keep their names), and workout starting moves to shared WorkoutStarter / StartedWorkoutNavigator plumbing. - New Progress tab: weekly goal streaks, workout trends, per-exercise weight progression, achievements, and the full history list (WorkoutLogsView -> WorkoutHistoryView). - Goals: stable categories workouts roll up to, managed from Settings. - New Meditation exercise + starter routine; timed sits record to Apple Health as Mind & Body sessions. Claude-Session: https://claude.ai/code/session_012qw2itfzKyEJ1HpsFt8Ex4
80 lines
3.9 KiB
Swift
80 lines
3.9 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([Routine.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()
|
|
// PINNED: routine documents live under the on-disk "Splits/" directory.
|
|
let routine = Routine(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.routine = routine
|
|
context.insert(routine)
|
|
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?.routine?.name == "Upper Body")
|
|
}
|
|
|
|
@Test func weightedLogFetchGatesChart() throws {
|
|
let context = try makeContext()
|
|
let workout = Workout(id: "W1", routineID: nil, routineName: "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)
|
|
}
|
|
}
|