fix(build): explain missing keyed loop facts
Improve the unkeyed h-for diagnostic so authors see the exact loop, stable h-key shape, generated helper consequence, and why dynamic child data-key is not enough. req: scope/001 req: diagnostics/002
This commit is contained in:
+19
-6
@@ -1543,11 +1543,16 @@ fn reject_unkeyed_loop(
|
||||
let Some(current) = surface.scopes.get(scope.0 as usize) else {
|
||||
return Ok(());
|
||||
};
|
||||
if matches!(current.kind, ScopeKind::For { key_expr: None, .. }) {
|
||||
if let ScopeKind::For {
|
||||
pattern,
|
||||
expr,
|
||||
key_expr: None,
|
||||
} = ¤t.kind
|
||||
{
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"{}: data-hemx-{kind}=\"{name}\" is inside an h-for without h-key; add a stable h-key=\"item.id\" to the loop that owns this generated target",
|
||||
"{}: data-hemx-{kind}=\"{name}\" is inside h-for=\"{pattern} in {expr}\" without h-key; add a stable h-key=\"{pattern}.id\" to that h-for so generated keyed helpers such as ui::{name}.replace(row) can target this partial. Dynamic +data-key on the child is rendered HTML, not the template fact hemx uses for generated targets.",
|
||||
path.display()
|
||||
),
|
||||
));
|
||||
@@ -2052,22 +2057,25 @@ fn main() {{
|
||||
|
||||
#[test]
|
||||
fn rejects_hemx_resources_inside_unkeyed_for() {
|
||||
// req: scope/001
|
||||
for (case, template, resource) in [
|
||||
// req: scope/001 req: diagnostics/002
|
||||
for (case, template, resource, helper) in [
|
||||
(
|
||||
"slot",
|
||||
r#"<template h-for="todo in &self.todos"><li data-hemx-slot="todo_row">{+ todo.title +}</li></template>"#,
|
||||
"data-hemx-slot=\"todo_row\"",
|
||||
"ui::todo_row",
|
||||
),
|
||||
(
|
||||
"slot_data_key",
|
||||
r#"<template h-for="todo in &self.todos"><li data-hemx-slot="todo_row" +data-key="todo.id">{+ todo.title +}</li></template>"#,
|
||||
"data-hemx-slot=\"todo_row\"",
|
||||
"ui::todo_row",
|
||||
),
|
||||
(
|
||||
"handle",
|
||||
r#"<template h-for="todo in &self.todos"><button data-hemx-handle="delete">Delete</button></template>"#,
|
||||
"data-hemx-handle=\"delete\"",
|
||||
"ui::delete",
|
||||
),
|
||||
] {
|
||||
let base = test_dir(&format!("hemx-build-unkeyed-for-{case}-test"));
|
||||
@@ -2082,8 +2090,13 @@ fn main() {{
|
||||
.out_dir(&out)
|
||||
.run()
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("inside an h-for without h-key"));
|
||||
assert!(err.to_string().contains(resource));
|
||||
let err = err.to_string();
|
||||
assert!(err.contains("inside h-for=\"todo in &self.todos\" without h-key"));
|
||||
assert!(err.contains(resource));
|
||||
assert!(err.contains("h-key=\"todo.id\""));
|
||||
assert!(err.contains("generated keyed helpers"));
|
||||
assert!(err.contains(helper));
|
||||
assert!(err.contains("Dynamic +data-key on the child"));
|
||||
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user