use super::*;
use crate::test_support::isolate_state_dir;
fn hook(entries: Vec<serde_json::Value>) -> EditorHook {
EditorHook::new("unused.jsonl".to_string(), entries)
}
fn world_with_fields() -> World {
let mut world = World::new();
for id in panel::all_field_ids()
.into_iter()
.chain(form_panel::all_field_ids())
{
world.add_component(crate::components::TextInput {
asset_id: id,
..Default::default()
});
}
world
}
fn prefab_hook(patch: serde_json::Value) -> EditorHook {
isolate_state_dir();
let mut h = hook(vec![
serde_json::json!({"name":"box","type":"ProceduralMesh","args":{"generator":"box"}}),
serde_json::json!({"name":"pair","type":"Prefab","args":{"props":[
{"name":"a","kind":"prop","mesh":"box","position":[1.0,0.0,0.0]}]}}),
serde_json::json!({"name":"i1","type":"Prop","args":{"prefab":"pair","position":[10.0,0.0,0.0]}}),
serde_json::json!({"name":"i1_a","type":"Prop","args": patch}),
]);
h.panel_open = true;
h
}
fn entity_option(h: &EditorHook, label_prefix: &str) -> usize {
let labels: Vec<String> = h
.entity_menu_options()
.into_iter()
.map(|(_, l)| l)
.collect();
labels
.iter()
.position(|l| l.starts_with(label_prefix))
.unwrap_or_else(|| panic!("no entity option starting {label_prefix:?}, got {labels:?}"))
}
fn entry<'a>(h: &'a EditorHook, name: &str) -> Option<&'a serde_json::Value> {
h.entries.iter().find(|e| entry_name(e) == Some(name))
}
#[test]
fn apply_all_writes_the_mappable_paths_and_keeps_the_rest() {
let _guard = crate::test_support::lock();
let mut h = prefab_hook(serde_json::json!({
"position": [5.0, 0.0, 0.0],
"cull_distance": 42.0
}));
let mut world = world_with_fields();
h.open_asset_form("i1_a", &mut world);
let k = entity_option(&h, "Apply all to Prefab");
h.apply_form(FormAction::OpenEntityMenu, &mut world);
h.apply_form(FormAction::PickEntityOption(k), &mut world);
let def = entry(&h, "pair").expect("the definition stands");
assert_eq!(
def["args"]["props"][0]["position"],
serde_json::json!([-5.0, 0.0, 0.0]),
"the mappable path landed in the definition"
);
let patch = entry(&h, "i1_a").expect("the patch line survives its unmappable field");
assert!(
patch["args"].get("position").is_none(),
"the applied path left the patch"
);
assert_eq!(patch["args"]["cull_distance"], 42.0);
assert!(
h.form_error
.as_ref()
.is_some_and(|e| e.contains("cull_distance")),
"the kept path is reported, got {:?}",
h.form_error
);
}
#[test]
fn apply_all_over_a_fully_mappable_patch_removes_the_line() {
let _guard = crate::test_support::lock();
let mut h = prefab_hook(serde_json::json!({"position": [5.0, 0.0, 0.0]}));
let mut world = world_with_fields();
let before = h.entries.len();
h.open_asset_form("i1_a", &mut world);
let k = entity_option(&h, "Apply all to Prefab");
h.apply_form(FormAction::OpenEntityMenu, &mut world);
h.apply_form(FormAction::PickEntityOption(k), &mut world);
assert_eq!(
h.entries.len(),
before - 1,
"the emptied patch line is gone"
);
assert!(h.form_error.is_none(), "nothing was kept back");
h.undo(&mut world);
assert_eq!(h.entries.len(), before);
assert_eq!(
entry(&h, "pair").unwrap()["args"]["props"][0]["position"],
serde_json::json!([1.0, 0.0, 0.0]),
"one undo restores the definition and the patch line together"
);
}
fn baseline_of(h: &mut EditorHook, name: &str, key: &str) -> serde_json::Value {
let mut world = world_with_fields();
h.open_asset_form(name, &mut world);
h.form_template
.as_ref()
.expect("a template-derived form")
.baseline
.get(key)
.cloned()
.unwrap_or(serde_json::Value::Null)
}
#[test]
fn minimize_removes_a_patch_that_matches_the_template() {
let _guard = crate::test_support::lock();
let mut probe = prefab_hook(serde_json::json!({"position": [5.0, 0.0, 0.0]}));
let inherited = baseline_of(&mut probe, "i1_a", "position");
let mut h = prefab_hook(serde_json::json!({ "position": inherited }));
let mut world = world_with_fields();
let before = h.entries.len();
h.open_asset_form("i1_a", &mut world);
let k = entity_option(&h, "Minimize override");
h.apply_form(FormAction::OpenEntityMenu, &mut world);
h.apply_form(FormAction::PickEntityOption(k), &mut world);
assert_eq!(h.entries.len(), before - 1, "a no-op patch is not a patch");
assert!(entry(&h, "i1_a").is_none());
}
#[test]
fn minimize_keeps_only_the_fields_that_differ() {
let _guard = crate::test_support::lock();
let mut probe = prefab_hook(serde_json::json!({"position": [5.0, 0.0, 0.0]}));
let inherited = baseline_of(&mut probe, "i1_a", "position");
let mut h = prefab_hook(serde_json::json!({
"position": inherited,
"cull_distance": 42.0
}));
let mut world = world_with_fields();
let before = h.entries.len();
h.open_asset_form("i1_a", &mut world);
let k = entity_option(&h, "Minimize override");
h.apply_form(FormAction::OpenEntityMenu, &mut world);
h.apply_form(FormAction::PickEntityOption(k), &mut world);
assert_eq!(
h.entries.len(),
before,
"the line stays for the real override"
);
let args = &entry(&h, "i1_a").expect("the patch line").clone()["args"];
assert_eq!(args["cull_distance"], 42.0);
assert!(
args.get("position").is_none(),
"the inherited field was stripped, got {args}"
);
}
#[test]
fn materializing_a_preset_prefab_authors_it_as_a_world_line() {
let _guard = crate::test_support::lock();
isolate_state_dir();
let dir = concinnity_host::store::paths::assets_dir()
.expect("the test installs a state dir")
.join("prefabs");
std::fs::create_dir_all(&dir).expect("preset dir");
std::fs::write(
dir.join("cn_test_prefab.json"),
r#"{"args":{"props":[{"name":"a","kind":"prop","mesh":"box","position":[2.0,0.0,0.0]}]}}"#,
)
.expect("preset file");
let mut h = hook(vec![
serde_json::json!({"name":"box","type":"ProceduralMesh","args":{"generator":"box"}}),
serde_json::json!({"name":"i1","type":"Prop","args":{"prefab":"cn_test_prefab","position":[10.0,0.0,0.0]}}),
serde_json::json!({"name":"i1_a","type":"Prop","args":{"position":[5.0,0.0,0.0]}}),
]);
h.panel_open = true;
let mut world = world_with_fields();
h.open_asset_form("i1_a", &mut world);
let k = entity_option(&h, "Materialize Prefab");
h.apply_form(FormAction::OpenEntityMenu, &mut world);
h.apply_form(FormAction::PickEntityOption(k), &mut world);
let def = entry(&h, "cn_test_prefab").expect("the definition was authored");
assert_eq!(def["type"], "Prefab");
assert_eq!(
def["args"]["props"][0]["position"],
serde_json::json!([2.0, 0.0, 0.0]),
"the preset's entries came across verbatim"
);
let _ = std::fs::remove_file(dir.join("cn_test_prefab.json"));
}
#[test]
fn jump_to_override_cycles_through_the_marked_fields() {
let _guard = crate::test_support::lock();
let mut h = prefab_hook(serde_json::json!({
"position": [5.0, 0.0, 0.0],
"scale": [2.0, 2.0, 2.0]
}));
let mut world = world_with_fields();
h.open_asset_form("i1_a", &mut world);
let marked: Vec<usize> = h
.form_override_marks()
.expect("marks for a template form")
.iter()
.enumerate()
.filter(|(_, m)| **m != overrides::FieldOrigin::Inherited)
.map(|(i, _)| i)
.collect();
assert!(
marked.len() >= 2,
"both pinned fields are marked, got {marked:?}"
);
let max = h.form_fields.len().saturating_sub(h.form_window());
let mut seen = Vec::new();
for _ in 0..marked.len() + 1 {
h.jump_to_override(&mut world);
seen.push(h.form_scroll);
}
assert!(
seen.iter().all(|s| marked.contains(s) || *s == max),
"every stop is a marked field (or the scroll clamp), got {seen:?} for {marked:?}"
);
assert!(
seen.first() == seen.last() || seen.len() > marked.len(),
"the walk wraps rather than sticking, got {seen:?}"
);
}
#[test]
fn jump_to_override_is_a_no_op_without_marks() {
let _guard = crate::test_support::lock();
let mut h = prefab_hook(serde_json::json!({}));
let mut world = world_with_fields();
h.open_asset_form("i1_a", &mut world);
h.form_scroll = 0;
h.jump_to_override(&mut world);
assert_eq!(h.form_scroll, 0);
}