test(lsp): cover documented completions

Add protocol and helper coverage for documented hemplate completion items, including data-hemx-root.

req: diagnostics/006
This commit is contained in:
slhx agent
2026-06-25 23:29:33 +02:00
parent 6cf800cac6
commit baf4b1529c
+80
View File
@@ -394,6 +394,11 @@ fn hemplate_completion_items(
"{+= trusted_html =+}",
"Trusted/rendered HTML expression; prefer escaped `{+ expr +}` for user content.",
),
completion_item(
"data-hemx-root",
"data-hemx-root=\"app\"",
"Generated hemx root for delegated runtime behavior.",
),
completion_item(
"data-hemx-slot",
"data-hemx-slot=\"content\"",
@@ -746,6 +751,81 @@ mod tests {
assert!(!outside_hover.to_string().contains("exercise.name: String"));
}
#[test]
fn completion_items_cover_documented_hemplate_surface() {
// req: diagnostics/006
let text = r#"<main data-hemx-root="app"><template h-for="item in &self.items" h-key="item.id"><p +class="self.class_name">{+ self.title +}{+= trusted =+}</p></template></main>"#;
let items = hemplate_completion_items("file:///tmp/app.heml", text, Some((0, 6)));
let labels = items
.iter()
.filter_map(|item| item["label"].as_str())
.collect::<std::collections::BTreeSet<_>>();
for label in [
"h-for",
"h-key",
"h-if",
"h-match",
"h-case",
"+attr",
"{+ expr +}",
"{+= expr =+}",
"data-hemx-root",
"data-hemx-slot",
"data-hemx-form",
"data-hemx-handle",
] {
assert!(labels.contains(label), "missing completion item `{label}`");
}
assert!(items.iter().all(|item| item["documentation"]["value"]
.as_str()
.unwrap_or_default()
.contains("docs/hemplate-syntax.md")));
}
#[test]
fn lsp_completion_returns_documented_items() {
// req: diagnostics/006
let text = r#"<main data-hemx-root="todos"><div data-hemx-slot="content">{+ self.title +}</div></main>"#;
let uri = "file:///tmp/completion.heml";
let input = [
lsp_message(serde_json::json!({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}})),
lsp_message(serde_json::json!({
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": {"textDocument": {"uri": uri, "languageId": "heml", "version": 1, "text": text}}
})),
lsp_message(serde_json::json!({
"jsonrpc": "2.0",
"id": 2,
"method": "textDocument/completion",
"params": {"textDocument": {"uri": uri}, "position": {"line": 0, "character": 6}}
})),
lsp_message(serde_json::json!({"jsonrpc": "2.0", "method": "exit"})),
]
.join("");
let mut reader = std::io::BufReader::new(input.as_bytes());
let mut output = Vec::new();
serve_lsp(&mut reader, &mut output).expect("serve LSP");
let messages = lsp_messages(&output);
let completion = messages
.iter()
.find(|message| message["id"] == 2)
.expect("completion response");
let completion_items = completion["result"]
.as_array()
.or_else(|| completion["result"]["items"].as_array())
.expect("completion items");
let labels = completion_items
.iter()
.filter_map(|item| item["label"].as_str())
.collect::<std::collections::BTreeSet<_>>();
assert!(labels.contains("h-if"));
assert!(labels.contains("data-hemx-root"));
assert!(labels.contains("data-hemx-slot"));
assert!(labels.contains("{+ expr +}"));
}
#[test]
fn workout_template_fields_are_available_from_repo_facts() {
// req: diagnostics/006