gioser

tawasuyu/llimphi

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

Rust 484Markdown 258TOML 131RON 38118 Definitions

⎇ main llimphi-jaula/src/pruebas.rs Rust · 211 lines · 7.5 KB History Who touched what raw

chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros d251eb32b · Sergio ·

1// =============================================================================
2//  pruebas_wire :: que el encoder produzca una escena que sirva  (W4)
3// -----------------------------------------------------------------------------
4//  Que esto COMPILE ya prueba lo principal —`render.rs` no cambió una línea y sin
5//  embargo emite hacia el IR de escena— pero compilar no es suficiente, y en este
6//  crate menos que en ningún otro: con un medidor que devuelva ceros el layout se
7//  colapsa entero y la escena sale «bien» con todas las cajas en el origen.
8//
9//  Así que se certifica lo que puede salir mal en silencio:
10//
11//    1. Que el texto viaje SIN SHAPEAR (es W4 entero).
12//    2. Que se haya usado el medidor de verdad y no la estimación de emergencia.
13//    3. Que la posición que emite el paint sea la que el layout calculó.
14//    4. Que un subárbol rotado conserve su matriz en vez de aplanarse.
15// =============================================================================
16
17use alloc::vec;
18
19use llimphi_layout::taffy::prelude::*;
20use llimphi_layout::Style;
21use llimphi_text::peniko::Color;
22use llimphi_text::wire::{Medida, Peticion};
23use llimphi_wire_escena::{EscenaWire, OrdenWire, UbicacionWire};
24
25use crate::{Jaula, View};
26
27/// Ancho por carácter del medidor de prueba. Un número redondo y feo a
28/// propósito: si algún cálculo lo perdiera, la diferencia sería visible.
29const ANCHO_GLIFO: f32 = 10.0;
30
31#[derive(Clone, Debug, PartialEq)]
32enum Msg {
33    Nada,
34}
35
36/// Un medidor determinista que ocupa el lugar del `sys_` de wawa.
37///
38/// En producción esto pregunta del otro lado de la frontera y allá contesta
39/// parley. Acá contesta una fórmula, porque lo que este test certifica no es la
40/// fidelidad tipográfica —eso lo prueba el reproductor, que shapea de verdad—
41/// sino que el número que devuelve el medidor sea el que termina gobernando el
42/// layout y la posición emitida.
43fn medidor(p: &Peticion<'_>) -> Medida {
44    let chars = p.cadena.chars().count() as f32;
45    let ancho = chars * ANCHO_GLIFO;
46    Medida {
47        width: ancho,
48        full_width: ancho,
49        height: p.estilo.tamano_px * p.estilo.alto_linea,
50    }
51}
52
53fn pantalla(w: f32, h: f32) -> Style {
54    Style {
55        size: Size {
56            width: length(w),
57            height: length(h),
58        },
59        ..Default::default()
60    }
61}
62
63/// Corre un fotograma entero y devuelve la escena lista para el cable.
64fn componer(vista: View<Msg>, viewport: (f32, f32)) -> (EscenaWire, usize) {
65    let mut jaula = Jaula::new(viewport.0, viewport.1, medidor);
66    let _montado = jaula.fotograma(vista);
67    (jaula.escena().clone(), jaula.medidas_estimadas())
68}
69
70#[test]
71fn el_paint_emite_el_texto_sin_shapear() {
72    let vista: View<Msg> = View::new(pantalla(200.0, 60.0))
73        .fill(Color::from_rgba8(10, 10, 20, 255))
74        .children(vec![View::new(Style::default()).text(
75            "hola",
76            14.0,
77            Color::from_rgba8(255, 255, 255, 255),
78        )]);
79
80    let (escena, estimadas) = componer(vista, (200.0, 60.0));
81
82    // Que NO se haya estimado es tan importante como que haya texto: una
83    // estimación silenciosa querría decir que el medidor no se enchufó y que
84    // todo el layout de este test es ficción.
85    assert_eq!(
86        estimadas, 0,
87        "se midió por estimación — el medidor no se usó"
88    );
89
90    assert_eq!(escena.textos(), 1, "tiene que haber exactamente un texto");
91    let texto = escena
92        .ordenes()
93        .iter()
94        .find_map(|o| match o {
95            OrdenWire::Texto(t) => Some(t),
96            _ => None,
97        })
98        .expect("la orden de texto");
99
100    assert_eq!(texto.cadena, "hola");
101    assert_eq!(texto.color, [255, 255, 255, 255]);
102    // El estilo se internó y se puede resolver.
103    let estilo = escena.estilo(texto.estilo).expect("estilo en tabla");
104    assert_eq!(estilo.tamano_px, 14.0);
105
106    // Y el fondo se emitió como relleno, o sea que el resto del paint también
107    // pasó por el cable y no sólo el texto.
108    assert!(
109        escena
110            .ordenes()
111            .iter()
112            .any(|o| matches!(o, OrdenWire::Rellenar { .. })),
113        "falta el relleno del fondo"
114    );
115}
116
117#[test]
118fn la_medida_del_medidor_gobierna_el_layout_y_la_posicion() {
119    // Dos etiquetas en columna: la segunda tiene que caer al alto de la primera,
120    // que es el número que devolvió el medidor. Si el layout ignorara la medida
121    // —o si el paint emitiera coordenadas crudas— las dos se pisarían en y=0.
122    let alto_linea = 14.0 * 1.2;
123    let vista: View<Msg> = View::new(Style {
124        size: Size {
125            width: length(300.0),
126            height: length(100.0),
127        },
128        flex_direction: FlexDirection::Column,
129        ..Default::default()
130    })
131    .children(vec![
132        View::new(Style::default()).text("arriba", 14.0, Color::BLACK),
133        View::new(Style::default()).text("abajo", 14.0, Color::BLACK),
134    ]);
135
136    let (escena, estimadas) = componer(vista, (300.0, 100.0));
137    assert_eq!(estimadas, 0);
138
139    let ys: alloc::vec::Vec<f32> = escena
140        .ordenes()
141        .iter()
142        .filter_map(|o| match o {
143            OrdenWire::Texto(t) => Some(t.xf.traslacion().1),
144            _ => None,
145        })
146        .collect();
147
148    assert_eq!(ys.len(), 2, "dos textos");
149
150    // Las comparaciones van con holgura de un píxel, y la razón no es pereza:
151    // taffy REDONDEA el rect final a píxeles enteros, así que una línea de 16,8
152    // px queda en una caja de 17, y el centrado vertical del paint la baja 0,1.
153    // Esos 0,1 son comportamiento correcto —el mismo que produce el backend de
154    // vello— y exigir el cero exacto sería certificar un bug.
155    assert!(ys[0] < 1.0, "el primero arriba de todo, cayó a {}", ys[0]);
156    assert!(
157        (ys[1] - alto_linea).abs() <= 1.0,
158        "el segundo tenía que caer a ~{alto_linea}, cayó a {}",
159        ys[1]
160    );
161    // Lo que de verdad importa: el segundo NO se pisa con el primero. Si el
162    // layout hubiera ignorado la medida, los dos estarían en cero.
163    assert!(
164        ys[1] - ys[0] > alto_linea * 0.9,
165        "los dos textos se pisaron: {} y {}",
166        ys[0],
167        ys[1]
168    );
169}
170
171#[test]
172fn un_subarbol_rotado_conserva_su_matriz() {
173    // El caso que motivó que la ubicación del texto sea `UbicacionWire` y no un
174    // punto. Con un punto, esto se pintaría derecho y nada avisaría.
175    let vista: View<Msg> = View::new(pantalla(200.0, 200.0)).children(vec![View::new(
176        Style::default(),
177    )
178    .transform(llimphi_text::peniko::kurbo::Affine::rotate(0.4))
179    .children(vec![View::new(Style::default()).text(
180        "girado",
181        14.0,
182        Color::BLACK,
183    )])]);
184
185    let (escena, _) = componer(vista, (200.0, 200.0));
186    let texto = escena
187        .ordenes()
188        .iter()
189        .find_map(|o| match o {
190            OrdenWire::Texto(t) => Some(t),
191            _ => None,
192        })
193        .expect("la orden de texto");
194
195    assert!(
196        matches!(texto.xf, UbicacionWire::Afin(_)),
197        "un texto bajo una rotación tiene que viajar con la matriz entera, vino {:?}",
198        texto.xf
199    );
200}
201
202#[test]
203fn sin_medidor_se_estima_y_se_delata() {
204    // La red de seguridad: si alguien olvida enchufar el medidor, el layout es
205    // aproximado — pero el contador lo dice en vez de que la pantalla salga rara
206    // sin explicación.
207    let mut ts = llimphi_text::wire::Typesetter::new();
208    let ancho = llimphi_text::text_width_with(&mut ts, "hola", 14.0);
209    assert!(ancho > 0.0, "la estimación no puede devolver cero");
210    assert_eq!(ts.medidas_estimadas(), 1, "y tiene que quedar contada");
211}