gioser

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definitions

⎇ 14d76bd4af13b219f0b162a54f09e784dea9aa8d widgets/text-editor-lsp/src/tests.rs Rust · 380 lines · 12.2 KB History Who touched what raw

feat: llimphi standalone — framework UI soberano extraído del monorepo e65e9cc62 · Sergio ·

1use super::*;
2
3#[test]
4fn noop_devuelve_vacio() {
5    let c = NoopLspClient;
6    assert!(c.diagnostics(&PathBuf::from("x")).is_empty());
7}
8
9#[test]
10fn noop_no_panic_en_eventos() {
11    let mut c = NoopLspClient;
12    c.did_open(&PathBuf::from("x"), "rust", "fn main() {}");
13    c.did_change(&PathBuf::from("x"), "fn main() { 1 }");
14    c.did_close(&PathBuf::from("x"));
15}
16
17#[test]
18fn parse_diagnostic_minimo() {
19    let json = serde_json::json!({
20        "range": {
21            "start": { "line": 3, "character": 5 },
22            "end":   { "line": 3, "character": 12 }
23        },
24        "severity": 1,
25        "message": "no es así",
26        "source": "rustc"
27    });
28    let d = parse_lsp_diagnostic(&json).unwrap();
29    assert_eq!(d.range.start, Pos::new(3, 5));
30    assert_eq!(d.range.end, Pos::new(3, 12));
31    assert_eq!(d.severity, Severity::Error);
32    assert_eq!(d.message, "no es así");
33    assert_eq!(d.source.as_deref(), Some("rustc"));
34}
35
36#[test]
37fn parse_diagnostic_sin_severidad_es_info() {
38    let json = serde_json::json!({
39        "range": {
40            "start": { "line": 0, "character": 0 },
41            "end":   { "line": 0, "character": 1 }
42        },
43        "message": "x"
44    });
45    let d = parse_lsp_diagnostic(&json).unwrap();
46    assert_eq!(d.severity, Severity::Information);
47}
48
49#[test]
50fn parse_completion_minimo() {
51    let v = serde_json::json!({
52        "label": "to_string",
53        "insertText": "to_string()",
54        "kind": 2,
55        "detail": "fn(&self) -> String"
56    });
57    let c = parse_completion(&v).unwrap();
58    assert_eq!(c.label, "to_string");
59    assert_eq!(c.insert_text.as_deref(), Some("to_string()"));
60    assert_eq!(c.kind.as_deref(), Some("Method"));
61    assert_eq!(c.detail.as_deref(), Some("fn(&self) -> String"));
62}
63
64#[test]
65fn parse_hover_string_simple() {
66    let v = serde_json::json!({ "contents": "hola" });
67    let h = parse_hover(&v).unwrap();
68    assert_eq!(h.contents, "hola");
69}
70
71#[test]
72fn parse_hover_marked_object() {
73    let v = serde_json::json!({
74        "contents": { "kind": "markdown", "value": "**fn**(x: i32) -> i32" }
75    });
76    let h = parse_hover(&v).unwrap();
77    assert_eq!(h.contents, "**fn**(x: i32) -> i32");
78}
79
80#[test]
81fn parse_hover_array_concatena() {
82    let v = serde_json::json!({
83        "contents": ["primero", { "value": "segundo" }, ""]
84    });
85    let h = parse_hover(&v).unwrap();
86    assert_eq!(h.contents, "primero\nsegundo");
87}
88
89#[test]
90fn parse_hover_vacio_devuelve_none() {
91    let v = serde_json::json!({ "contents": "" });
92    assert!(parse_hover(&v).is_none());
93}
94
95#[test]
96fn parse_completion_sin_insert_text_usa_label() {
97    let v = serde_json::json!({ "label": "main" });
98    let c = parse_completion(&v).unwrap();
99    assert_eq!(c.text_to_insert(), "main");
100}
101
102fn make_state() -> SharedState {
103    Arc::new(Mutex::new(SharedInner::default()))
104}
105
106#[test]
107fn handle_rename_changes_map() {
108    let s = make_state();
109    let json = serde_json::json!({
110        "id": 1,
111        "result": {
112            "changes": {
113                "file:///tmp/a.rs": [
114                    { "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 3 } }, "newText": "bar" }
115                ],
116                "file:///tmp/b.rs": [
117                    { "range": { "start": { "line": 5, "character": 4 }, "end": { "line": 5, "character": 7 } }, "newText": "bar" },
118                    { "range": { "start": { "line": 10, "character": 0 }, "end": { "line": 10, "character": 3 } }, "newText": "bar" }
119                ]
120            }
121        }
122    });
123    handle_rename_response(&json, &s);
124    let we = s.lock().unwrap().workspace_edit.clone();
125    assert_eq!(we.len(), 2);
126    assert_eq!(we.get(&PathBuf::from("/tmp/a.rs")).unwrap().len(), 1);
127    assert_eq!(we.get(&PathBuf::from("/tmp/b.rs")).unwrap().len(), 2);
128}
129
130#[test]
131fn handle_rename_document_changes() {
132    let s = make_state();
133    let json = serde_json::json!({
134        "id": 1,
135        "result": {
136            "documentChanges": [
137                {
138                    "textDocument": { "uri": "file:///tmp/x.rs", "version": 2 },
139                    "edits": [
140                        { "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 3 } }, "newText": "foo" }
141                    ]
142                }
143            ]
144        }
145    });
146    handle_rename_response(&json, &s);
147    let we = s.lock().unwrap().workspace_edit.clone();
148    assert_eq!(we.len(), 1);
149    assert_eq!(we.get(&PathBuf::from("/tmp/x.rs")).unwrap().len(), 1);
150}
151
152#[test]
153fn handle_document_symbols_jerarquico() {
154    let s = make_state();
155    // Estructura: struct Foo { fn bar(), fn baz() } + fn top()
156    let json = serde_json::json!({
157        "id": 1,
158        "result": [
159            {
160                "name": "Foo",
161                "kind": 23, // struct
162                "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 10, "character": 1 } },
163                "selectionRange": { "start": { "line": 0, "character": 7 }, "end": { "line": 0, "character": 10 } },
164                "children": [
165                    {
166                        "name": "bar",
167                        "kind": 6, // method
168                        "range": { "start": { "line": 2, "character": 4 }, "end": { "line": 4, "character": 5 } },
169                        "selectionRange": { "start": { "line": 2, "character": 7 }, "end": { "line": 2, "character": 10 } }
170                    },
171                    {
172                        "name": "baz",
173                        "kind": 6,
174                        "range": { "start": { "line": 6, "character": 4 }, "end": { "line": 8, "character": 5 } },
175                        "selectionRange": { "start": { "line": 6, "character": 7 }, "end": { "line": 6, "character": 10 } }
176                    }
177                ]
178            },
179            {
180                "name": "top",
181                "kind": 12, // function
182                "range": { "start": { "line": 12, "character": 0 }, "end": { "line": 14, "character": 1 } },
183                "selectionRange": { "start": { "line": 12, "character": 3 }, "end": { "line": 12, "character": 6 } }
184            }
185        ]
186    });
187    handle_document_symbols_response(&json, &s);
188    let syms = s.lock().unwrap().document_symbols.clone();
189    assert_eq!(syms.len(), 4, "esperaba 4 entradas flattening");
190
191    assert_eq!(syms[0].name, "Foo");
192    assert_eq!(syms[0].kind, "struct");
193    assert_eq!(syms[0].line, 0);
194    assert_eq!(syms[0].depth, 0);
195    assert_eq!(syms[0].container, None);
196
197    assert_eq!(syms[1].name, "bar");
198    assert_eq!(syms[1].kind, "method");
199    assert_eq!(syms[1].line, 2);
200    assert_eq!(syms[1].depth, 1);
201    assert_eq!(syms[1].container.as_deref(), Some("Foo"));
202
203    assert_eq!(syms[2].name, "baz");
204    assert_eq!(syms[2].depth, 1);
205    assert_eq!(syms[2].container.as_deref(), Some("Foo"));
206
207    assert_eq!(syms[3].name, "top");
208    assert_eq!(syms[3].kind, "fn");
209    assert_eq!(syms[3].depth, 0);
210}
211
212#[test]
213fn handle_document_symbols_legacy_symbolinformation() {
214    let s = make_state();
215    // Formato viejo: SymbolInformation[] (plano + location).
216    let json = serde_json::json!({
217        "id": 1,
218        "result": [
219            {
220                "name": "main",
221                "kind": 12,
222                "location": {
223                    "uri": "file:///tmp/x.rs",
224                    "range": { "start": { "line": 0, "character": 3 }, "end": { "line": 0, "character": 7 } }
225                }
226            },
227            {
228                "name": "helper",
229                "kind": 12,
230                "containerName": "main",
231                "location": {
232                    "uri": "file:///tmp/x.rs",
233                    "range": { "start": { "line": 5, "character": 3 }, "end": { "line": 5, "character": 9 } }
234                }
235            }
236        ]
237    });
238    handle_document_symbols_response(&json, &s);
239    let syms = s.lock().unwrap().document_symbols.clone();
240    assert_eq!(syms.len(), 2);
241    assert_eq!(syms[1].name, "helper");
242    assert_eq!(syms[1].container.as_deref(), Some("main"));
243}
244
245#[test]
246fn handle_references_response_array() {
247    let s = make_state();
248    let json = serde_json::json!({
249        "id": 1,
250        "result": [
251            { "uri": "file:///tmp/a.rs", "range": { "start": { "line": 1, "character": 2 }, "end": { "line": 1, "character": 5 } } },
252            { "uri": "file:///tmp/b.rs", "range": { "start": { "line": 10, "character": 0 }, "end": { "line": 10, "character": 3 } } }
253        ]
254    });
255    handle_references_response(&json, &s);
256    let refs = s.lock().unwrap().references.clone();
257    assert_eq!(refs.len(), 2);
258    assert_eq!(refs[0].path, PathBuf::from("/tmp/a.rs"));
259    assert_eq!(refs[0].line, 1);
260    assert_eq!(refs[1].path, PathBuf::from("/tmp/b.rs"));
261    assert_eq!(refs[1].line, 10);
262}
263
264#[test]
265fn parse_signature_help_basic() {
266    let result = serde_json::json!({
267        "signatures": [{
268            "label": "fn foo(x: i32, y: String) -> u64",
269            "parameters": [
270                { "label": "x: i32" },
271                { "label": "y: String" }
272            ]
273        }],
274        "activeSignature": 0,
275        "activeParameter": 1
276    });
277    let info = parse_signature_help(&result).unwrap();
278    assert_eq!(info.label, "fn foo(x: i32, y: String) -> u64");
279    assert_eq!(info.active_param, 1);
280    assert_eq!(info.param_labels, vec!["x: i32", "y: String"]);
281}
282
283#[test]
284fn parse_signature_help_offset_label() {
285    // Label como [start, end] dentro del label de la firma.
286    let result = serde_json::json!({
287        "signatures": [{
288            "label": "foo(x, y)",
289            "parameters": [
290                { "label": [4, 5] },
291                { "label": [7, 8] }
292            ]
293        }]
294    });
295    let info = parse_signature_help(&result).unwrap();
296    assert_eq!(info.param_labels, vec!["x", "y"]);
297}
298
299#[test]
300fn parse_text_edit_basic() {
301    let v = serde_json::json!({
302        "range": {
303            "start": { "line": 1, "character": 0 },
304            "end":   { "line": 1, "character": 4 }
305        },
306        "newText": "let "
307    });
308    let e = parse_text_edit(&v).unwrap();
309    assert_eq!(e.start_line, 1);
310    assert_eq!(e.start_col, 0);
311    assert_eq!(e.end_line, 1);
312    assert_eq!(e.end_col, 4);
313    assert_eq!(e.new_text, "let ");
314}
315
316#[test]
317fn handle_text_edits_response_array() {
318    let s = make_state();
319    let json = serde_json::json!({
320        "id": 1,
321        "result": [
322            { "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 0, "character": 3 } }, "newText": "fn " },
323            { "range": { "start": { "line": 1, "character": 4 }, "end": { "line": 1, "character": 5 } }, "newText": "" }
324        ]
325    });
326    handle_text_edits_response(&json, &s);
327    let edits = s.lock().unwrap().text_edits.clone();
328    assert_eq!(edits.len(), 2);
329}
330
331#[test]
332fn handle_definition_location_simple() {
333    let s = make_state();
334    let json = serde_json::json!({
335        "id": 1,
336        "result": {
337            "uri": "file:///tmp/x.rs",
338            "range": {
339                "start": { "line": 10, "character": 4 },
340                "end":   { "line": 10, "character": 9 }
341            }
342        }
343    });
344    handle_definition_response(&json, &s);
345    let d = s.lock().unwrap().definition.clone().unwrap();
346    assert_eq!(d.path, PathBuf::from("/tmp/x.rs"));
347    assert_eq!(d.line, 10);
348    assert_eq!(d.col, 4);
349}
350
351#[test]
352fn handle_definition_location_link_array() {
353    let s = make_state();
354    let json = serde_json::json!({
355        "id": 1,
356        "result": [
357            {
358                "targetUri": "file:///tmp/y.rs",
359                "targetSelectionRange": {
360                    "start": { "line": 0, "character": 7 },
361                    "end":   { "line": 0, "character": 12 }
362                }
363            }
364        ]
365    });
366    handle_definition_response(&json, &s);
367    let d = s.lock().unwrap().definition.clone().unwrap();
368    assert_eq!(d.path, PathBuf::from("/tmp/y.rs"));
369    assert_eq!(d.line, 0);
370    assert_eq!(d.col, 7);
371}
372
373#[test]
374fn rust_analyzer_client_sin_binary_no_panic() {
375    // Si rust-analyzer no está instalado, el spawn falla en silencio
376    // y el client queda en modo no-op (state vacío).
377    let c = RustAnalyzerClient::with_command(PathBuf::from("/tmp"), "rust-analyzer-missing-99999");
378    // diagnostics() siempre devuelve vacío hasta que el server responde.
379    assert!(c.diagnostics(&PathBuf::from("/tmp/x")).is_empty());
380}