use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{FieldDef, FunctionDef, TypeDef, TypeRef};
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::{Assertion, Fixture};
use std::collections::{HashMap, HashSet};
fn sample_lib_type_defs() -> Vec<TypeDef> {
vec![
TypeDef {
name: "SampleResult".to_string(),
fields: vec![FieldDef {
name: "entries".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Entry".to_string()))),
..FieldDef::default()
}],
..TypeDef::default()
},
TypeDef {
name: "Entry".to_string(),
fields: vec![
FieldDef {
name: "sections".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Section".to_string()))),
optional: true,
..FieldDef::default()
},
FieldDef {
name: "tags".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::String)),
optional: false,
..FieldDef::default()
},
],
..TypeDef::default()
},
TypeDef {
name: "Section".to_string(),
fields: vec![FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
..FieldDef::default()
}],
..TypeDef::default()
},
]
}
fn sample_lib_functions() -> Vec<FunctionDef> {
vec![FunctionDef {
name: "get_report".to_string(),
return_type: TypeRef::Named("SampleResult".to_string()),
..FunctionDef::default()
}]
}
fn make_assertion(field: &str, value: serde_json::Value) -> Assertion {
Assertion {
assertion_type: "equals".to_string(),
field: Some(field.to_string()),
value: Some(value),
..Assertion::default()
}
}
fn render(assertions: Vec<Assertion>) -> String {
let fixture = Fixture {
id: "get_report_sections".into(),
description: "sample_lib report with optional nested sections".into(),
assertions,
..Fixture::default()
};
let mut e2e_config = crate::e2e::config::E2eConfig::default();
e2e_config.call.function = "get_report".into();
e2e_config.call.result_var = "result".into();
let placeholder_resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
let config = ResolvedCrateConfig {
name: "sample_lib".into(),
..ResolvedCrateConfig::default()
};
let mut out = String::new();
let mut visitor_class_decls: Vec<String> = Vec::new();
super::render_test_method(
&mut out,
&mut visitor_class_decls,
&fixture,
"SampleResultClient",
"GetReport",
"SampleLibException",
"result",
&[],
&placeholder_resolver,
false,
false,
&e2e_config,
&HashMap::new(),
&HashMap::new(),
&HashMap::new(),
&[],
&config,
&sample_lib_type_defs(),
&[],
&sample_lib_functions(),
&[],
);
out
}
#[test]
fn nested_optional_segment_field_null_forgives_before_indexing() {
let out = render(vec![make_assertion(
"entries[0].sections[0].label",
serde_json::Value::String("alpha".to_string()),
)]);
assert!(
out.contains("Sections![0]"),
"must null-forgive the Option<Vec<Section>> before indexing; got:\n{out}"
);
assert!(
!out.contains("Sections[0]"),
"must not emit the un-null-forgiven Option<Vec<T>> index; got:\n{out}"
);
}
#[test]
fn nested_optional_segment_field_null_forgives_before_count() {
let out = render(vec![make_assertion(
"entries[0].sections.length",
serde_json::Value::Number(serde_json::Number::from(1u64)),
)]);
assert!(
out.contains("Sections!.Count"),
"must null-forgive the Option<Vec<Section>> before .Count; got:\n{out}"
);
assert!(
!out.contains("Sections.Count"),
"must not emit .Count directly against the nullable collection; got:\n{out}"
);
}
#[test]
fn non_optional_sibling_segment_field_stays_plain() {
let out = render(vec![
make_assertion("entries[0].tags[0]", serde_json::Value::String("beta".to_string())),
make_assertion(
"entries[0].tags.length",
serde_json::Value::Number(serde_json::Number::from(1u64)),
),
]);
assert!(
out.contains("Tags[0]") && !out.contains("Tags![0]"),
"a non-optional Vec<T> field must index plainly, with no null-forgiving operator; got:\n{out}"
);
assert!(
out.contains("Tags.Count") && !out.contains("Tags!.Count"),
"a non-optional Vec<T> field must read .Count plainly, with no null-forgiving operator; got:\n{out}"
);
}