import Foundation import SwiftData import Testing @testable import Workouts_Watch_App /// Exercises the real `WatchConnectivityBridge` — constructed against an in-memory cache with no /// live `WCSession` (its `init` never touches WatchConnectivity; only `activate()` does, which we /// don't call). This pins the two things reachable without a session: /// • the optimistic local update: `update(workout:)` writes straight to the read-through cache /// (the forward-to-phone step no-ops with no session), so the UI sees the edit immediately; and /// • the fresh-bridge state contract (no sync date, no edit lock, no follower frame). /// /// It deliberately does not exercise updatedAt/version ordering on the apply path. @MainActor struct WatchConnectivityBridgeTests { private static let ts = Date(timeIntervalSince1970: 1_700_000_000) private func makeContainer() throws -> ModelContainer { let schema = Schema([Routine.self, Exercise.self, Workout.self, WorkoutLog.self]) let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true, cloudKitDatabase: .none) return try ModelContainer(for: schema, configurations: [config]) } private func workout(id: String, name: String, routineName: String) -> WorkoutDocument { WorkoutDocument( schemaVersion: WorkoutDocument.currentSchemaVersion, id: id, routineID: "S", routineName: routineName, start: Self.ts, end: nil, status: WorkoutStatus.inProgress.rawValue, createdAt: Self.ts, updatedAt: Self.ts, logs: [WorkoutLogDocument(id: "L-\(id)", exerciseName: name, order: 0, sets: 4, reps: 10, weight: 135, loadType: LoadType.weight.rawValue, durationSeconds: 0, currentStateIndex: 0, status: WorkoutStatus.notStarted.rawValue, notes: nil, date: Self.ts)], metrics: nil ) } @Test func updateWritesWorkoutOptimisticallyIntoTheCache() throws { let container = try makeContainer() let bridge = WatchConnectivityBridge(container: container) let doc = workout(id: "01WKOPT", name: "Bench Press", routineName: "Push") #expect(CacheMapper.fetchWorkout(id: doc.id, in: container.mainContext) == nil) bridge.update(workout: doc) let fetched = try #require(CacheMapper.fetchWorkout(id: doc.id, in: container.mainContext)) #expect(fetched.id == "01WKOPT") #expect(fetched.routineName == "Push") #expect(fetched.logsArray.first?.exerciseName == "Bench Press") } @Test func updateAppliesAnEditInPlaceOnASecondCall() throws { let container = try makeContainer() let bridge = WatchConnectivityBridge(container: container) bridge.update(workout: workout(id: "01WKEDIT", name: "Bench Press", routineName: "Push")) var edited = workout(id: "01WKEDIT", name: "Incline Press", routineName: "Push Day") edited.status = WorkoutStatus.completed.rawValue bridge.update(workout: edited) let all = try container.mainContext.fetch(FetchDescriptor()) #expect(all.count == 1) // updated in place, not duplicated let fetched = try #require(CacheMapper.fetchWorkout(id: "01WKEDIT", in: container.mainContext)) #expect(fetched.routineName == "Push Day") #expect(fetched.status == .completed) #expect(fetched.logsArray.first?.exerciseName == "Incline Press") } @Test func freshBridgeHasNoSyncDateEditLockOrFollowerFrame() throws { let bridge = try WatchConnectivityBridge(container: makeContainer()) #expect(bridge.lastSyncDate == nil) #expect(bridge.editingWorkoutID == nil) #expect(bridge.editingRoutineID == nil) #expect(bridge.liveIncoming == nil) #expect(bridge.presentable == nil) #expect(bridge.navigatedRunID == nil) } /// `muteLive()` with no incoming frame is a harmless no-op — `presentable` stays nil rather /// than trapping on the optional it reads. @Test func muteLiveWithoutIncomingFrameIsHarmless() throws { let bridge = try WatchConnectivityBridge(container: makeContainer()) bridge.muteLive() #expect(bridge.presentable == nil) } }