tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 124Markdown 82TOML 78Shell 12149 Sutichaykuna

⎇ main widgets/fugaces/src/tests.rs Rust · 212 siq'ikuna · 8.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos b3f12e136 · Sergio ·

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}