tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos
1//! Los tests de la **máquina**, sobre un enum de juguete. Los tests de que cada
2//! consumidor la usa bien viven en el consumidor (en `pata`, sobre `Fugaz`).
3
4use super::*;
5use alloc::vec;
6
7#[derive(Clone, Copy, PartialEq, Eq, Debug)]
8enum T {
9 Uno,
10 Dos,
11 Tres,
12}
13
14impl Fantasma for T {
15 fn clave(self) -> &'static str {
16 match self {
17 T::Uno => "uno",
18 T::Dos => "dos",
19 T::Tres => "tres",
20 }
21 }
22 fn prior(self) -> f32 {
23 match self {
24 T::Uno => 1.0,
25 T::Dos => 2.0,
26 T::Tres => 3.0,
27 }
28 }
29}
30
31const TODOS: [T; 3] = [T::Uno, T::Dos, T::Tres];
32
33/// Lo urgente no rota: las críticas salen **todas** y a la vez.
34#[test]
35fn las_criticas_van_todas_juntas() {
36 let cands = [(T::Uno, Nivel::Critica), (T::Dos, Nivel::Critica), (T::Tres, Nivel::Leve)];
37 let vis = elegir_visibles(&cands, 0, None);
38 assert!(vis.contains(&T::Uno) && vis.contains(&T::Dos));
39}
40
41/// Un acuse de segundos **no espera** una rotación de cuatro. Éste es el test
42/// del reporte «no veo el volumen cuando sube».
43#[test]
44fn un_evento_no_espera_turno() {
45 let cands = [(T::Uno, Nivel::Leve), (T::Dos, Nivel::Leve), (T::Tres, Nivel::Evento)];
46 for idx in 0..6 {
47 assert!(
48 elegir_visibles(&cands, idx, None).contains(&T::Tres),
49 "el acuse se perdió el turno {idx}"
50 );
51 }
52}
53
54/// De las leves se ve UNA, y va turnándose.
55#[test]
56fn las_leves_se_turnan_de_a_una() {
57 let cands = [(T::Uno, Nivel::Leve), (T::Dos, Nivel::Leve)];
58 assert_eq!(elegir_visibles(&cands, 0, None), vec![T::Uno]);
59 assert_eq!(elegir_visibles(&cands, 1, None), vec![T::Dos]);
60 assert_eq!(elegir_visibles(&cands, 2, None), vec![T::Uno]);
61}
62
63/// Sin ninguna leve la franja **no queda muerta**: el turno lo toma el fondo
64/// ambiental, y a paso más lento.
65#[test]
66fn el_fondo_ambiental_ocupa_el_turno_vacio_y_va_mas_lento() {
67 let cands = [(T::Uno, Nivel::Ambiente), (T::Dos, Nivel::Ambiente)];
68 assert_eq!(elegir_visibles(&cands, 0, None), vec![T::Uno]);
69 // Todavía la misma: el ambiente dura AMBIENTE_TURNOS turnos.
70 assert_eq!(elegir_visibles(&cands, 1, None), vec![T::Uno]);
71 assert_eq!(elegir_visibles(&cands, AMBIENTE_TURNOS, None), vec![T::Dos]);
72 // Con una leve presente, el ambiente cede el turno.
73 let con_leve = [(T::Uno, Nivel::Ambiente), (T::Tres, Nivel::Leve)];
74 assert_eq!(elegir_visibles(&con_leve, 0, None), vec![T::Tres]);
75}
76
77/// El pinneado se queda aunque su condición haya caído — pero sólo si sigue
78/// siendo candidato (si su dato desapareció del todo, no hay nada que pinnear).
79#[test]
80fn el_pin_sobrevive_a_que_se_le_caiga_la_condicion() {
81 let cands = [(T::Uno, Nivel::Leve), (T::Tres, Nivel::Nada)];
82 assert!(elegir_visibles(&cands, 0, Some(T::Tres)).contains(&T::Tres));
83 assert!(!elegir_visibles(&cands, 0, Some(T::Dos)).contains(&T::Dos));
84}
85
86/// Nada saliente y nada ambiental = franja vacía. No se inventa un ocupante.
87#[test]
88fn sin_nada_que_decir_la_franja_queda_vacia() {
89 let cands = [(T::Uno, Nivel::Nada), (T::Dos, Nivel::Nada)];
90 assert!(elegir_visibles(&cands, 3, None).is_empty());
91}
92
93/// El turno gira cada [`ROT_US`] y **se detiene congelado**, re-estampando el
94/// reloj: al soltar, el próximo giro tarda el intervalo completo.
95#[test]
96fn el_turno_gira_por_reloj_y_se_frena_congelado() {
97 let (mut idx, mut reloj) = (0usize, 0u64);
98 assert!(!avanzar_idx(&mut idx, &mut reloj, ROT_US - 1, false));
99 assert!(avanzar_idx(&mut idx, &mut reloj, ROT_US, false));
100 assert_eq!(idx, 1);
101 // Congelado: no gira, y el reloj queda estampado en `ahora`.
102 assert!(!avanzar_idx(&mut idx, &mut reloj, 10 * ROT_US, true));
103 assert_eq!(idx, 1);
104 // Recién un intervalo COMPLETO después de soltar vuelve a girar.
105 assert!(!avanzar_idx(&mut idx, &mut reloj, 10 * ROT_US + 1, false));
106 assert!(avanzar_idx(&mut idx, &mut reloj, 11 * ROT_US, false));
107 assert_eq!(idx, 2);
108}
109
110/// El asiento: el más útil se sienta **último**, y el uso corrige el prior.
111#[test]
112fn el_uso_corrige_el_prior_y_satura() {
113 let sin_uso = orden_asientos(&TODOS, |_| 0);
114 assert_eq!(sin_uso, vec![T::Uno, T::Dos, T::Tres]);
115 // Cuatro clicks a `Uno` (prior 1) lo suben a 1 + 2·2 = 5: pasa a Tres (3).
116 let con_uso = orden_asientos(&TODOS, |f| if f == T::Uno { 4 } else { 0 });
117 assert_eq!(con_uso.last(), Some(&T::Uno));
118 // Y satura: cuadruplicar los clicks sólo DUPLICA lo que el asiento se
119 // mueve. Es la propiedad que hace que el orden se pueda memorizar — los
120 // primeros clicks mandan, los siguientes casi no mueven nada.
121 let d1 = score(T::Uno, 4) - T::Uno.prior();
122 let d2 = score(T::Uno, 16) - T::Uno.prior();
123 assert!(d2 < 4.0 * d1, "el asiento no satura: {d1} → {d2}");
124 assert!((d2 - 2.0 * d1).abs() < 1e-4, "la raíz no es la que satura: {d1} → {d2}");
125}
126
127/// **Lo que el congelado protege.** Clickear aprende, pero el asiento nuevo no
128/// puede regir mientras la mano está sobre la franja.
129#[test]
130fn el_asiento_congelado_no_se_mueve_aunque_el_uso_cambie() {
131 let cands = [(T::Uno, Nivel::Leve)];
132 let fijo = congelar(&TODOS, &cands, |_| 0, 0, None, 7);
133 assert_eq!(fijo.orden, vec![T::Uno, T::Dos, T::Tres]);
134 // Mientras tanto el usuario clickeó `Uno` nueve veces: el orden VIVO cambia…
135 let vivo = orden_asientos(&TODOS, |f| if f == T::Uno { 9 } else { 0 });
136 assert_ne!(vivo, fijo.orden);
137 // …pero el snapshot congelado sigue diciendo lo mismo.
138 assert_eq!(fijo.orden, vec![T::Uno, T::Dos, T::Tres]);
139 assert_eq!(fijo.reloj_us, 7);
140}
141
142/// **La membresía congelada, que es la mitad menos obvia.** Un fantasma que
143/// desaparece a mitad del hover deja un HUECO de su ancho: los vecinos no se
144/// corren bajo el mouse.
145#[test]
146fn el_que_se_va_durante_el_hover_deja_su_hueco() {
147 let presentes = [(T::Uno, Nivel::Leve), (T::Dos, Nivel::Leve), (T::Tres, Nivel::Leve)];
148 let fijo = congelar(&TODOS, &presentes, |_| 0, 0, None, 0);
149 // Ahora `Dos` se fue: sólo llegan dos vistas.
150 let cands: alloc::vec::Vec<(T, Nivel, View<()>)> = vec![
151 (T::Uno, Nivel::Leve, View::new(Style::default())),
152 (T::Tres, Nivel::Leve, View::new(Style::default())),
153 ];
154 let vis = [T::Uno, T::Dos, T::Tres];
155 let franja = Franja {
156 visibles: &vis,
157 fijo: Some(&fijo),
158 pin: None,
159 base: 1.0,
160 revelar_alpha: 1.0,
161 alto_hueco: 20.0,
162 };
163 let out = slots(cands, &franja, |_| 0, |_| 10.0);
164 assert_eq!(out.len(), 3, "el hueco del que se fue no se reservó");
165}
166
167/// Y el que **aparece** a mitad del hover no se inserta: correría a los demás.
168#[test]
169fn el_que_llega_durante_el_hover_no_se_inserta() {
170 let presentes = [(T::Uno, Nivel::Leve)];
171 let fijo = congelar(&TODOS, &presentes, |_| 0, 0, None, 0);
172 let cands: alloc::vec::Vec<(T, Nivel, View<()>)> = vec![
173 (T::Uno, Nivel::Leve, View::new(Style::default())),
174 (T::Dos, Nivel::Leve, View::new(Style::default())),
175 ];
176 let vis = [T::Uno];
177 let franja = Franja {
178 visibles: &vis,
179 fijo: Some(&fijo),
180 pin: None,
181 base: 1.0,
182 revelar_alpha: 1.0,
183 alto_hueco: 20.0,
184 };
185 let out = slots(cands, &franja, |_| 0, |_| 10.0);
186 assert_eq!(out.len(), 1, "el recién llegado se coló y corrió a los demás");
187}
188
189/// Fuera del reveal, lo que no está visible **no se monta** (no ocupa lugar);
190/// con el reveal encendido se monta todo.
191#[test]
192fn fuera_del_reveal_solo_se_monta_lo_visible() {
193 let armar = |revelar: f32| {
194 let cands: alloc::vec::Vec<(T, Nivel, View<()>)> = vec![
195 (T::Uno, Nivel::Leve, View::new(Style::default())),
196 (T::Dos, Nivel::Ambiente, View::new(Style::default())),
197 (T::Tres, Nivel::Nada, View::new(Style::default())),
198 ];
199 let vis = [T::Uno];
200 let franja = Franja {
201 visibles: &vis,
202 fijo: None,
203 pin: None,
204 base: 1.0,
205 revelar_alpha: revelar,
206 alto_hueco: 20.0,
207 };
208 slots(cands, &franja, |_| 0, |_| 10.0).len()
209 };
210 assert_eq!(armar(0.0), 1, "en reposo se coló algo que nadie pidió");
211 assert_eq!(armar(1.0), 3, "el reveal no trajo a todos");
212}