use super::types::gen_options_py;
use crate::core::config::DtoConfig;
use crate::core::ir::{ApiSurface, FieldDef, TypeDef, TypeRef};
const WIDGET: &str = "Widget";
const MODULE_NAME: &str = "_rust";
fn widget_api(type_has_serde: bool) -> ApiSurface {
ApiSurface {
types: vec![TypeDef {
name: WIDGET.to_owned(),
rust_path: format!("sample_core::{WIDGET}"),
has_default: true,
has_serde: type_has_serde,
fields: vec![FieldDef {
name: "label".to_owned(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
}],
..ApiSurface::default()
}
}
#[test]
fn gen_options_py_emits_from_json_delegating_to_native_class() {
let api = widget_api(true);
let options_py = gen_options_py(&api, MODULE_NAME, &DtoConfig::default(), &[], true);
assert!(
options_py.contains("def from_json(json_str: str) -> Widget:"),
"options.py must declare a from_json staticmethod on the Widget dataclass, with an \
UNQUOTED return annotation -- the file unconditionally carries `from __future__ import \
annotations`, so the quotes are dead weight ruff's UP037 rejects as an error:\n{options_py}"
);
assert!(
options_py.contains(&format!(
"_from_native_widget({MODULE_NAME}.Widget.from_json(json_str))"
)),
"from_json must call the native class's from_json and lift the result back through \
the existing _from_native_widget converter, not construct Widget directly:\n{options_py}"
);
assert!(
options_py.contains(&format!("from . import {MODULE_NAME}\n")),
"the native module must be imported by name so from_json can reach {MODULE_NAME}.Widget:\n{options_py}"
);
}
#[test]
fn gen_options_py_from_json_docstring_uses_an_before_a_vowel_class_name() {
const ACCELERATION_CONFIG: &str = "AccelerationConfig";
let api = ApiSurface {
types: vec![TypeDef {
name: ACCELERATION_CONFIG.to_owned(),
rust_path: format!("sample_core::{ACCELERATION_CONFIG}"),
has_default: true,
has_serde: true,
fields: vec![FieldDef {
name: "label".to_owned(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
}],
..ApiSurface::default()
};
let options_py = gen_options_py(&api, MODULE_NAME, &DtoConfig::default(), &[], true);
assert!(
options_py.contains("\"\"\"Build an AccelerationConfig from a JSON string, via the native binding.\"\"\""),
"a vowel-starting class name must take the article `an`, not `a`:\n{options_py}"
);
}
#[test]
fn gen_options_py_omits_from_json_when_type_has_no_serde() {
let api = widget_api(false);
let options_py = gen_options_py(&api, MODULE_NAME, &DtoConfig::default(), &[], true);
assert!(
!options_py.contains("def from_json("),
"a type without core Deserialize must not get a from_json delegate:\n{options_py}"
);
assert!(
!options_py.contains(&format!("from . import {MODULE_NAME}\n")),
"no from_json delegate anywhere in the file means the native-module-alias import must \
not be emitted either -- it would be dead code:\n{options_py}"
);
}
#[test]
fn gen_options_py_omits_from_json_when_crate_has_no_serde() {
let api = widget_api(true);
let options_py = gen_options_py(&api, MODULE_NAME, &DtoConfig::default(), &[], false);
assert!(
!options_py.contains("def from_json("),
"no from_json delegate anywhere when the crate has no serde support:\n{options_py}"
);
}