Exercise Library/ holds per-exercise reference docs (setup, cues, mistakes, progressions) with SVG visuals and a Python-rendered motion pipeline; Workouts/ExerciseFigure renders the bundled *.motion.json rigs as animated stick figures on the exercise screen. Exercises gain a warm-up/main-circuit category, timed exercises display hold time via planSummary, and a completed exercise reopens to a check screen instead of its timers.
77 lines
2.7 KiB
Swift
77 lines
2.7 KiB
Swift
import Foundation
|
|
import Testing
|
|
import IndieSync
|
|
@testable import Workouts
|
|
|
|
/// Round-trips `SplitDocument` through `DocumentCoder` — the exact JSON codec
|
|
/// (ISO-8601 dates, sorted keys, pretty-printed) `DocumentFileStore` uses to
|
|
/// write and read every document in this app's iCloud container.
|
|
struct SplitDocumentCodableTests {
|
|
/// A whole-second epoch so ISO-8601 round-trips (no fractional-second loss).
|
|
private static let stableTimestamp = Date(timeIntervalSince1970: 1_700_000_000)
|
|
|
|
@Test func encodeDecodeRoundTrip() throws {
|
|
let exercise = ExerciseDocument(
|
|
id: ULID.make(),
|
|
name: "Bench Press",
|
|
order: 0,
|
|
sets: 4,
|
|
reps: 10,
|
|
weight: 135,
|
|
loadType: 0,
|
|
durationSeconds: 0,
|
|
weightLastUpdated: Self.stableTimestamp,
|
|
weightReminderWeeks: 4,
|
|
category: ExerciseCategory.warmup.rawValue
|
|
)
|
|
let original = SplitDocument(
|
|
schemaVersion: SplitDocument.currentSchemaVersion,
|
|
id: ULID.make(),
|
|
name: "Push Day",
|
|
color: "blue",
|
|
systemImage: "dumbbell.fill",
|
|
order: 0,
|
|
createdAt: Self.stableTimestamp,
|
|
updatedAt: Self.stableTimestamp,
|
|
exercises: [exercise],
|
|
activityType: nil
|
|
)
|
|
|
|
let data = try DocumentCoder.encode(original)
|
|
let decoded = try DocumentCoder.decode(SplitDocument.self, from: data)
|
|
|
|
#expect(decoded == original)
|
|
#expect(decoded.relativePath == "Splits/\(original.id).json")
|
|
}
|
|
|
|
/// A pre-category exercise (no `category` key) must still decode — the field is
|
|
/// optional and deliberately not schema-bumped, defaulting to the main circuit.
|
|
@Test func decodesLegacyExerciseWithoutCategory() throws {
|
|
let json = """
|
|
{
|
|
"schemaVersion": 1,
|
|
"id": "01HZZZZZZZZZZZZZZZZZZZZZZZ",
|
|
"name": "Push Day",
|
|
"color": "blue",
|
|
"systemImage": "dumbbell.fill",
|
|
"order": 0,
|
|
"createdAt": "2023-11-14T22:13:20Z",
|
|
"updatedAt": "2023-11-14T22:13:20Z",
|
|
"exercises": [{
|
|
"id": "01HZZZZZZZZZZZZZZZZZZZZZZY",
|
|
"name": "Bench Press",
|
|
"order": 0,
|
|
"sets": 4,
|
|
"reps": 10,
|
|
"weight": 135,
|
|
"loadType": 1,
|
|
"durationSeconds": 0,
|
|
"weightReminderWeeks": 4
|
|
}]
|
|
}
|
|
"""
|
|
let decoded = try DocumentCoder.decode(SplitDocument.self, from: Data(json.utf8))
|
|
#expect(decoded.exercises.first?.category == nil)
|
|
}
|
|
}
|