use alef::backends::dart::DartBackend;
use alef::core::backend::Backend;
use alef::core::config::{ResolvedCrateConfig, new_config::NewAlefConfig};
use alef::core::ir::{ApiSurface, CoreWrapper, EnumDef, EnumVariant, FieldDef, PrimitiveType, TypeRef};
fn make_field(name: &str, ty: TypeRef, optional: bool) -> FieldDef {
FieldDef {
version: Default::default(),
name: name.to_string(),
ty,
optional,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
serde_rename: None,
serde_flatten: false,
serde_with: None,
serde_skip_serializing_if: false,
serde_skip: false,
binding_excluded: false,
binding_exclusion_reason: None,
original_type: None,
}
}
fn make_basic_config() -> ResolvedCrateConfig {
let toml = r#"
[workspace]
languages = ["dart"]
[[crates]]
name = "demo"
sources = ["src/lib.rs"]
version_from = "/nonexistent/Cargo.toml"
"#;
let cfg: NewAlefConfig = toml::from_str(toml).expect("test config must parse");
cfg.resolve().expect("test config must resolve").remove(0)
}
fn make_image_output_format_enum() -> EnumDef {
EnumDef {
name: "ImageOutputFormat".to_string(),
rust_path: "demo::ImageOutputFormat".to_string(),
original_rust_path: String::new(),
variants: vec![
EnumVariant {
name: "Native".to_string(),
fields: vec![],
doc: "Keep the original image format.".to_string(),
is_default: true,
serde_rename: None,
is_tuple: false,
binding_excluded: false,
binding_exclusion_reason: None,
originally_had_data_fields: false,
cfg: None,
version: Default::default(),
},
EnumVariant {
name: "Jpeg".to_string(),
fields: vec![make_field("quality", TypeRef::Primitive(PrimitiveType::U8), false)],
doc: "JPEG output.".to_string(),
is_default: false,
serde_rename: None,
is_tuple: false,
binding_excluded: false,
binding_exclusion_reason: None,
originally_had_data_fields: false,
cfg: None,
version: Default::default(),
},
EnumVariant {
name: "Heif".to_string(),
fields: vec![make_field("quality", TypeRef::Primitive(PrimitiveType::U8), false)],
doc: "HEIF/HEIC output. Requires the `heic` feature.".to_string(),
is_default: false,
serde_rename: None,
is_tuple: false,
binding_excluded: false,
binding_exclusion_reason: None,
originally_had_data_fields: false,
cfg: Some(r#"feature = "heic""#.to_string()),
version: Default::default(),
},
],
excluded_variants: vec![],
methods: vec![],
doc: "Output image format for extraction.".to_string(),
cfg: None,
is_copy: false,
has_serde: true,
serde_tag: None,
serde_content: None,
serde_untagged: false,
serde_rename_all: None,
rename_all_fields: None,
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
has_default: false,
}
}
fn generate_lib_rs(enum_def: EnumDef) -> String {
let api = ApiSurface {
crate_name: "demo".into(),
version: "0.1.0".into(),
types: vec![],
functions: vec![],
enums: vec![enum_def],
errors: vec![],
excluded_type_paths: ::std::collections::HashMap::new(),
excluded_trait_names: ::std::collections::HashSet::new(),
services: vec![],
handler_contracts: vec![],
unsupported_public_items: Vec::new(),
};
let config = make_basic_config();
let files = DartBackend.generate_bindings(&api, &config).unwrap();
files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("lib.rs"))
.expect("lib.rs must be generated")
.content
.clone()
}
fn mirror_enum_section(lib_rs: &str) -> &str {
let start = lib_rs
.find("pub enum ImageOutputFormat {")
.expect("mirror enum section must exist");
let end = lib_rs[start..]
.find("\n}\n\n// From<SourceT>")
.map(|offset| start + offset + "\n}\n".len())
.expect("mirror enum section must end before conversion impls");
&lib_rs[start..end]
}
fn from_core_section(lib_rs: &str) -> &str {
let start = lib_rs
.find("impl From<demo::ImageOutputFormat> for ImageOutputFormat")
.expect("From<CoreType> impl must exist");
&lib_rs[start..]
}
#[test]
fn cfg_gated_variant_keeps_mirror_variant_unconditional() {
let lib_rs = generate_lib_rs(make_image_output_format_enum());
let mirror_enum = mirror_enum_section(&lib_rs);
assert!(
mirror_enum.contains("Heif {"),
"mirror enum must include the feature-gated variant unconditionally:\n{mirror_enum}"
);
assert!(
!mirror_enum.contains(r#"#[cfg(feature = "heic")]"#),
"mirror enum must not propagate upstream cfg guards because FRB generates \
unconditional references to mirror variants:\n{mirror_enum}"
);
}
#[test]
fn cfg_gated_variant_cfg_precedes_variant_in_from_core_arm() {
let lib_rs = generate_lib_rs(make_image_output_format_enum());
let from_core = from_core_section(&lib_rs);
let cfg_line = from_core
.lines()
.enumerate()
.find(|(_, l)| l.contains(r#"#[cfg(feature = "heic")]"#))
.map(|(i, _)| i)
.expect("#[cfg(feature = \"heic\")] line not found");
let heif_arm_line = from_core
.lines()
.enumerate()
.find(|(_, l)| l.contains("demo::ImageOutputFormat::Heif"))
.map(|(i, _)| i)
.expect("Heif match arm line not found");
assert!(
cfg_line < heif_arm_line,
"#[cfg] attribute (line {cfg_line}) must precede the Heif match arm (line {heif_arm_line})",
);
}
#[test]
fn non_cfg_variants_have_no_cfg_attribute() {
let lib_rs = generate_lib_rs(make_image_output_format_enum());
let cfg_count = lib_rs
.lines()
.filter(|l| l.contains(r#"#[cfg(feature = "heic")]"#))
.count();
assert_eq!(
cfg_count, 2,
"Expected exactly 2 occurrences of #[cfg(feature = \"heic\")]: \
the From<Core> arm and fallible decoder-helper arm that reference the upstream variant. \
Found {cfg_count}:\n{lib_rs}",
);
let lines: Vec<_> = lib_rs.lines().collect();
for (index, line) in lines.iter().enumerate() {
if line.contains(r#"#[cfg(feature = "heic")]"#) {
let guarded = lines.get(index + 1).copied().unwrap_or_default();
assert!(
guarded.contains("ImageOutputFormat::Heif"),
"cfg attributes must guard only the Heif conversion paths, not Native or Jpeg: {guarded}"
);
}
}
}
#[test]
fn ungated_variants_are_present_without_cfg() {
let lib_rs = generate_lib_rs(make_image_output_format_enum());
assert!(lib_rs.contains("Native"), "Native variant missing from lib.rs");
assert!(lib_rs.contains("Jpeg"), "Jpeg variant missing from lib.rs");
}