tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1// =============================================================================
2// llimphi-voxel :: tests/historia — versiones, alternativas, dedup por contenido
3// -----------------------------------------------------------------------------
4// El DAG sobre el que se monta todo: commitear versiones, reconstruir el
5// proyecto de cualquiera, ramificar alternativas, seguir el linaje, y que
6// snapshots idénticos se guarden una sola vez.
7// =============================================================================
8
9use llimphi_voxel::{Historia, Project};
10
11#[test]
12fn commitea_y_reconstruye_el_proyecto() {
13 let mut h = Historia::nueva();
14 assert!(h.is_empty());
15
16 let v1 = h.commit(&Project::nacimiento(), "el nacimiento").unwrap();
17 assert_eq!(h.len(), 1);
18 assert_eq!(h.punta(), Some(v1));
19
20 // El proyecto de la versión reconstruye el snapshot íntegro.
21 let recuperado = h.proyecto_de(&v1).expect("la versión existe");
22 assert!(recuperado.validar_referencias().is_empty());
23 assert_eq!(recuperado.escenas[0].name, "el nacimiento");
24}
25
26#[test]
27fn la_cadena_de_versiones_es_el_linaje() {
28 let mut h = Historia::nueva();
29 let mut p = Project::nacimiento();
30 let v1 = h.commit(&p, "v1: el nacimiento").unwrap();
31 // Editar y commitear otra versión.
32 if let Some(esc) = p.escenas.first_mut() {
33 if let Some(atm) = esc.atmosfera.as_mut() {
34 atm.bruma = 4.0; // más bruma
35 }
36 }
37 let v2 = h.commit(&p, "v2: más bruma").unwrap();
38
39 // El linaje de v2 = [v2, v1].
40 assert_eq!(h.linaje(&v2), vec![v2, v1]);
41 // El padre de v2 es v1.
42 assert_eq!(h.version(&v2).unwrap().padres, vec![v1]);
43 // La punta avanzó a v2.
44 assert_eq!(h.punta(), Some(v2));
45}
46
47#[test]
48fn las_ramas_son_alternativas() {
49 let mut h = Historia::nueva();
50 let base = h.commit(&Project::nacimiento(), "base").unwrap();
51
52 // Ramificar dos alternativas desde la base.
53 h.ramificar("con-montañas").unwrap();
54 h.ramificar("sin-bruma").unwrap();
55
56 // En "con-montañas": una versión con montañas.
57 assert!(h.cambiar_rama("con-montañas"));
58 let mut p = h.proyecto_actual().unwrap();
59 p.biomas[0].mountains = 0.6;
60 let alt_a = h.commit(&p, "el nacimiento con montañas").unwrap();
61
62 // En "sin-bruma": otra versión, hermana de la anterior (mismo padre `base`).
63 assert!(h.cambiar_rama("sin-bruma"));
64 let mut p = h.proyecto_actual().unwrap();
65 p.escenas[0].atmosfera = None;
66 let alt_b = h.commit(&p, "el nacimiento despejado").unwrap();
67
68 // alt_a y alt_b son ALTERNATIVAS: comparten el padre `base`.
69 assert_eq!(h.version(&alt_a).unwrap().padres, vec![base]);
70 assert_eq!(h.version(&alt_b).unwrap().padres, vec![base]);
71 assert!(h.alternativas(&alt_a).contains(&alt_b));
72 assert!(h.alternativas(&alt_b).contains(&alt_a));
73
74 // Y cada rama reconstruye SU proyecto.
75 assert_eq!(h.proyecto_de(&alt_a).unwrap().biomas[0].mountains, 0.6);
76 assert!(h.proyecto_de(&alt_b).unwrap().escenas[0].atmosfera.is_none());
77}
78
79#[test]
80fn snapshots_identicos_se_dedupean() {
81 let mut h = Historia::nueva();
82 // Dos versiones del MISMO proyecto: distinta versión (distinto orden), pero
83 // el snapshot es el mismo hash → un solo blob almacenado.
84 let p = Project::nacimiento();
85 let v1 = h.commit(&p, "una").unwrap();
86 h.ramificar("otra").unwrap();
87 h.cambiar_rama("otra");
88 let v2 = h.commit(&p, "otra").unwrap();
89
90 assert_ne!(v1, v2, "son versiones distintas");
91 // Pero apuntan al MISMO snapshot (contenido idéntico).
92 assert_eq!(h.version(&v1).unwrap().snapshot, h.version(&v2).unwrap().snapshot);
93}
94
95#[test]
96fn navegar_vuelve_a_una_version_y_bifurca_desde_ahi() {
97 let mut h = Historia::nueva();
98 let mut p = Project::nacimiento();
99 let v1 = h.commit(&p, "v1").unwrap();
100 p.biomas[0].mountains = 0.5;
101 let v2 = h.commit(&p, "v2 con montañas").unwrap();
102 assert_eq!(h.actual(), Some(v2));
103
104 // Volver (checkout) a v1: la cabeza se mueve y reconstruye ESE proyecto.
105 let vuelto = h.checkout(&v1).expect("v1 existe");
106 assert_eq!(h.actual(), Some(v1));
107 assert!(vuelto.biomas[0].mountains < 0.1, "v1 no tenía montañas");
108
109 // Bifurcar desde el pasado: un commit ahora cuelga de v1, no de v2.
110 let mut p = vuelto;
111 p.escenas[0].atmosfera = None;
112 let v3 = h.commit(&p, "v3 desde v1, despejado").unwrap();
113 assert_eq!(h.version(&v3).unwrap().padres, vec![v1]);
114 // v2 y v3 son alternativas (ambas cuelgan de v1).
115 assert!(h.alternativas(&v3).contains(&v2));
116}
117
118#[test]
119fn versiones_ordenadas_por_creacion() {
120 let mut h = Historia::nueva();
121 let a = h.commit(&Project::nacimiento(), "a").unwrap();
122 let b = h.commit(&Project::nacimiento(), "b").unwrap();
123 let orden: Vec<_> = h.versiones_ordenadas().into_iter().map(|(id, _)| id).collect();
124 assert_eq!(orden, vec![a, b], "en orden de creación");
125}
126
127#[test]
128fn la_historia_viaja_por_archivo() {
129 let mut h = Historia::nueva();
130 let v1 = h.commit(&Project::nacimiento(), "el nacimiento").unwrap();
131 h.ramificar("variante").unwrap();
132
133 let bytes = h.a_bytes().unwrap();
134 let recuperada = Historia::de_bytes(&bytes).unwrap();
135
136 assert_eq!(recuperada.len(), h.len());
137 assert_eq!(recuperada.punta(), Some(v1));
138 assert!(recuperada.ramas().contains_key("variante"));
139 // El proyecto sobrevive el round-trip de la historia.
140 assert_eq!(
141 recuperada.proyecto_de(&v1).unwrap().escenas[0].name,
142 "el nacimiento"
143 );
144}