use super::*;
fn named_default_field(name: &str, type_name: &str, default_path: &str) -> FieldDef {
FieldDef {
name: name.to_string(),
ty: TypeRef::Named(type_name.to_string()),
typed_default: Some(DefaultValue::PublicFunctionCall(default_path.to_string())),
..Default::default()
}
}
fn owning_type(rust_path: &str, type_name: &str, fields: Vec<FieldDef>) -> TypeDef {
TypeDef {
name: type_name.to_string(),
rust_path: rust_path.to_string(),
fields,
has_serde: true,
..Default::default()
}
}
#[test]
fn format_default_value_named_function_call_appends_into() {
let field = named_default_field("ssrf", "SsrfPolicy", "sample_crate::SsrfPolicy::from_env");
let typ = owning_type("sample_crate::CrawlConfig", "CrawlConfig", vec![field.clone()]);
assert_eq!(
format_default_value(&field, &typ, "WasmSsrfPolicy"),
"sample_crate::SsrfPolicy::from_env().into()"
);
}
#[test]
fn format_default_value_named_function_call_uses_serde_when_mapped_to_js_value() {
let field = named_default_field(
"model",
"ScoringModelType",
"sample_crate::ScoringConfig::default_scoring_model",
);
let typ = owning_type("sample_crate::ScoringConfig", "ScoringConfig", vec![field.clone()]);
let rendered = format_default_value(&field, &typ, "JsValue");
assert!(
!rendered.contains(".into()"),
"a JsValue-mapped field must not use .into(): {rendered}"
);
assert!(
rendered.starts_with("serde_wasm_bindgen::to_value(&")
&& rendered.ends_with(".unwrap_or(wasm_bindgen::JsValue::NULL)"),
"expected a serde_wasm_bindgen bridge, got: {rendered}"
);
}
#[test]
fn maps_to_js_value_accepts_every_path_spelling() {
for spelling in [
"JsValue",
"wasm_bindgen::JsValue",
"::wasm_bindgen::JsValue",
"wasm_bindgen::prelude::JsValue",
" JsValue ",
] {
assert!(maps_to_js_value(spelling), "{spelling} must be recognised as JsValue");
}
for other in ["WasmSsrfPolicy", "String", "Option<JsValue>", "Vec<JsValue>", ""] {
assert!(!maps_to_js_value(other), "{other} must not be treated as JsValue");
}
}
#[test]
fn format_default_value_uses_serde_for_qualified_js_value_spelling() {
let field = named_default_field(
"model",
"ScoringModelType",
"sample_crate::ScoringConfig::default_scoring_model",
);
let typ = owning_type("sample_crate::ScoringConfig", "ScoringConfig", vec![field.clone()]);
let rendered = format_default_value(&field, &typ, "::wasm_bindgen::prelude::JsValue");
assert!(
!rendered.contains(".into()") && rendered.starts_with("serde_wasm_bindgen::to_value(&"),
"expected a serde_wasm_bindgen bridge, got: {rendered}"
);
}
#[test]
fn format_default_value_non_named_function_call_has_no_into() {
let field = FieldDef {
name: "retry_limit".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
typed_default: Some(DefaultValue::PublicFunctionCall(
"sample_crate::defaults::retry_limit".to_string(),
)),
..Default::default()
};
let typ = owning_type("sample_crate::CrawlConfig", "CrawlConfig", vec![field.clone()]);
assert_eq!(
format_default_value(&field, &typ, ""),
"sample_crate::defaults::retry_limit()"
);
}
#[test]
fn wasm_constructor_recovers_private_serde_default_via_deserialize() {
let field = FieldDef {
name: "max_archive_depth".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
typed_default: Some(DefaultValue::FunctionCall("default_archive_depth".to_string())),
..Default::default()
};
let typ = owning_type(
"sample_crate::ExtractionConfig",
"ExtractionConfig",
vec![field.clone()],
);
let rendered = format_default_value(&field, &typ, "");
assert_eq!(
rendered,
"serde_json::from_str::<sample_crate::ExtractionConfig>(r#\"{}\"#).expect(\"alef-generated \
default JSON for `ExtractionConfig` failed to deserialize\").max_archive_depth"
);
assert!(
!rendered.contains("default_archive_depth()"),
"the private source-crate function must never be emitted as a bare callable: {rendered}"
);
}
#[test]
fn wasm_constructor_appends_into_for_named_field_default() {
let field = FieldDef {
name: "crawl".to_string(),
ty: TypeRef::Named("CrawlConfig".to_string()),
typed_default: Some(DefaultValue::FunctionCall(
"default_sample_crate_crawl_config".to_string(),
)),
..Default::default()
};
let typ = owning_type(
"sample_crate::ExtractionConfig",
"ExtractionConfig",
vec![field.clone()],
);
let rendered = format_default_value(&field, &typ, "");
assert_eq!(
rendered,
"serde_json::from_str::<sample_crate::ExtractionConfig>(r#\"{}\"#).expect(\"alef-generated \
default JSON for `ExtractionConfig` failed to deserialize\").crawl.into()"
);
}
#[test]
fn wasm_constructor_default_recovery_failure_emits_compile_error() {
let owner_field = FieldDef {
name: "owner".to_string(),
ty: TypeRef::Named("Author".to_string()),
..Default::default()
};
let retry_field = FieldDef {
name: "retry_limit".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
typed_default: Some(DefaultValue::FunctionCall("defaults::retry_limit".to_string())),
..Default::default()
};
let typ = owning_type(
"sample_crate::RetryHolder",
"RetryHolder",
vec![owner_field, retry_field.clone()],
);
let rendered = format_default_value(&retry_field, &typ, "");
assert!(
rendered.starts_with("compile_error!"),
"unrecoverable private default must fail generation, not a bare path call: {rendered}"
);
assert!(
rendered.contains("defaults::retry_limit") && rendered.contains("retry_limit"),
"the failure must name the uncallable function and the field: {rendered}"
);
assert!(
!rendered.contains("defaults::retry_limit()"),
"the uncallable source function must never be emitted as a callable: {rendered}"
);
}
#[test]
fn config_constructor_parts_named_default_field_applies_and_converts() {
let field = named_default_field("ssrf", "SsrfPolicy", "sample_crate::SsrfPolicy::from_env");
let fields = vec![field.clone()];
let type_mapper = |ty: &TypeRef| match ty {
TypeRef::Named(name) => format!("Wasm{name}"),
_ => "String".to_string(),
};
let typ = owning_type("sample_crate::CrawlConfig", "CrawlConfig", vec![field]);
let (_, _, assignments) = config_constructor_parts_with_options(&fields, &type_mapper, true, &typ);
assert!(
assignments.contains("ssrf.unwrap_or_else(|| sample_crate::SsrfPolicy::from_env().into())"),
"expected an unwrap_or_else default that converts via .into(), got: {assignments}"
);
}
#[test]
fn config_constructor_parts_private_default_field_recovers_via_deserialize() {
let field = FieldDef {
name: "max_archive_depth".to_string(),
ty: TypeRef::Primitive(PrimitiveType::U32),
typed_default: Some(DefaultValue::FunctionCall("default_archive_depth".to_string())),
..Default::default()
};
let fields = vec![field.clone()];
let type_mapper = |ty: &TypeRef| match ty {
TypeRef::Named(name) => format!("Wasm{name}"),
_ => "u32".to_string(),
};
let typ = owning_type("sample_crate::ExtractionConfig", "ExtractionConfig", vec![field]);
let (_, _, assignments) = config_constructor_parts_with_options(&fields, &type_mapper, true, &typ);
assert!(
assignments.contains(
"max_archive_depth.unwrap_or_else(|| serde_json::from_str::<sample_crate::ExtractionConfig>(r#\"{}\"#)"
) && assignments.contains(".max_archive_depth)"),
"expected an unwrap_or_else default that recovers via deserialize, got: {assignments}"
);
assert!(
!assignments.contains("default_archive_depth()"),
"the private source-crate function must never be emitted as a bare callable: {assignments}"
);
}
#[test]
fn config_constructor_parts_bare_zero_arg_default_drops_redundant_closure() {
let field = FieldDef {
name: "max_archive_depth".to_string(),
ty: TypeRef::Primitive(PrimitiveType::I64),
typed_default: Some(DefaultValue::PublicFunctionCall(
"sample_crate::ExtractionConfig::default_archive_depth".to_string(),
)),
..Default::default()
};
let fields = vec![field.clone()];
let type_mapper = |_: &TypeRef| "i64".to_string();
let typ = owning_type("sample_crate::ExtractionConfig", "ExtractionConfig", vec![field]);
let (_, _, assignments) = config_constructor_parts_with_options(&fields, &type_mapper, true, &typ);
assert!(
assignments.contains("max_archive_depth.unwrap_or_else(sample_crate::ExtractionConfig::default_archive_depth)"),
"expected the redundant closure dropped in favor of a bare function reference, got: {assignments}"
);
assert!(
!assignments.contains("unwrap_or_else(|| sample_crate::ExtractionConfig::default_archive_depth())"),
"clippy::redundant_closure: a bare zero-arg call must never stay wrapped in a closure, got: {assignments}"
);
}