tawasuyu

tawasuyu/llimphi

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

Rust 229TOML 101Markdown 82Shell 24052 Definitions

⎇ main llimphi-wasm-runner/tests/counter.rs Rust · 77 lines · 2.8 KB History Who touched what raw

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

1//! Certificación headless del bucle Tier 3 — sin GPU ni ventana.
2//!
3//! Carga el `.wasm` del contador (compilado a wasm32, versionado en assets/),
4//! lo corre vía [`WasmGuest`] y verifica el viaje redondo completo: el guest
5//! pinta un `WireNode`, el host lo lee, un evento cruza a `wasm_dispatch` con su
6//! `EventId` + `EventPayload`, el `Model` del guest muta y la nueva vista lo
7//! refleja. También materializa el `WireNode` a un `View<RunnerMsg>` real.
8
9use llimphi_wasm_runner::{wire_to_view, EventPayload, RunnerMsg, WasmGuest};
10
11const COUNTER_WASM: &[u8] = include_bytes!("../assets/counter.wasm");
12
13// El +1 es el primer control que registra el guest ⇒ EventId 0; reset ⇒ 1.
14const EV_INCREMENT: u32 = 0;
15const EV_RESET: u32 = 1;
16
17/// El texto del número es el del primer hijo del árbol raíz (col → [número, botones]).
18fn number(guest: &WasmGuest) -> String {
19    guest.view().children[0]
20        .text
21        .as_ref()
22        .expect("el nodo número tiene texto")
23        .content
24        .clone()
25}
26
27#[test]
28fn counter_round_trip() {
29    let mut guest = WasmGuest::load(COUNTER_WASM, 0).expect("carga el guest counter");
30
31    // Estado inicial.
32    assert_eq!(number(&guest), "0");
33
34    // Los botones cargan los EventId que el guest asignó.
35    let buttons = &guest.view().children[1].children;
36    assert_eq!(buttons.len(), 2, "fila con +1 y reset");
37    assert_eq!(buttons[0].on_click, Some(EV_INCREMENT));
38    assert_eq!(buttons[1].on_click, Some(EV_RESET));
39
40    // Un click en +1 cruza a wasm_dispatch y muta el Model del guest.
41    guest
42        .dispatch(EV_INCREMENT, EventPayload::Click)
43        .expect("dispatch increment");
44    assert_eq!(number(&guest), "1");
45
46    guest
47        .dispatch(EV_INCREMENT, EventPayload::Click)
48        .expect("dispatch increment");
49    assert_eq!(number(&guest), "2");
50
51    // Reset vuelve a 0.
52    guest
53        .dispatch(EV_RESET, EventPayload::Click)
54        .expect("dispatch reset");
55    assert_eq!(number(&guest), "0");
56}
57
58#[test]
59fn materializa_view_real() {
60    let guest = WasmGuest::load(COUNTER_WASM, 0).expect("carga el guest counter");
61
62    // El WireNode se convierte en un View<RunnerMsg> Llimphi de verdad: si el
63    // mapeo explota (estilos, colores, handlers), esto trap-ea.
64    let view: llimphi_ui::View<RunnerMsg> = wire_to_view(guest.view(), None, None);
65
66    // Raíz: columna con relleno y dos hijos (número + fila de botones).
67    assert_eq!(view.children.len(), 2);
68
69    // El botón +1 quedó con on_click = RunnerMsg::Event(0, Click).
70    let row = &view.children[1];
71    let inc = &row.children[0];
72    match inc.on_click.as_ref().expect("el botón +1 tiene on_click") {
73        RunnerMsg::Event(id, EventPayload::Click) => assert_eq!(*id, EV_INCREMENT),
74        other => panic!("on_click inesperado: {other:?}"),
75    }
76    assert!(inc.fill.is_some(), "el botón conserva su relleno");
77}