feat: llimphi standalone — framework UI soberano extraído del monorepo
Motor gráfico Llimphi como workspace independiente: bucle Elm (input→update→view→layout→raster→present) sobre wgpu+vello+taffy+parley. Núcleo (hal/raster/layout/text/ui/theme/surface/motion/icons) + ~40 widgets + módulos, sin dependencias al resto del monorepo. cargo check --workspace pasa (64 crates). Puerta de entrada: cargo run -p llimphi-ui --example counter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
//! Smoke tests del fuzzy match y el routing de teclas. Sin backend
|
||||
//! gráfico — sólo `apply` + `refilter`.
|
||||
|
||||
use llimphi_module_symbol_outline::{
|
||||
self as outline, OutlineAction, OutlineMsg, OutlineState, SymbolItem,
|
||||
};
|
||||
use llimphi_ui::{Key, KeyEvent, KeyState, Modifiers};
|
||||
|
||||
fn seed() -> Vec<SymbolItem> {
|
||||
vec![
|
||||
SymbolItem {
|
||||
name: "Model".into(),
|
||||
kind: "struct".into(),
|
||||
line: 100,
|
||||
col: 0,
|
||||
container: None,
|
||||
depth: 0,
|
||||
},
|
||||
SymbolItem {
|
||||
name: "init".into(),
|
||||
kind: "fn".into(),
|
||||
line: 110,
|
||||
col: 4,
|
||||
container: Some("Model".into()),
|
||||
depth: 1,
|
||||
},
|
||||
SymbolItem {
|
||||
name: "update".into(),
|
||||
kind: "fn".into(),
|
||||
line: 200,
|
||||
col: 4,
|
||||
container: Some("Model".into()),
|
||||
depth: 1,
|
||||
},
|
||||
SymbolItem {
|
||||
name: "Renderer".into(),
|
||||
kind: "struct".into(),
|
||||
line: 300,
|
||||
col: 0,
|
||||
container: None,
|
||||
depth: 0,
|
||||
},
|
||||
SymbolItem {
|
||||
name: "draw".into(),
|
||||
kind: "fn".into(),
|
||||
line: 310,
|
||||
col: 4,
|
||||
container: Some("Renderer".into()),
|
||||
depth: 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn key_char(c: &str) -> KeyEvent {
|
||||
KeyEvent {
|
||||
key: Key::Character(c.into()),
|
||||
state: KeyState::Pressed,
|
||||
text: Some(c.into()),
|
||||
modifiers: Modifiers::default(),
|
||||
repeat: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn estado_vacio_lista_todos_los_simbolos() {
|
||||
let items = seed();
|
||||
let s = OutlineState::new(&items);
|
||||
assert_eq!(s.results.len(), items.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzy_match_filtra_por_nombre_de_clase_contenedora() {
|
||||
// Tipear "render" debería traer `draw` (su container es "Renderer")
|
||||
// gracias a que refilter incluye container en la haystack.
|
||||
let items = seed();
|
||||
let mut s = OutlineState::new(&items);
|
||||
for ch in ["r", "e", "n", "d", "e", "r"] {
|
||||
outline::apply(&mut s, OutlineMsg::KeyInput(key_char(ch)), &items);
|
||||
}
|
||||
let names: Vec<&str> = s.results.iter().map(|&i| items[i].name.as_str()).collect();
|
||||
assert!(
|
||||
names.contains(&"draw") || names.contains(&"Renderer"),
|
||||
"esperaba draw o Renderer en {names:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_emite_goto_con_line_col_del_item_seleccionado() {
|
||||
let items = seed();
|
||||
let mut s = OutlineState::new(&items);
|
||||
// Filtrar "update".
|
||||
for ch in ["u", "p", "d", "a", "t", "e"] {
|
||||
outline::apply(&mut s, OutlineMsg::KeyInput(key_char(ch)), &items);
|
||||
}
|
||||
let action = outline::apply(&mut s, OutlineMsg::Apply, &items);
|
||||
assert_eq!(action, OutlineAction::GoTo { line: 200, col: 4 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nav_wrap_around() {
|
||||
let items = seed();
|
||||
let mut s = OutlineState::new(&items);
|
||||
assert_eq!(s.selected, 0);
|
||||
outline::apply(&mut s, OutlineMsg::Nav(-1), &items);
|
||||
assert_eq!(s.selected, items.len() - 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_shortcut_es_ctrl_shift_o() {
|
||||
let mk = |ctrl: bool, shift: bool, c: &str| KeyEvent {
|
||||
key: Key::Character(c.into()),
|
||||
state: KeyState::Pressed,
|
||||
text: Some(c.into()),
|
||||
modifiers: Modifiers { ctrl, shift, ..Modifiers::default() },
|
||||
repeat: false,
|
||||
};
|
||||
assert!(outline::open_shortcut(&mk(true, true, "o")));
|
||||
assert!(outline::open_shortcut(&mk(true, true, "O")));
|
||||
assert!(!outline::open_shortcut(&mk(true, false, "o")));
|
||||
assert!(!outline::open_shortcut(&mk(false, true, "o")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn items_vacios_no_paniquean() {
|
||||
let items: Vec<SymbolItem> = Vec::new();
|
||||
let mut s = OutlineState::new(&items);
|
||||
assert!(s.results.is_empty());
|
||||
let action = outline::apply(&mut s, OutlineMsg::Apply, &items);
|
||||
assert_eq!(action, OutlineAction::None);
|
||||
}
|
||||
Reference in New Issue
Block a user