tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — vello 0.9 / wgpu 29, 120 miembros
1//! El cableado del formulario con el `field`, montado.
2//!
3//! El núcleo (reglas, agregación, cuándo mostrar) se prueba en el propio crate.
4//! Acá se prueba lo otro: que el asterisco de obligatorio, la etiqueta y el
5//! error **lleguen a la pantalla desde las reglas** y no de una segunda
6//! escritura a mano, y que el resumen lleve a cada campo.
7//!
8//! Sin GPU (CLAUDE.md §8).
9
10use llimphi_test::query::{Sel, Ui};
11use llimphi_ui::Role;
12use llimphi_ui::{llimphi_layout::Style, View};
13use llimphi_widget_form::{
14 campo_view, resumen_errores_view, CampoDef, EstadoForm, Formulario, FormPalette, Regla,
15};
16
17#[derive(Clone, Debug, PartialEq, Eq, Copy)]
18enum Id {
19 Nombre,
20 Correo,
21}
22
23#[derive(Clone, Debug, PartialEq)]
24enum Msg {
25 IrA(Id),
26}
27
28fn form() -> Formulario<Id> {
29 Formulario::new(vec![
30 CampoDef::new(Id::Nombre, "Nombre")
31 .regla(Regla::Requerido)
32 .ayuda("Como figura en el documento"),
33 CampoDef::new(Id::Correo, "Correo").reglas(vec![Regla::Requerido, Regla::Correo]),
34 ])
35}
36
37fn pal() -> FormPalette {
38 FormPalette::from_theme(&llimphi_theme::Theme::default())
39}
40
41fn control() -> View<Msg> {
42 View::<Msg>::new(Style::default())
43}
44
45#[test]
46fn el_asterisco_y_la_etiqueta_salen_de_las_reglas() {
47 let f = form();
48 let sin_errores = Formulario::<Id>::new(vec![]).validar(&[]);
49 let ui = Ui::new(
50 campo_view(&f, Id::Nombre, control(), &sin_errores, &pal()),
51 300.0,
52 90.0,
53 );
54 // La etiqueta es la del CampoDef, con el asterisco que pone `Requerido`.
55 // Nadie lo escribió dos veces: si mañana se saca la regla, el asterisco se
56 // va solo.
57 assert!(ui.find("Nombre *").is_some(), "{}", ui.dump());
58 // Y la ayuda se ve mientras no haya error.
59 assert!(ui.find("Como figura en el documento").is_some());
60}
61
62#[test]
63fn el_error_reemplaza_a_la_ayuda_solo_cuando_toca_mostrarlo() {
64 let f = form();
65 let errores = f.validar(&[(Id::Nombre, ""), (Id::Correo, "")]);
66 let mut estado: EstadoForm<Id> = EstadoForm::nuevo();
67
68 // Recién abierto: hay error, pero no se muestra — la ayuda sigue.
69 let ui = Ui::new(
70 campo_view(&f, Id::Nombre, control(), &estado.visibles(&errores), &pal()),
71 300.0,
72 90.0,
73 );
74 assert!(ui.find("Nombre es obligatorio").is_none(), "{}", ui.dump());
75 assert!(ui.find("Como figura en el documento").is_some());
76
77 // Al salir del campo sí, y desplaza a la ayuda (son excluyentes).
78 estado.tocar(Id::Nombre);
79 let ui = Ui::new(
80 campo_view(&f, Id::Nombre, control(), &estado.visibles(&errores), &pal()),
81 300.0,
82 90.0,
83 );
84 assert!(ui.find("Nombre es obligatorio").is_some(), "{}", ui.dump());
85 assert!(ui.find("Como figura en el documento").is_none());
86}
87
88#[test]
89fn el_resumen_lista_los_errores_y_lleva_a_cada_campo() {
90 let f = form();
91 let errores = f.validar(&[(Id::Nombre, ""), (Id::Correo, "roto")]);
92 let v = resumen_errores_view(&errores, Msg::IrA, &pal()).expect("hay errores");
93 let ui = Ui::new(v, 400.0, 120.0);
94
95 // Un título que dice cuántos son, anunciado como alerta: es el punto de
96 // entrada tras un submit fallido.
97 let alerta = ui.get(Role::Alert);
98 assert_eq!(alerta.name(), Some("Hay 2 errores que corregir"));
99 // Cada error es clickeable y lleva a SU campo.
100 assert_eq!(
101 ui.get(Sel::new().role(Role::Button).label("Nombre es obligatorio")).click(),
102 Some(Msg::IrA(Id::Nombre))
103 );
104 assert_eq!(
105 ui.get(Sel::new().role(Role::Button).label("Correo no parece un correo")).click(),
106 Some(Msg::IrA(Id::Correo))
107 );
108}
109
110#[test]
111fn sin_errores_el_resumen_no_existe() {
112 // Devuelve `None`, no un panel vacío: así el caller lo mete sin un `if`
113 // propio y no queda un hueco de 30 px en el formulario.
114 let f = form();
115 let ok = f.validar(&[(Id::Nombre, "Sergio"), (Id::Correo, "sergio@gioser.net")]);
116 assert!(ok.es_valido());
117 assert!(resumen_errores_view(&ok, Msg::IrA, &pal()).is_none());
118}
119
120#[test]
121fn el_singular_no_dice_1_errores() {
122 let f = form();
123 let uno = f.validar(&[(Id::Nombre, ""), (Id::Correo, "a@b.co")]);
124 let v = resumen_errores_view(&uno, Msg::IrA, &pal()).expect("hay un error");
125 let ui = Ui::new(v, 400.0, 80.0);
126 assert_eq!(ui.get(Role::Alert).name(), Some("Hay 1 error que corregir"));
127}