test(build): close placement diagnostic mutants

Assert exact InvalidData diagnostics for navigation, boost, client-module, and SSE misplacement and prove each accepted placement through AppBuilder::run.

req: page_swap/001

req: page_swap/007

req: push/006

req: diagnostics/002

req: test/021
This commit is contained in:
slhx agent
2026-07-17 06:07:04 +02:00
parent a322e519d3
commit 4e7d772311
2 changed files with 51 additions and 23 deletions
+50 -22
View File
@@ -3920,73 +3920,101 @@ fn main() {{
}
#[test]
fn rejects_invalid_hemx_attr_placement() {
fn placement_contracts_fail_closed_with_exact_diagnostics() {
// req: page_swap/001 req: page_swap/007 req: push/006 req: diagnostics/002
for (case, template, attr, guidance) in [
for (case, template, attr, expectation) in [
(
"nav-button",
r#"<button data-hemx-nav="" data-hemx-handle="go">Go</button>"#,
"data-hemx-nav",
"<a href=...>",
"expected a real `<a href=...>` link so navigation works without JavaScript",
),
(
"nav-missing-href",
r#"<a data-hemx-nav="">Docs</a>"#,
"data-hemx-nav",
"<a href=...>",
"expected a real `<a href=...>` link so navigation works without JavaScript",
),
(
"nav-empty-href",
r#"<a href="" data-hemx-nav="">Docs</a>"#,
r#"<a href=" " data-hemx-nav="">Docs</a>"#,
"data-hemx-nav",
"<a href=...>",
"expected a real `<a href=...>` link so navigation works without JavaScript",
),
(
"boost-anchor",
r#"<a href="/docs" data-hemx-boost="">Docs</a>"#,
"data-hemx-boost",
"data-hemx-nav",
"expected a container around descendant links/forms; use `data-hemx-nav` on anchors or `data-hemx-handle` on forms",
),
(
"boost-form",
r#"<form data-hemx-boost=""><input name="q"></form>"#,
"data-hemx-boost",
"data-hemx-handle",
"expected a container around descendant links/forms; use `data-hemx-nav` on anchors or `data-hemx-handle` on forms",
),
(
"client-module-child",
r#"<section data-hemx-root="app"><div data-hemx-client-module="/app.js"></div></section>"#,
"data-hemx-client-module",
"expected placement on the same element as `data-hemx-root`",
),
(
"sse-child",
r#"<section data-hemx-root="feed"><div data-hemx-sse="/events"></div></section>"#,
"data-hemx-sse",
"data-hemx-root",
"expected placement on the same element as `data-hemx-root`",
),
] {
let base = test_dir(&format!("hemx-build-invalid-placement-{case}-test"));
let templates = base.join("templates");
let out = base.join("out");
let template_path = templates.join("placement.heml");
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&templates).unwrap();
std::fs::write(templates.join("placement.heml"), template).unwrap();
std::fs::write(&template_path, template).unwrap();
let err = app()
let error = app()
.template_dir(&templates)
.out_dir(&out)
.run()
.unwrap_err();
assert!(
err.to_string().contains(attr),
"missing attr in diagnostic: {err}"
);
assert!(
err.to_string().contains("invalid"),
"missing invalid-placement diagnostic: {err}"
);
assert!(
err.to_string().contains(guidance),
"missing placement guidance {guidance:?}: {err}"
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
assert_eq!(
error.to_string(),
format!(
"{}: invalid {attr} placement; {expectation}",
template_path.display()
)
);
let _ = std::fs::remove_dir_all(&base);
}
for (case, template) in [
("nav-link", r#"<a href="/docs" data-hemx-nav="">Docs</a>"#),
(
"boost-container",
r#"<nav data-hemx-boost=""><a href="/docs">Docs</a></nav>"#,
),
(
"client-module-root",
r#"<main data-hemx-root="app" data-hemx-client-module="/app.js"></main>"#,
),
(
"sse-root",
r#"<main data-hemx-root="feed" data-hemx-sse="/events"></main>"#,
),
] {
let base = test_dir(&format!("hemx-build-valid-placement-{case}-test"));
let templates = base.join("templates");
let out = base.join("out");
let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&templates).unwrap();
std::fs::write(templates.join("placement.heml"), template).unwrap();
app().template_dir(&templates).out_dir(&out).run().unwrap();
let _ = std::fs::remove_dir_all(base);
}
}
#[test]