tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definitions

⎇ main llimphi-test/tests/form_validacion.rs Rust · 127 lines · 4.3 KB History Who touched what raw

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

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}