Phase 3 core (verifiable): WorkoutAnalyticsAdapter (app data → DaySample), AnalyticsCoordinator (idempotent backfill of missing weekly/monthly reports + retention prune, injectable providers), AnalyticsDeepLink (wenza://analytics report routing). Fixed a reconcile bug caught by tests: a retention-boundary week was generated then immediately pruned, regenerating every run — aligned completedWeekStartKeys to the prune cutoff → idempotent. Tests: AnalyticsAdapterTests + AnalyticsCoordinatorTests. Verified standalone via swiftc (~90 analytics assertions total across engine/store/adapter/coordinator). Remaining (app wiring, notifications, UI) requires an Xcode build — logged in ISSUES.md as device-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
62 lines
2.8 KiB
Swift
62 lines
2.8 KiB
Swift
import XCTest
|
|
@testable import KisaniCal
|
|
|
|
/// Adapter tests: exercise reduction (identity + volume), inclusive day-key
|
|
/// ranges, and app-data → DaySample mapping (rest/missing/scheduled handling).
|
|
final class AnalyticsAdapterTests: XCTestCase {
|
|
|
|
private func cal() -> Calendar {
|
|
var c = Calendar(identifier: .gregorian)
|
|
c.timeZone = TimeZone(identifier: "UTC")!
|
|
c.locale = Locale(identifier: "en_US_POSIX")
|
|
return c
|
|
}
|
|
|
|
func testExerciseSampleReduction() {
|
|
let es = WorkoutAnalyticsAdapter.exerciseSample(name: " Bench Press ",
|
|
completedSets: [(60, 10), (60, 8), (70, 6)])
|
|
XCTAssertEqual(es.identity, "bench press")
|
|
XCTAssertEqual(es.sets, 3)
|
|
XCTAssertEqual(es.reps, 24)
|
|
XCTAssertEqual(es.volume, 1500) // 600 + 480 + 420
|
|
}
|
|
|
|
func testInclusiveDayKeys() {
|
|
let c = cal()
|
|
let start = Date(timeIntervalSince1970: 1_783_500_000) // 2026-07-08
|
|
let keys = WorkoutAnalyticsAdapter.dayKeys(from: start, to: c.date(byAdding: .day, value: 6, to: start)!, calendar: c)
|
|
XCTAssertEqual(keys.count, 7)
|
|
XCTAssertEqual(keys, keys.sorted())
|
|
}
|
|
|
|
func testDaySampleMapping() {
|
|
let c = cal()
|
|
let start = Date(timeIntervalSince1970: 1_783_500_000)
|
|
let keys = WorkoutAnalyticsAdapter.dayKeys(from: start, to: c.date(byAdding: .day, value: 6, to: start)!, calendar: c)
|
|
let es = WorkoutAnalyticsAdapter.exerciseSample(name: "Squat", completedSets: [(100, 5), (100, 5)])
|
|
let raw = RawWorkoutDay(dateKey: keys[0], didWorkout: true, sessions: 1, durationMinutes: 45, exercises: [es])
|
|
let samples = WorkoutAnalyticsAdapter.daySamples(
|
|
dayKeys: keys,
|
|
workouts: [keys[0]: raw],
|
|
scheduledKeys: [keys[0], keys[2]],
|
|
health: [keys[0]: RawHealthDay(steps: 8000, activeCalories: 500, restingHeartRate: 55)])
|
|
|
|
XCTAssertEqual(samples.count, 7)
|
|
XCTAssertTrue(samples[0].didWorkout)
|
|
XCTAssertEqual(samples[0].volume, 1000)
|
|
XCTAssertTrue(samples[0].scheduled)
|
|
XCTAssertEqual(samples[0].steps, 8000)
|
|
XCTAssertFalse(samples[1].didWorkout) // rest / missing
|
|
XCTAssertEqual(samples[1].volume, 0)
|
|
XCTAssertNil(samples[1].steps) // honest: no fabricated data
|
|
XCTAssertTrue(samples[2].scheduled && !samples[2].didWorkout) // scheduled but missed
|
|
}
|
|
|
|
func testWeekAndMonthDayKeys() {
|
|
let c = cal()
|
|
let d = Date(timeIntervalSince1970: 1_783_500_000)
|
|
XCTAssertEqual(WorkoutAnalyticsAdapter.weekDayKeys(containing: d, calendar: c).count, 7)
|
|
XCTAssertEqual(WorkoutAnalyticsAdapter.monthDayKeys(containing: d, calendar: c).count, 31) // July
|
|
}
|
|
}
|