tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definiciones

⎇ main modules/shuma-term/tests/smoke.rs Rust · 157 líneas · 5.2 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos b3f12e136 · Sergio ·

1//! Smoke test del terminal: spawnea un shell, le tipea `echo hola`,
2//! drena hasta ver el output, y verifica que el contenido del screen
3//! contenga "hola". Cierre con SIGTERM se valida por el Drop.
4//!
5//! Requiere `/bin/sh` y un sistema Linux real (no corre en sandbox
6//! puro). Es razonable porque shuma-exec ya lo asume.
7
8use std::time::{Duration, Instant};
9
10use llimphi_module_shuma_term::{self as term, ShumaTermAction, ShumaTermMsg};
11
12#[test]
13fn echo_a_traves_del_pty_aparece_en_el_screen() {
14    let mut state = term::spawn_with(
15        "/tmp".to_string(),
16        "/bin/sh".to_string(),
17        Vec::new(),
18        80,
19        24,
20    );
21
22    // El shell escribe su prompt al arrancar; lo drenamos sin asumir
23    // su contenido (cambia por distro).
24    spin_drain(&mut state, Duration::from_millis(200));
25
26    // Tipeamos el comando. Sin Llimphi alrededor llamamos a write_input
27    // directamente — el módulo permite hacerlo via KeyInput, pero
28    // construir KeyEvents aquí es ruido para este test.
29    write_raw(&mut state, b"echo hola_del_test\n");
30
31    // Esperamos hasta 2s a que el output llegue al screen.
32    let deadline = Instant::now() + Duration::from_secs(2);
33    let mut visto = false;
34    while Instant::now() < deadline {
35        spin_drain(&mut state, Duration::from_millis(50));
36        if state.screen_contents().contains("hola_del_test") {
37            visto = true;
38            break;
39        }
40    }
41    assert!(
42        visto,
43        "esperaba ver 'hola_del_test' en el screen, contenido actual:\n{}",
44        state.screen_contents()
45    );
46}
47
48#[test]
49fn ctrl_shift_w_emite_action_close_sin_pasar_al_pty() {
50    use llimphi_compositor::{Key, KeyEvent, KeyState, Modifiers};
51
52    let mut state = term::spawn_with(
53        "/tmp".to_string(),
54        "/bin/sh".to_string(),
55        Vec::new(),
56        80,
57        24,
58    );
59    spin_drain(&mut state, Duration::from_millis(100));
60
61    let ev = KeyEvent {
62        key: Key::Character("w".into()),
63        state: KeyState::Pressed,
64        text: Some("w".into()),
65        modifiers: Modifiers { ctrl: true, shift: true, ..Modifiers::default() },
66        repeat: false,
67    };
68    let action = term::apply(&mut state, ShumaTermMsg::KeyInput(ev));
69    assert_eq!(action, ShumaTermAction::Close);
70}
71
72#[test]
73fn key_to_bytes_mapea_los_casos_canonicos() {
74    use llimphi_compositor::{Key, KeyEvent, KeyState, Modifiers, NamedKey};
75
76    let mk = |key: Key, mods: Modifiers, text: Option<&str>| KeyEvent {
77        key,
78        state: KeyState::Pressed,
79        text: text.map(|s| s.to_string()),
80        modifiers: mods,
81        repeat: false,
82    };
83
84    // Enter → CR (no LF — el driver del PTY lo expande).
85    assert_eq!(
86        term::key_to_bytes(&mk(Key::Named(NamedKey::Enter), Modifiers::default(), None)),
87        b"\r"
88    );
89    // Backspace → DEL.
90    assert_eq!(
91        term::key_to_bytes(&mk(
92            Key::Named(NamedKey::Backspace),
93            Modifiers::default(),
94            None
95        )),
96        vec![0x7f]
97    );
98    // ArrowUp → CSI A.
99    assert_eq!(
100        term::key_to_bytes(&mk(
101            Key::Named(NamedKey::ArrowUp),
102            Modifiers::default(),
103            None
104        )),
105        b"\x1b[A"
106    );
107    // Ctrl+C → 0x03.
108    let ctrl = Modifiers { ctrl: true, ..Modifiers::default() };
109    assert_eq!(
110        term::key_to_bytes(&mk(Key::Character("c".into()), ctrl, Some("c"))),
111        vec![0x03]
112    );
113    // Texto plano (con shift aplicado por el backend) → ese mismo texto.
114    assert_eq!(
115        term::key_to_bytes(&mk(Key::Character("A".into()), Modifiers::default(), Some("A"))),
116        b"A"
117    );
118    // Alt+x → ESC + x.
119    let alt = Modifiers { alt: true, ..Modifiers::default() };
120    assert_eq!(
121        term::key_to_bytes(&mk(Key::Character("x".into()), alt, Some("x"))),
122        vec![0x1b, b'x']
123    );
124}
125
126// ---------------------------------------------------------------------
127// helpers
128// ---------------------------------------------------------------------
129
130/// Pequeño polling: dispara Tick varias veces durante `total` para que
131/// el módulo drene los bytes que el reader thread haya emitido.
132fn spin_drain(state: &mut llimphi_module_shuma_term::ShumaTermState, total: Duration) {
133    let deadline = Instant::now() + total;
134    while Instant::now() < deadline {
135        term::apply(state, ShumaTermMsg::Tick);
136        std::thread::sleep(Duration::from_millis(10));
137    }
138}
139
140/// Atajo: enviar bytes crudos al PTY sin construir un KeyEvent. Usa la
141/// API pública via un truco — convertimos a un KeyEvent "texto" para
142/// evitar exponer write_input crudo en el contrato.
143fn write_raw(state: &mut llimphi_module_shuma_term::ShumaTermState, bytes: &[u8]) {
144    use llimphi_compositor::{Key, KeyEvent, KeyState, Modifiers};
145    // Texto entero (incluyendo el LF) en un solo KeyInput. apply() lo
146    // copia tal cual al PTY via la rama `text`.
147    let s = std::str::from_utf8(bytes).expect("test usa ascii");
148    let ev = KeyEvent {
149        // Key::Character vacío para que no entremos por la rama ctrl/alt.
150        key: Key::Character("".into()),
151        state: KeyState::Pressed,
152        text: Some(s.to_string()),
153        modifiers: Modifiers::default(),
154        repeat: false,
155    };
156    term::apply(state, ShumaTermMsg::KeyInput(ev));
157}