use super::gen_enum;
use crate::backends::napi::gen_bindings::errors::gen_dts;
use crate::core::ir::{ApiSurface, EnumDef, EnumVariant, FieldDef, TypeDef, TypeRef};
fn sample_kind_enum() -> EnumDef {
EnumDef {
name: "SampleKind".to_string(),
rust_path: "test_core::SampleKind".to_string(),
variants: vec![
EnumVariant {
name: "Function".to_string(),
is_tuple: true,
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
},
EnumVariant {
name: "Idle".to_string(),
..Default::default()
},
],
..Default::default()
}
}
#[test]
fn runtime_struct_is_tagged_object_for_default_tagged_data_enum() {
let enum_def = sample_kind_enum();
let runtime = gen_enum(&enum_def, "Js", false, "test_core", None, &[]);
let expected = "\
#[derive(Clone)]
#[napi(object, js_name = \"SampleKind\")]
pub struct JsSampleKind {
#[napi(js_name = \"type\")]
pub type_tag: String,
pub function: Option<String>,
}
impl Default for JsSampleKind {
fn default() -> Self { Self { type_tag: \"Function\".to_string(), function: None } }
}";
assert_eq!(runtime, expected);
}
#[test]
fn dts_declaration_is_discriminated_union_for_default_tagged_data_enum() {
let enum_def = sample_kind_enum();
let api = ApiSurface {
enums: vec![enum_def],
..Default::default()
};
let dts = gen_dts(
&api,
"Js",
&Default::default(),
&[],
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
"",
None,
);
let start = dts
.find("export type SampleKind =")
.expect("gen_dts must declare SampleKind as a discriminated union type, not a plain enum");
let declaration = dts[start..].trim_end();
assert_eq!(
declaration,
"export type SampleKind =\n | { type: 'Function'; function: string }\n | { type: 'Idle' }"
);
}
#[test]
fn dts_and_runtime_agree_on_discriminant_and_payload_field_names() {
let enum_def = sample_kind_enum();
let runtime = gen_enum(&enum_def, "Js", false, "test_core", None, &[]);
let runtime_tag_line = runtime
.lines()
.find(|l| l.trim_start().starts_with("#[napi(js_name ="))
.expect("runtime struct must declare a js_name for its discriminant field");
assert_eq!(runtime_tag_line.trim(), "#[napi(js_name = \"type\")]");
let runtime_payload_line = runtime
.lines()
.find(|l| l.trim_start().starts_with("pub function:"))
.expect("runtime struct must declare the Function variant's payload field");
assert_eq!(runtime_payload_line.trim(), "pub function: Option<String>,");
let api = ApiSurface {
enums: vec![enum_def],
..Default::default()
};
let dts = gen_dts(
&api,
"Js",
&Default::default(),
&[],
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
"",
None,
);
let start = dts
.find("export type SampleKind =")
.expect("gen_dts must declare SampleKind as a discriminated union type, not a plain enum");
let function_member = dts[start..]
.lines()
.find(|l| l.trim_start().starts_with("| { type: 'Function';"))
.expect(".d.ts must declare the Function variant's member shape");
assert_eq!(function_member.trim(), "| { type: 'Function'; function: string }");
}
fn fully_flattened_format_metadata_enum() -> EnumDef {
EnumDef {
name: "FormatMetadata".to_string(),
rust_path: "test_core::FormatMetadata".to_string(),
serde_tag: Some("format_type".to_string()),
serde_rename_all: Some("snake_case".to_string()),
variants: vec![
EnumVariant {
name: "Excel".to_string(),
is_tuple: true,
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::Named("ExcelMetadata".to_string()),
..Default::default()
}],
..Default::default()
},
EnumVariant {
name: "Csv".to_string(),
is_tuple: true,
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::Named("CsvMetadata".to_string()),
..Default::default()
}],
..Default::default()
},
],
..Default::default()
}
}
fn fully_flattened_format_metadata_types() -> Vec<TypeDef> {
vec![
TypeDef {
name: "ExcelMetadata".to_string(),
rust_path: "test_core::ExcelMetadata".to_string(),
fields: vec![FieldDef {
name: "sheet_count".to_string(),
ty: TypeRef::Primitive(crate::core::ir::PrimitiveType::U32),
..Default::default()
}],
..Default::default()
},
TypeDef {
name: "CsvMetadata".to_string(),
rust_path: "test_core::CsvMetadata".to_string(),
fields: vec![FieldDef {
name: "delimiter".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
},
]
}
#[test]
fn runtime_is_json_passthrough_for_fully_flattened_enum() {
let enum_def = fully_flattened_format_metadata_enum();
let types = fully_flattened_format_metadata_types();
let runtime = gen_enum(&enum_def, "Js", true, "test_core", None, &types);
assert!(
runtime.contains("pub struct JsFormatMetadata(pub serde_json::Value)"),
"must route through the JSON passthrough wrapper; got:\n{runtime}"
);
assert!(!runtime.contains("#[napi(object"), "got:\n{runtime}");
}
#[test]
fn dts_declaration_camel_cases_wire_keys_for_fully_flattened_enum() {
let enum_def = fully_flattened_format_metadata_enum();
let api = ApiSurface {
enums: vec![enum_def],
types: fully_flattened_format_metadata_types(),
..Default::default()
};
let dts = gen_dts(
&api,
"Js",
&Default::default(),
&[],
&Default::default(),
&Default::default(),
&Default::default(),
&Default::default(),
"",
None,
);
let start = dts
.find("export type FormatMetadata =")
.expect("gen_dts must declare FormatMetadata as a union type, not a plain enum");
let declaration_block = &dts[start..];
assert!(
declaration_block.contains("formatType: \"excel\""),
"the tag key must be the enum's own serde_tag, camelCased; got:\n{dts}"
);
assert!(declaration_block.contains("formatType: \"csv\""), "got:\n{dts}");
assert!(
!declaration_block.contains("kind:"),
"must never fall back to napi's synthesized 'kind' discriminant; got:\n{dts}"
);
assert!(
declaration_block.contains("\"excel\"") && !declaration_block.contains("\"Excel\""),
"variant values must stay verbatim serde values; got:\n{dts}"
);
assert!(dts.contains("sheetCount: number"), "got:\n{dts}");
assert!(dts.contains("delimiter: string"), "got:\n{dts}");
assert!(
!dts.contains("export declare enum FormatMetadata"),
"must never fall back to a plain string enum; got:\n{dts}"
);
}