test: failing regression test for #265: Renaming a pattern copy renumbers the remaining copies (#280)

* test: reproduce #265 (Renaming a pattern copy renumbers the remaining copies)

* fix: renaming a body keeps its sibling bodies' names (#265)

body.rename pinned only the names of the feature's bodies before the renamed one, so unpinned
pattern copies after it were renumbered into the freed name. Pin every sibling's current name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

---------

Co-authored-by: bherbruck <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-12 10:59:08 -04:00
1 parent 93fd18eaad
commit aea16f2b5a
2 files changed
+17 -2

No files matched your search

+3 -2
View File
@@ -445,13 +445,14 @@ fn rename_body(s: &mut Session, p: &Value) -> Result<Value> {
if st.body(name).is_some() {
return Err(bad(cmd, format!("another body is called `{name}`")));
}
// The body is the k-th new body of its feature; the names before it are kept as they are.
// The body is the k-th new body of its feature; its siblings keep the names they have now
// (unpinned pattern copies would otherwise be renumbered into the freed name, #265).
let fid = b.feature;
let siblings: Vec<String> = st.bodies.iter().filter(|x| x.feature == fid).map(|x| x.name.clone()).collect();
let k = siblings.iter().position(|n| *n == old).unwrap_or(0);
let d = s.doc_mut();
let f = d.feature_mut(fid).ok_or_else(|| bad(cmd, "the body's feature is gone"))?;
while f.body_names.len() <= k {
while f.body_names.len() < siblings.len().max(k + 1) {
let i = f.body_names.len();
f.body_names.push(siblings.get(i).cloned().unwrap_or_default());
}
+14
View File
@@ -2478,6 +2478,20 @@ fn special_files_are_refused_by_every_file_command() {
let _ = std::fs::remove_dir_all(&dir);
}
/// Renaming one pattern copy leaves the other copies' names alone (#265).
#[test]
fn renaming_a_pattern_copy_keeps_the_other_copies_names() {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10, "operation": "new_body", "body_name": "X"}));
run(&mut s, "solid.pattern.rectangular", json!({"bodies": ["X"], "dir1": [1, 0, 0], "count1": 3, "spacing1": 20}));
run(&mut s, "body.rename", json!({"body": "X (2)", "name": "Second"}));
let third = run(&mut s, "inspect.measure", json!({"bodies": ["X (3)"]}));
assert_eq!(third["bodies"][0]["bbox"]["min"][0].as_f64().map(f64::round), Some(40.0), "X (3) is still the copy at x = 40: {third}");
run(&mut s, "body.rename", json!({"body": "X (3)", "name": "Third"}));
let names: Vec<String> = s.model.state().bodies.iter().map(|b| b.name.clone()).collect();
assert_eq!(names, ["X", "Second", "Third"]);
}
/// Parameters that work are listed in the command's params doc, which is all agents see (#271).
#[test]
fn params_docs_list_working_parameters() {