test(derive): close sixth mutation shard

Exercise malformed parameter manifests, handler patterns, app diagnostics, and registration guards; delete an unreachable empty-handler branch and narrowly classify quote-owned parse invariants.

req: derive_app/001

req: component/003

req: derive_handler/003

req: test/020

req: test/021

req: test/022

req: test/023
This commit is contained in:
slhx agent
2026-07-17 14:40:56 +02:00
parent 31c52698e3
commit d1af300bd4
3 changed files with 94 additions and 6 deletions
+86 -5
View File
@@ -765,9 +765,6 @@ fn add_component_register_helper(mut module: ItemMod, component: &str) -> ItemMo
let Some(state_ty) = component_state_type(items) else {
return module;
};
if handlers.is_empty() {
return module;
}
let calls = handlers
.iter()
.map(|handler| component_registration_call(handler, &component_ident));
@@ -1021,8 +1018,8 @@ mod tests {
add_app_registry_helper, add_component_register_helper, client_handler_has_inputs,
component_handler_names, expand_handler_function, form_contract_errors, form_decode_fields,
form_fields, form_impl_generics, form_model_type, form_resource_id, generic_inner_type,
handle_params, handle_requires_form, handler_form_model_type, handler_placement,
handler_syms_path, has_form_param, has_non_unit_return, is_type_named,
handle_params, handle_requires_form, handler_arg_names, handler_form_model_type,
handler_placement, handler_syms_path, has_form_param, has_non_unit_return, is_type_named,
join_contract_errors, missing_component_handlers, missing_form_generated_files_message,
missing_handle_params, missing_handler_generated_files_message, parser_type,
returns_result, rust_ident, syms_contains_handle, HandlerPlacement,
@@ -1461,6 +1458,14 @@ mod tests {
assert_eq!(missing_handle_params(&path, "show", &missing), vec!["mode"]);
assert!(missing_handle_params(&path, "unknown", &missing).is_empty());
std::fs::write(&path, "handle_param\nhandle_param\tshow\tmode\n").unwrap();
assert_eq!(handle_params(&path, "show"), vec!["mode"]);
let patterns: ItemFn = parse_quote! {
fn patterns(self, named: String, (left, right): (u8, u8), _: bool) {}
};
assert_eq!(handler_arg_names(&patterns), vec!["named"]);
let _ = std::fs::remove_file(path);
}
@@ -1518,6 +1523,80 @@ mod tests {
);
assert!(generated.contains("hemx_axum :: State"), "{generated}");
assert!(generated.contains("state . clone"), "{generated}");
let no_components: ItemFn = parse_quote!(
fn registry(state: App) {}
);
assert_eq!(
add_app_registry_helper(no_components, vec![]).err().unwrap(),
"#[hemx::app] requires component registry module(s), for example #[hemx::app(todo_handlers, auth_handlers)]"
);
let unnamed_state: ItemFn = parse_quote!(
fn registry((state,): (App,)) {}
);
assert_eq!(
add_app_registry_helper(unnamed_state, vec![parse_quote!(handlers)])
.err()
.unwrap(),
"#[hemx::app] must be used on a registry function with a named app state argument"
);
}
#[test]
fn component_registration_generation_respects_existing_and_incomplete_modules() {
let external: syn::ItemMod = parse_quote!(
mod handlers;
);
let unchanged = add_component_register_helper(external.clone(), "todos");
assert_eq!(
quote!(#external).to_string(),
quote!(#unchanged).to_string()
);
for existing in ["register", "register_with_state"] {
let existing_ident = syn::Ident::new(existing, proc_macro2::Span::call_site());
let module: syn::ItemMod = parse_quote! {
mod handlers {
struct App;
#[hemx::handler]
fn create(app: App) {}
fn #existing_ident() {}
}
};
let generated = add_component_register_helper(module, "todos");
let items = generated.content.unwrap().1;
assert_eq!(items.len(), 3, "must preserve existing {existing}");
}
let no_state: syn::ItemMod = parse_quote! {
mod handlers {
#[hemx::handler]
fn create() {}
}
};
assert_eq!(
add_component_register_helper(no_state, "todos")
.content
.unwrap()
.1
.len(),
1
);
let no_handlers: syn::ItemMod = parse_quote! {
mod handlers {
struct App;
fn helper(app: App) {}
}
};
assert_eq!(
add_component_register_helper(no_handlers, "todos")
.content
.unwrap()
.1
.len(),
2
);
// test req: component/003 req: derive_handler/003
}
#[test]
@@ -1525,6 +1604,8 @@ mod tests {
// req: component/003 req: derive_handler/003
let module = parse_quote! {
mod handlers {
const COMPONENT_KIND: &str = "todos";
#[hemx::handler]
fn sync_form(app: super::App, form: super::NewTodo) -> impl hemx::IntoEffect {
hemx::EventName::new("sync-form").emit("")