use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::{EnumDef, EnumVariant, FieldDef, FunctionDef, TypeDef, TypeRef};
use crate::e2e::config::{CallConfig, CallOverride, E2eConfig};
use crate::e2e::field_access::SwiftFirstClassMap;
use crate::e2e::fixture::{Assertion, Fixture};
use std::collections::{HashMap, HashSet};
fn unit_tag_enum() -> EnumDef {
EnumDef {
name: "UnitTag".to_string(),
variants: vec![
EnumVariant {
name: "Alpha".to_string(),
..EnumVariant::default()
},
EnumVariant {
name: "Beta".to_string(),
..EnumVariant::default()
},
],
has_serde: true,
..EnumDef::default()
}
}
fn payload_tag_enum() -> EnumDef {
EnumDef {
name: "PayloadTag".to_string(),
variants: vec![EnumVariant {
name: "Text".to_string(),
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::String,
..FieldDef::default()
}],
is_tuple: true,
..EnumVariant::default()
}],
has_serde: true,
..EnumDef::default()
}
}
fn gate_item_type() -> TypeDef {
TypeDef {
name: "GateItem".to_string(),
fields: vec![
FieldDef {
name: "kind".to_string(),
ty: TypeRef::Named("UnitTag".to_string()),
..FieldDef::default()
},
FieldDef {
name: "payload".to_string(),
ty: TypeRef::Named("PayloadTag".to_string()),
..FieldDef::default()
},
FieldDef {
name: "label".to_string(),
ty: TypeRef::String,
..FieldDef::default()
},
],
..TypeDef::default()
}
}
fn gate_result_type() -> TypeDef {
TypeDef {
name: "GateResult".to_string(),
fields: vec![FieldDef {
name: "items".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("GateItem".to_string()))),
..FieldDef::default()
}],
..TypeDef::default()
}
}
fn first_class_map() -> SwiftFirstClassMap {
let mut field_types = HashMap::new();
field_types.insert(
"GateResult".to_string(),
HashMap::from([("items".to_string(), "GateItem".to_string())]),
);
let mut stringy_fields_by_type = HashMap::new();
stringy_fields_by_type.insert(
"GateItem".to_string(),
vec![
crate::e2e::field_access::StringyField {
name: "kind".to_string(),
kind: crate::e2e::field_access::StringyFieldKind::Plain,
},
crate::e2e::field_access::StringyField {
name: "payload".to_string(),
kind: crate::e2e::field_access::StringyFieldKind::Plain,
},
crate::e2e::field_access::StringyField {
name: "label".to_string(),
kind: crate::e2e::field_access::StringyFieldKind::Plain,
},
],
);
SwiftFirstClassMap {
first_class_types: ["GateResult", "GateItem"].into_iter().map(str::to_string).collect(),
field_types,
vec_field_names: HashSet::from(["items".to_string()]),
json_bridged_field_names: HashSet::new(),
json_bridged_by_type: HashMap::new(),
getter_optionality: HashMap::new(),
root_type: Some("GateResult".to_string()),
stringy_fields_by_type,
}
}
fn e2e_config() -> E2eConfig {
let mut call_config = CallConfig {
function: "gate".to_string(),
..CallConfig::default()
};
call_config.overrides.insert(
"csharp".to_string(),
CallOverride {
result_type: Some("GateResult".to_string()),
..CallOverride::default()
},
);
let mut e2e_config = E2eConfig::default();
e2e_config.calls.insert("gate".to_string(), call_config);
e2e_config
}
fn opaque_root_map() -> SwiftFirstClassMap {
let mut map = first_class_map();
map.first_class_types.remove("GateResult");
map
}
fn render(fixture: &Fixture) -> String {
render_with_map(fixture, first_class_map())
}
fn render_with_map(fixture: &Fixture, map: SwiftFirstClassMap) -> String {
let type_defs = [gate_result_type(), gate_item_type()];
let enums = [unit_tag_enum(), payload_tag_enum()];
let functions = [FunctionDef {
name: "gate".to_string(),
return_type: TypeRef::Named("GateResult".to_string()),
..FunctionDef::default()
}];
let e2e_config = e2e_config();
let config = ResolvedCrateConfig {
name: "sample".to_string(),
..ResolvedCrateConfig::default()
};
let mut out = String::new();
super::test_method::render_test_method(
&mut out,
fixture,
&e2e_config,
"",
"",
&[],
false,
None,
&map,
"Sample",
&config,
&type_defs,
&enums,
&functions,
&[],
);
out
}
fn wildcard_fixture(id: &str, assertion_type: &str, field: &str, value: Option<&str>) -> Fixture {
Fixture {
id: id.to_string(),
description: id.to_string(),
call: Some("gate".to_string()),
assertions: vec![Assertion {
assertion_type: assertion_type.to_string(),
field: Some(field.to_string()),
value: value.map(|v| serde_json::Value::String(v.to_string())),
..Assertion::default()
}],
..Fixture::default()
}
}
const FORBIDDEN: &[&str] = &[
"kind()",
"kind.toString()",
"label()",
"label.toString()",
"payload()",
"payload.rawValue",
];
fn assert_gate_holds(out: &str, scenario: &str) {
for needle in FORBIDDEN {
assert!(
!out.contains(needle),
"{scenario}: rendered Swift references a first-class leaf via opaque or unavailable \
syntax (found {needle:?}), got:\n{out}"
);
}
}
#[test]
fn wildcard_contains_never_emits_opaque_or_unavailable_syntax() {
for field in ["items[].kind", "items[].label", "items[].payload"] {
let fixture = wildcard_fixture(&format!("contains_{field}"), "contains", field, Some("x"));
let out = render(&fixture);
assert_gate_holds(&out, &format!("wildcard contains on '{field}'"));
}
let payload_out = render(&wildcard_fixture(
"contains_payload",
"contains",
"items[].payload",
Some("x"),
));
assert!(
payload_out.contains("XCTAssertTrue") && payload_out.contains("payload.toString()"),
"a payload-carrying union element must lower through its own .toString(), got:\n{payload_out}"
);
assert!(
!payload_out.contains("skipped:"),
"a payload-carrying union element has a real accessor now — it must not be skipped, got:\n{payload_out}"
);
let kind_out = render(&wildcard_fixture(
"contains_kind",
"contains",
"items[].kind",
Some("x"),
));
assert!(
kind_out.contains("kind.rawValue"),
"a first-class unit enum element must still lower through .rawValue, got:\n{kind_out}"
);
let label_out = render(&wildcard_fixture(
"contains_label",
"contains",
"items[].label",
Some("x"),
));
assert!(
label_out.contains("$0.label"),
"a first-class String element must stay bare property access, got:\n{label_out}"
);
}
#[test]
fn wildcard_not_empty_never_emits_opaque_or_unavailable_syntax() {
for field in ["items[].kind", "items[].label", "items[].payload"] {
let fixture = wildcard_fixture(&format!("not_empty_{field}"), "not_empty", field, None);
let out = render(&fixture);
assert_gate_holds(&out, &format!("wildcard not_empty on '{field}'"));
assert!(
!out.contains("skipped:"),
"not_empty on '{field}' has a real accessor available — it must not be skipped, got:\n{out}"
);
}
}
#[test]
fn stringy_aggregator_never_emits_opaque_or_unavailable_syntax() {
let fixture = wildcard_fixture("aggregator", "contains", "items", Some("x"));
let out = render(&fixture);
assert_gate_holds(&out, "stringy aggregator over 'items'");
for needle in [
"texts.append(item.label)",
"texts.append(item.kind.rawValue)",
"texts.append(item.payload.toString())",
] {
assert!(
out.contains(needle),
"expected the aggregator to include {needle:?}, got:\n{out}"
);
}
}
#[test]
fn payload_carrying_enum_leaf_renders_a_real_assertion_not_a_skip() {
let scenarios = [
(
"wildcard contains",
render(&wildcard_fixture(
"pc_contains",
"contains",
"items[].payload",
Some("x"),
)),
),
(
"wildcard not_empty",
render(&wildcard_fixture("pc_not_empty", "not_empty", "items[].payload", None)),
),
(
"stringy aggregator",
render(&wildcard_fixture("pc_aggregator", "contains", "items", Some("x"))),
),
];
for (scenario, out) in scenarios {
assert!(
!out.contains("skipped:"),
"{scenario}: a payload-carrying enum leaf has a real .toString() accessor now, it must \
not render a skip — got:\n{out}"
);
assert!(
out.contains("payload.toString()"),
"{scenario}: expected the payload-carrying enum leaf to lower through .toString(), got:\n{out}"
);
assert!(
out.contains("XCTAssertTrue"),
"{scenario}: expected a real XCTAssertTrue, got:\n{out}"
);
}
}
#[test]
fn stringy_aggregator_uses_method_syntax_when_root_is_opaque() {
let fixture = wildcard_fixture("aggregator_opaque_root", "contains", "items", Some("x"));
let out = render_with_map(&fixture, opaque_root_map());
assert!(
out.contains("result.items().contains(where:"),
"expected the array accessor on an opaque root to be a method call, got:\n{out}"
);
for needle in [
"texts.append(item.label().toString())",
"texts.append(item.kind().toString())",
"texts.append(item.payload().toString())",
] {
assert!(
out.contains(needle),
"expected the aggregator to lower every element field as an opaque getter, expected \
{needle:?}, got:\n{out}"
);
}
for needle in [
"texts.append(item.label)",
"item.kind.rawValue",
"item.payload.toString()",
] {
assert!(
!out.contains(needle),
"an element pulled from a RustVec on an opaque root has no properties, found {needle:?} in:\n{out}"
);
}
}