// ============================================================================= // llimphi-voxel :: tests/historia — versiones, alternativas, dedup por contenido // ----------------------------------------------------------------------------- // El DAG sobre el que se monta todo: commitear versiones, reconstruir el // proyecto de cualquiera, ramificar alternativas, seguir el linaje, y que // snapshots idénticos se guarden una sola vez. // ============================================================================= use llimphi_voxel::{Historia, Project}; #[test] fn commitea_y_reconstruye_el_proyecto() { let mut h = Historia::nueva(); assert!(h.is_empty()); let v1 = h.commit(&Project::nacimiento(), "el nacimiento").unwrap(); assert_eq!(h.len(), 1); assert_eq!(h.punta(), Some(v1)); // El proyecto de la versión reconstruye el snapshot íntegro. let recuperado = h.proyecto_de(&v1).expect("la versión existe"); assert!(recuperado.validar_referencias().is_empty()); assert_eq!(recuperado.escenas[0].name, "el nacimiento"); } #[test] fn la_cadena_de_versiones_es_el_linaje() { let mut h = Historia::nueva(); let mut p = Project::nacimiento(); let v1 = h.commit(&p, "v1: el nacimiento").unwrap(); // Editar y commitear otra versión. if let Some(esc) = p.escenas.first_mut() { if let Some(atm) = esc.atmosfera.as_mut() { atm.bruma = 4.0; // más bruma } } let v2 = h.commit(&p, "v2: más bruma").unwrap(); // El linaje de v2 = [v2, v1]. assert_eq!(h.linaje(&v2), vec![v2, v1]); // El padre de v2 es v1. assert_eq!(h.version(&v2).unwrap().padres, vec![v1]); // La punta avanzó a v2. assert_eq!(h.punta(), Some(v2)); } #[test] fn las_ramas_son_alternativas() { let mut h = Historia::nueva(); let base = h.commit(&Project::nacimiento(), "base").unwrap(); // Ramificar dos alternativas desde la base. h.ramificar("con-montañas").unwrap(); h.ramificar("sin-bruma").unwrap(); // En "con-montañas": una versión con montañas. assert!(h.cambiar_rama("con-montañas")); let mut p = h.proyecto_actual().unwrap(); p.biomas[0].mountains = 0.6; let alt_a = h.commit(&p, "el nacimiento con montañas").unwrap(); // En "sin-bruma": otra versión, hermana de la anterior (mismo padre `base`). assert!(h.cambiar_rama("sin-bruma")); let mut p = h.proyecto_actual().unwrap(); p.escenas[0].atmosfera = None; let alt_b = h.commit(&p, "el nacimiento despejado").unwrap(); // alt_a y alt_b son ALTERNATIVAS: comparten el padre `base`. assert_eq!(h.version(&alt_a).unwrap().padres, vec![base]); assert_eq!(h.version(&alt_b).unwrap().padres, vec![base]); assert!(h.alternativas(&alt_a).contains(&alt_b)); assert!(h.alternativas(&alt_b).contains(&alt_a)); // Y cada rama reconstruye SU proyecto. assert_eq!(h.proyecto_de(&alt_a).unwrap().biomas[0].mountains, 0.6); assert!(h.proyecto_de(&alt_b).unwrap().escenas[0].atmosfera.is_none()); } #[test] fn snapshots_identicos_se_dedupean() { let mut h = Historia::nueva(); // Dos versiones del MISMO proyecto: distinta versión (distinto orden), pero // el snapshot es el mismo hash → un solo blob almacenado. let p = Project::nacimiento(); let v1 = h.commit(&p, "una").unwrap(); h.ramificar("otra").unwrap(); h.cambiar_rama("otra"); let v2 = h.commit(&p, "otra").unwrap(); assert_ne!(v1, v2, "son versiones distintas"); // Pero apuntan al MISMO snapshot (contenido idéntico). assert_eq!(h.version(&v1).unwrap().snapshot, h.version(&v2).unwrap().snapshot); } #[test] fn navegar_vuelve_a_una_version_y_bifurca_desde_ahi() { let mut h = Historia::nueva(); let mut p = Project::nacimiento(); let v1 = h.commit(&p, "v1").unwrap(); p.biomas[0].mountains = 0.5; let v2 = h.commit(&p, "v2 con montañas").unwrap(); assert_eq!(h.actual(), Some(v2)); // Volver (checkout) a v1: la cabeza se mueve y reconstruye ESE proyecto. let vuelto = h.checkout(&v1).expect("v1 existe"); assert_eq!(h.actual(), Some(v1)); assert!(vuelto.biomas[0].mountains < 0.1, "v1 no tenía montañas"); // Bifurcar desde el pasado: un commit ahora cuelga de v1, no de v2. let mut p = vuelto; p.escenas[0].atmosfera = None; let v3 = h.commit(&p, "v3 desde v1, despejado").unwrap(); assert_eq!(h.version(&v3).unwrap().padres, vec![v1]); // v2 y v3 son alternativas (ambas cuelgan de v1). assert!(h.alternativas(&v3).contains(&v2)); } #[test] fn versiones_ordenadas_por_creacion() { let mut h = Historia::nueva(); let a = h.commit(&Project::nacimiento(), "a").unwrap(); let b = h.commit(&Project::nacimiento(), "b").unwrap(); let orden: Vec<_> = h.versiones_ordenadas().into_iter().map(|(id, _)| id).collect(); assert_eq!(orden, vec![a, b], "en orden de creación"); } #[test] fn la_historia_viaja_por_archivo() { let mut h = Historia::nueva(); let v1 = h.commit(&Project::nacimiento(), "el nacimiento").unwrap(); h.ramificar("variante").unwrap(); let bytes = h.a_bytes().unwrap(); let recuperada = Historia::de_bytes(&bytes).unwrap(); assert_eq!(recuperada.len(), h.len()); assert_eq!(recuperada.punta(), Some(v1)); assert!(recuperada.ramas().contains_key("variante")); // El proyecto sobrevive el round-trip de la historia. assert_eq!( recuperada.proyecto_de(&v1).unwrap().escenas[0].name, "el nacimiento" ); }