tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos
1//! Smoke tests del cómputo de filas y el routing de teclas. Sin
2//! backend gráfico — pruebas puras sobre `compute_rows` y `apply`.
3
4use llimphi_module_diff_viewer::{
5 self as diff, DiffAction, DiffKind, DiffMsg, DiffState,
6};
7
8#[test]
9fn diff_basico_inserts_y_deletes() {
10 let before = "a\nb\nc\n";
11 let after = "a\nB\nc\nd\n";
12 let (rows, stats) = diff::compute_rows(before, after);
13
14 // El diff esperado:
15 // = a / a
16 // - b
17 // + B
18 // = c / c
19 // + d
20 assert_eq!(stats.equals, 2);
21 assert_eq!(stats.deletes, 1);
22 assert_eq!(stats.inserts, 2);
23
24 assert_eq!(rows[0].kind, DiffKind::Equal);
25 assert_eq!(rows[0].left.as_ref().unwrap().text, "a");
26 assert_eq!(rows[0].right.as_ref().unwrap().text, "a");
27
28 // El primer cambio debe ser un Delete o Insert (similar agrupa);
29 // verificamos que B aparezca y b no.
30 let texts_left: Vec<&str> = rows
31 .iter()
32 .filter_map(|r| r.left.as_ref().map(|c| c.text.as_str()))
33 .collect();
34 let texts_right: Vec<&str> = rows
35 .iter()
36 .filter_map(|r| r.right.as_ref().map(|c| c.text.as_str()))
37 .collect();
38 assert!(texts_left.contains(&"b"));
39 assert!(texts_right.contains(&"B"));
40 assert!(texts_right.contains(&"d"));
41}
42
43#[test]
44fn numeros_de_linea_son_correctos() {
45 let before = "alpha\nbeta\ngamma\n";
46 let after = "alpha\nBETA\ngamma\ndelta\n";
47 let (rows, _) = diff::compute_rows(before, after);
48
49 // alpha en línea 1 de ambos.
50 let alpha_row = rows.iter().find(|r| {
51 r.left.as_ref().map(|c| c.text == "alpha").unwrap_or(false)
52 }).unwrap();
53 assert_eq!(alpha_row.left.as_ref().unwrap().line_no, 1);
54 assert_eq!(alpha_row.right.as_ref().unwrap().line_no, 1);
55
56 // beta (delete) en línea 2 izquierda.
57 let beta_row = rows.iter().find(|r| {
58 r.left.as_ref().map(|c| c.text == "beta").unwrap_or(false)
59 }).unwrap();
60 assert_eq!(beta_row.left.as_ref().unwrap().line_no, 2);
61 assert!(beta_row.right.is_none());
62
63 // delta (insert) en línea 4 derecha.
64 let delta_row = rows.iter().find(|r| {
65 r.right.as_ref().map(|c| c.text == "delta").unwrap_or(false)
66 }).unwrap();
67 assert_eq!(delta_row.right.as_ref().unwrap().line_no, 4);
68 assert!(delta_row.left.is_none());
69}
70
71#[test]
72fn textos_identicos_solo_equal() {
73 let text = "uno\ndos\ntres\n";
74 let (rows, stats) = diff::compute_rows(text, text);
75 assert_eq!(rows.len(), 3);
76 assert!(rows.iter().all(|r| r.kind == DiffKind::Equal));
77 assert_eq!(stats.inserts, 0);
78 assert_eq!(stats.deletes, 0);
79 assert_eq!(stats.equals, 3);
80}
81
82#[test]
83fn scroll_no_excede_los_limites() {
84 let before = (0..50).map(|i| i.to_string()).collect::<Vec<_>>().join("\n");
85 let after = before.clone(); // identical → 50 Equal rows
86 let mut state = DiffState::new("a", "b", &before, &after);
87 assert_eq!(state.scroll, 0);
88
89 // Scroll grande hacia abajo: tope = 50 - visible_rows.
90 diff::apply(&mut state, DiffMsg::Scroll(1000), 10);
91 assert_eq!(state.scroll, 40);
92
93 // Scroll arriba: tope mínimo 0.
94 diff::apply(&mut state, DiffMsg::Scroll(-1000), 10);
95 assert_eq!(state.scroll, 0);
96}
97
98#[test]
99fn next_hunk_salta_a_la_proxima_diferencia() {
100 // 20 líneas iguales + 2 cambios + 20 más. visible_rows=5 deja
101 // espacio real para scrollear.
102 let mut before = String::new();
103 let mut after = String::new();
104 for i in 0..20 {
105 before.push_str(&format!("eq{i}\n"));
106 after.push_str(&format!("eq{i}\n"));
107 }
108 before.push_str("DEL\n");
109 after.push_str("INS\n");
110 for i in 20..40 {
111 before.push_str(&format!("eq{i}\n"));
112 after.push_str(&format!("eq{i}\n"));
113 }
114 let mut state = DiffState::new("a", "b", &before, &after);
115 assert_eq!(state.scroll, 0);
116
117 diff::apply(&mut state, DiffMsg::NextHunk, 5);
118 assert!(state.scroll > 0, "scroll quedó en 0 — no saltó al hunk");
119 let row = &state.rows[state.scroll];
120 assert!(
121 !matches!(row.kind, DiffKind::Equal),
122 "esperaba aterrizar en un hunk, vi {:?}",
123 row.kind
124 );
125
126 // PrevHunk: vuelve al inicio (no hay hunk antes del primer cambio).
127 diff::apply(&mut state, DiffMsg::PrevHunk, 5);
128 // Puede quedarse en el mismo hunk si era el único accesible hacia
129 // atrás, o saltar más arriba. Lo único que verificamos es que no
130 // hubo panic ni scroll fuera de rango.
131 assert!(state.scroll < state.rows.len());
132}
133
134#[test]
135fn escape_cierra() {
136 let mut state = DiffState::new("a", "b", "x\n", "y\n");
137 let action = diff::apply(&mut state, DiffMsg::Close, 10);
138 assert_eq!(action, DiffAction::Close);
139}
140
141#[test]
142fn open_shortcut_es_ctrl_shift_d() {
143 use llimphi_compositor::{Key, KeyEvent, KeyState, Modifiers};
144 let mk = |ctrl: bool, shift: bool, c: &str| KeyEvent {
145 key: Key::Character(c.into()),
146 state: KeyState::Pressed,
147 text: Some(c.into()),
148 modifiers: Modifiers { ctrl, shift, ..Modifiers::default() },
149 repeat: false,
150 };
151 assert!(diff::open_shortcut(&mk(true, true, "d")));
152 assert!(diff::open_shortcut(&mk(true, true, "D")));
153 assert!(!diff::open_shortcut(&mk(true, false, "d")));
154 assert!(!diff::open_shortcut(&mk(false, true, "d")));
155}