use std::collections::{HashMap, HashSet};
use super::assertions::render_assertion;
use crate::core::ir::{EnumDef, EnumVariant, FieldDef, TypeDef, TypeRef};
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Assertion;
const FIELD_ACCESS: &str = "result.kind";
fn node_kind_enums() -> Vec<EnumDef> {
vec![EnumDef {
name: "NodeKind".to_string(),
variants: vec![
variant("KeyValue", Some("key-value")),
variant("Plain", None),
variant("Anchor", Some("Plain")),
variant("Bold", Some("bold")),
],
..EnumDef::default()
}]
}
fn variant(name: &str, serde_rename: Option<&str>) -> EnumVariant {
EnumVariant {
name: name.to_string(),
serde_rename: serde_rename.map(str::to_string),
..EnumVariant::default()
}
}
fn renamed_enum_resolver() -> FieldResolver {
let type_defs = vec![TypeDef {
name: "Result".to_string(),
fields: vec![FieldDef {
name: "kind".to_string(),
ty: TypeRef::Named("NodeKind".to_string()),
..FieldDef::default()
}],
..TypeDef::default()
}];
let ir_enum_map = FieldResolver::ir_enum_fields(&type_defs, &node_kind_enums());
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_ir_enum_map(ir_enum_map, Some("Result".to_string()))
}
fn config_only_resolver() -> FieldResolver {
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_enum_fields(HashSet::from(["kind".to_string()]))
}
fn wildcard_enum_resolver() -> FieldResolver {
let type_defs = vec![
TypeDef {
name: "Result".to_string(),
fields: vec![FieldDef {
name: "links".to_string(),
ty: TypeRef::Vec(Box::new(TypeRef::Named("Link".to_string()))),
..FieldDef::default()
}],
..TypeDef::default()
},
TypeDef {
name: "Link".to_string(),
fields: vec![FieldDef {
name: "link_type".to_string(),
ty: TypeRef::Named("NodeKind".to_string()),
..FieldDef::default()
}],
..TypeDef::default()
},
];
let ir_enum_map = FieldResolver::ir_enum_fields(&type_defs, &node_kind_enums());
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_ir_enum_map(ir_enum_map, Some("Result".to_string()))
}
fn render_wildcard(operator: &str, fixture_value: &str) -> String {
render(
&Assertion {
assertion_type: operator.to_string(),
field: Some("links[].link_type".to_string()),
value: Some(serde_json::Value::String(fixture_value.to_string())),
..Assertion::default()
},
&wildcard_enum_resolver(),
)
}
fn render(assertion: &Assertion, resolver: &FieldResolver) -> String {
let mut out = String::new();
render_assertion(
&mut out,
assertion,
"result",
"sample",
"sample",
false,
&[],
resolver,
false,
false,
false,
false,
false,
None,
);
out
}
fn render_single(operator: &str, resolver: &FieldResolver, fixture_value: &str) -> String {
render(
&Assertion {
assertion_type: operator.to_string(),
field: Some("kind".to_string()),
value: Some(serde_json::Value::String(fixture_value.to_string())),
..Assertion::default()
},
resolver,
)
}
fn render_multi(operator: &str, resolver: &FieldResolver, fixture_values: &[&str]) -> String {
render(
&Assertion {
assertion_type: operator.to_string(),
field: Some("kind".to_string()),
values: Some(
fixture_values
.iter()
.map(|v| serde_json::Value::String((*v).to_string()))
.collect(),
),
..Assertion::default()
},
resolver,
)
}
fn predicate(needle: &str) -> String {
format!("format!(\"{{:?}}\", {FIELD_ACCESS}).to_lowercase().contains(&r#\"{needle}\"#.to_lowercase())")
}
fn contains_line(needle: &str) -> String {
format!(
" assert!({}, \"expected to contain: {{}}\", r#\"{needle}\"#);\n",
predicate(needle)
)
}
fn not_contains_line(needle: &str) -> String {
format!(
" assert!(!{}, \"expected NOT to contain: {{}}\", r#\"{needle}\"#);\n",
predicate(needle)
)
}
#[test]
fn harness_renders_the_field_access_these_tests_assume() {
let ir = renamed_enum_resolver();
assert_eq!(ir.accessor("kind", "rust", "result"), FIELD_ACCESS);
assert!(ir.is_enum("kind"), "the IR must classify `kind` as enum-typed");
let config_only = config_only_resolver();
assert_eq!(config_only.accessor("kind", "rust", "result"), FIELD_ACCESS);
assert!(
config_only.is_enum("kind"),
"the hand-maintained config entry must classify `kind` as enum-typed"
);
}
#[test]
fn contains_searches_for_the_rust_identifier_of_a_renamed_variant() {
let rendered = render_single("contains", &renamed_enum_resolver(), "key-value");
assert_eq!(
rendered,
contains_line("KeyValue"),
"`contains` must reconcile the wire value onto the Debug surface, got: {rendered}"
);
}
#[test]
fn every_containment_operator_reconciles_the_renamed_variant() {
let resolver = renamed_enum_resolver();
let contains_all = render_multi("contains_all", &resolver, &["key-value", "not-a-variant"]);
assert_eq!(
contains_all,
format!("{}{}", contains_line("KeyValue"), contains_line("not-a-variant")),
"`contains_all` must translate per value, got: {contains_all}"
);
let not_contains = render_single("not_contains", &resolver, "key-value");
assert_eq!(
not_contains,
not_contains_line("KeyValue"),
"`not_contains` must translate, or it asserts the absence of a string no variant renders, \
got: {not_contains}"
);
let contains_any = render_multi("contains_any", &resolver, &["key-value", "Plain"]);
assert_eq!(
contains_any,
format!(
" assert!({} || {}, \"expected to contain at least one of the specified values\");\n",
predicate("KeyValue"),
predicate("Plain")
),
"`contains_any` must translate each alternative independently, got: {contains_any}"
);
}
#[test]
fn collision_and_unknown_fixture_values_pass_through_untranslated() {
let resolver = renamed_enum_resolver();
let collision = render_single("contains", &resolver, "Plain");
assert_eq!(
collision,
contains_line("Plain"),
"a wire value that is another variant's identifier must not be translated, got: {collision}"
);
let unknown = render_single("contains", &resolver, "not-a-variant");
assert_eq!(
unknown,
contains_line("not-a-variant"),
"an unrecognized fixture value must not be rewritten, got: {unknown}"
);
let renamed = render_single("contains", &resolver, "key-value");
assert_ne!(renamed, collision);
assert_ne!(renamed, unknown);
assert_ne!(collision, unknown);
}
#[test]
fn a_case_only_rename_is_translated_and_the_predicate_is_unchanged_by_it() {
let resolver = renamed_enum_resolver();
let rendered = render_single("contains", &resolver, "bold");
assert_eq!(
rendered,
contains_line("Bold"),
"the shared map records a case-only rename like any other, got: {rendered}"
);
let folded_needle = "Bold".to_lowercase();
assert_eq!(
folded_needle,
"bold".to_lowercase(),
"both spellings must fold together, which is what makes the rewrite semantically inert"
);
}
#[test]
fn config_only_enum_classification_leaves_the_containment_needle_untranslated() {
let rendered = render_single("contains", &config_only_resolver(), "key-value");
assert_eq!(
rendered,
contains_line("key-value"),
"no IR means no rename knowledge; the literal must be emitted verbatim, got: {rendered}"
);
}
#[test]
fn wildcard_containment_reconciles_the_renamed_variant() {
let rendered = render_wildcard("contains", "key-value");
assert!(
rendered.contains("format!(\"{:?}\", e.link_type)"),
"the wildcard element must stringify through Debug for this test to exercise the enum \
arm at all, got: {rendered}"
);
assert!(
rendered.contains(".contains(r#\"KeyValue\"#)"),
"the wildcard needle must be the Rust identifier, got: {rendered}"
);
assert!(
!rendered.contains("r#\"key-value\"#"),
"no occurrence of the wire spelling may survive in the emitted needle, got: {rendered}"
);
}
#[test]
fn wildcard_containment_keeps_its_controls() {
let negated = render_wildcard("not_contains", "key-value");
assert!(
negated.contains(".contains(r#\"KeyValue\"#)") && negated.contains("assert!(!"),
"`not_contains` must negate a predicate that searches for the identifier, got: {negated}"
);
let collision = render_wildcard("contains", "Plain");
assert!(
collision.contains(".contains(r#\"Plain\"#)"),
"a wire value that is another variant's identifier must not be translated, got: {collision}"
);
let unknown = render_wildcard("contains", "not-a-variant");
assert!(
unknown.contains(".contains(r#\"not-a-variant\"#)"),
"an unrecognized fixture value must not be rewritten, got: {unknown}"
);
}
#[test]
fn the_bare_element_half_does_not_resolve_the_enum_on_its_own() {
let resolver = wildcard_enum_resolver();
assert!(
resolver.is_enum("links[].link_type"),
"the full wildcard path must resolve to the enum"
);
assert!(
!resolver.is_enum("link_type"),
"the bare leaf must not resolve against the root type, or the wildcard caller could key \
the rename lookup on the wrong owner"
);
assert_eq!(
resolver.enum_variant_for_wire_value("link_type", "key-value"),
None,
"the bare leaf must yield no rename"
);
assert_eq!(
resolver.enum_variant_for_wire_value("links[].link_type", "key-value"),
Some("KeyValue"),
"the full path must yield the renamed variant"
);
}