// ============================================================================= // pruebas_wire :: que el encoder produzca una escena que sirva (W4) // ----------------------------------------------------------------------------- // Que esto COMPILE ya prueba lo principal —`render.rs` no cambió una línea y sin // embargo emite hacia el IR de escena— pero compilar no es suficiente, y en este // crate menos que en ningún otro: con un medidor que devuelva ceros el layout se // colapsa entero y la escena sale «bien» con todas las cajas en el origen. // // Así que se certifica lo que puede salir mal en silencio: // // 1. Que el texto viaje SIN SHAPEAR (es W4 entero). // 2. Que se haya usado el medidor de verdad y no la estimación de emergencia. // 3. Que la posición que emite el paint sea la que el layout calculó. // 4. Que un subárbol rotado conserve su matriz en vez de aplanarse. // ============================================================================= use alloc::vec; use llimphi_layout::taffy::prelude::*; use llimphi_layout::Style; use llimphi_text::peniko::Color; use llimphi_text::wire::{Medida, Peticion}; use llimphi_wire_escena::{EscenaWire, OrdenWire, UbicacionWire}; use crate::{Jaula, View}; /// Ancho por carácter del medidor de prueba. Un número redondo y feo a /// propósito: si algún cálculo lo perdiera, la diferencia sería visible. const ANCHO_GLIFO: f32 = 10.0; #[derive(Clone, Debug, PartialEq)] enum Msg { Nada, } /// Un medidor determinista que ocupa el lugar del `sys_` de wawa. /// /// En producción esto pregunta del otro lado de la frontera y allá contesta /// parley. Acá contesta una fórmula, porque lo que este test certifica no es la /// fidelidad tipográfica —eso lo prueba el reproductor, que shapea de verdad— /// sino que el número que devuelve el medidor sea el que termina gobernando el /// layout y la posición emitida. fn medidor(p: &Peticion<'_>) -> Medida { let chars = p.cadena.chars().count() as f32; let ancho = chars * ANCHO_GLIFO; Medida { width: ancho, full_width: ancho, height: p.estilo.tamano_px * p.estilo.alto_linea, } } fn pantalla(w: f32, h: f32) -> Style { Style { size: Size { width: length(w), height: length(h), }, ..Default::default() } } /// Corre un fotograma entero y devuelve la escena lista para el cable. fn componer(vista: View, viewport: (f32, f32)) -> (EscenaWire, usize) { let mut jaula = Jaula::new(viewport.0, viewport.1, medidor); let _montado = jaula.fotograma(vista); (jaula.escena().clone(), jaula.medidas_estimadas()) } #[test] fn el_paint_emite_el_texto_sin_shapear() { let vista: View = View::new(pantalla(200.0, 60.0)) .fill(Color::from_rgba8(10, 10, 20, 255)) .children(vec![View::new(Style::default()).text( "hola", 14.0, Color::from_rgba8(255, 255, 255, 255), )]); let (escena, estimadas) = componer(vista, (200.0, 60.0)); // Que NO se haya estimado es tan importante como que haya texto: una // estimación silenciosa querría decir que el medidor no se enchufó y que // todo el layout de este test es ficción. assert_eq!( estimadas, 0, "se midió por estimación — el medidor no se usó" ); assert_eq!(escena.textos(), 1, "tiene que haber exactamente un texto"); let texto = escena .ordenes() .iter() .find_map(|o| match o { OrdenWire::Texto(t) => Some(t), _ => None, }) .expect("la orden de texto"); assert_eq!(texto.cadena, "hola"); assert_eq!(texto.color, [255, 255, 255, 255]); // El estilo se internó y se puede resolver. let estilo = escena.estilo(texto.estilo).expect("estilo en tabla"); assert_eq!(estilo.tamano_px, 14.0); // Y el fondo se emitió como relleno, o sea que el resto del paint también // pasó por el cable y no sólo el texto. assert!( escena .ordenes() .iter() .any(|o| matches!(o, OrdenWire::Rellenar { .. })), "falta el relleno del fondo" ); } #[test] fn la_medida_del_medidor_gobierna_el_layout_y_la_posicion() { // Dos etiquetas en columna: la segunda tiene que caer al alto de la primera, // que es el número que devolvió el medidor. Si el layout ignorara la medida // —o si el paint emitiera coordenadas crudas— las dos se pisarían en y=0. let alto_linea = 14.0 * 1.2; let vista: View = View::new(Style { size: Size { width: length(300.0), height: length(100.0), }, flex_direction: FlexDirection::Column, ..Default::default() }) .children(vec![ View::new(Style::default()).text("arriba", 14.0, Color::BLACK), View::new(Style::default()).text("abajo", 14.0, Color::BLACK), ]); let (escena, estimadas) = componer(vista, (300.0, 100.0)); assert_eq!(estimadas, 0); let ys: alloc::vec::Vec = escena .ordenes() .iter() .filter_map(|o| match o { OrdenWire::Texto(t) => Some(t.xf.traslacion().1), _ => None, }) .collect(); assert_eq!(ys.len(), 2, "dos textos"); // Las comparaciones van con holgura de un píxel, y la razón no es pereza: // taffy REDONDEA el rect final a píxeles enteros, así que una línea de 16,8 // px queda en una caja de 17, y el centrado vertical del paint la baja 0,1. // Esos 0,1 son comportamiento correcto —el mismo que produce el backend de // vello— y exigir el cero exacto sería certificar un bug. assert!(ys[0] < 1.0, "el primero arriba de todo, cayó a {}", ys[0]); assert!( (ys[1] - alto_linea).abs() <= 1.0, "el segundo tenía que caer a ~{alto_linea}, cayó a {}", ys[1] ); // Lo que de verdad importa: el segundo NO se pisa con el primero. Si el // layout hubiera ignorado la medida, los dos estarían en cero. assert!( ys[1] - ys[0] > alto_linea * 0.9, "los dos textos se pisaron: {} y {}", ys[0], ys[1] ); } #[test] fn un_subarbol_rotado_conserva_su_matriz() { // El caso que motivó que la ubicación del texto sea `UbicacionWire` y no un // punto. Con un punto, esto se pintaría derecho y nada avisaría. let vista: View = View::new(pantalla(200.0, 200.0)).children(vec![View::new( Style::default(), ) .transform(llimphi_text::peniko::kurbo::Affine::rotate(0.4)) .children(vec![View::new(Style::default()).text( "girado", 14.0, Color::BLACK, )])]); let (escena, _) = componer(vista, (200.0, 200.0)); let texto = escena .ordenes() .iter() .find_map(|o| match o { OrdenWire::Texto(t) => Some(t), _ => None, }) .expect("la orden de texto"); assert!( matches!(texto.xf, UbicacionWire::Afin(_)), "un texto bajo una rotación tiene que viajar con la matriz entera, vino {:?}", texto.xf ); } #[test] fn sin_medidor_se_estima_y_se_delata() { // La red de seguridad: si alguien olvida enchufar el medidor, el layout es // aproximado — pero el contador lo dice en vez de que la pantalla salga rara // sin explicación. let mut ts = llimphi_text::wire::Typesetter::new(); let ancho = llimphi_text::text_width_with(&mut ts, "hola", 14.0); assert!(ancho > 0.0, "la estimación no puede devolver cero"); assert_eq!(ts.medidas_estimadas(), 1, "y tiene que quedar contada"); }