use std::fs;
use std::path::PathBuf;
use syntax_workout_core::{Visit, Workout};
use syntax_workout_core::stats::{
self, compute_tree_stats, SetCounter, VolumeCalculator, RepCounter, ExerciseVolumeCalculator,
};
fn workspace_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
fn load_workout(path: &str) -> Workout {
let full_path = workspace_root().join(path);
let content = fs::read_to_string(&full_path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", full_path.display()));
serde_json::from_str(&content)
.unwrap_or_else(|e| panic!("failed to parse {}: {e}", full_path.display()))
}
#[test]
fn estimate_1rm_known_values() {
let result = stats::estimate_1rm(80.0, 5);
assert!((result - 93.333).abs() < 0.01);
assert_eq!(stats::estimate_1rm(100.0, 1), 100.0);
}
#[test]
fn straight_sets_stats() {
let w = load_workout("examples/strength/straight-sets.json");
let stats = compute_tree_stats(&w.root);
assert_eq!(stats.total_sets, 3);
assert_eq!(stats.total_reps, 14);
assert!((stats.total_volume_kg - 1120.0).abs() < 0.01);
assert_eq!(stats.exercises.len(), 1);
let bench = &stats.exercises[0];
assert_eq!(bench.name, "Bench Press");
assert_eq!(bench.sets, 3);
assert_eq!(bench.reps, 14);
assert!((bench.volume_kg - 1120.0).abs() < 0.01);
assert!(bench.estimated_1rm.is_some());
assert!((bench.estimated_1rm.unwrap() - 93.333).abs() < 0.01);
}
#[test]
fn supersets_counts_parallel_blocks() {
let w = load_workout("examples/strength/supersets.json");
let stats = compute_tree_stats(&w.root);
assert_eq!(stats.total_sets, 4);
assert_eq!(stats.total_reps, 44); assert!((stats.total_volume_kg - 1200.0).abs() < 0.01);
assert_eq!(stats.exercises.len(), 2);
assert_eq!(stats.exercises[0].name, "DB Row");
assert_eq!(stats.exercises[0].sets, 2);
assert_eq!(stats.exercises[1].name, "Incline Press");
assert_eq!(stats.exercises[1].sets, 2);
}
#[test]
fn endurance_has_zero_volume() {
let w = load_workout("examples/endurance/easy-run.json");
let stats = compute_tree_stats(&w.root);
assert_eq!(stats.total_sets, 1);
assert_eq!(stats.total_reps, 0);
assert!((stats.total_volume_kg).abs() < 0.01);
}
#[test]
fn combined_matches_individual_visitors() {
let w = load_workout("examples/strength/straight-sets.json");
let mut set_counter = SetCounter(0);
set_counter.visit_tree(&w.root);
let mut vol_calc = VolumeCalculator(0.0);
vol_calc.visit_tree(&w.root);
let mut rep_counter = RepCounter(0);
rep_counter.visit_tree(&w.root);
let mut ex_vol = ExerciseVolumeCalculator {
exercise_name: "Bench Press".to_string(),
volume: 0.0,
};
ex_vol.visit_tree(&w.root);
let stats = compute_tree_stats(&w.root);
assert_eq!(stats.total_sets, set_counter.0);
assert_eq!(stats.total_reps, rep_counter.0);
assert!((stats.total_volume_kg - vol_calc.0).abs() < 0.001);
assert!((stats.exercises[0].volume_kg - ex_vol.volume).abs() < 0.001);
}
#[test]
fn tree_stats_serializes_to_json() {
let w = load_workout("examples/strength/straight-sets.json");
let stats = compute_tree_stats(&w.root);
let json = serde_json::to_string(&stats).unwrap();
assert!(json.contains("total_sets"));
assert!(json.contains("total_volume_kg"));
assert!(json.contains("Bench Press"));
let back: syntax_workout_core::stats::TreeStats = serde_json::from_str(&json).unwrap();
assert_eq!(back.total_sets, stats.total_sets);
}