forked from 1b-agent/opencadstudio-upstream
fix(bedit): refuse entities_create and redefine while a session is open
This commit is contained in:
1 parent
f7bd0ca5ee
commit
b221ce5e33
5 files changed
+301
No files matched your search
@@ -54,6 +54,11 @@ fn main_depth(app: &OpenCADStudio) -> usize {
|
||||
.unwrap_or(tab.history.undo_stack.len())
|
||||
}
|
||||
|
||||
fn feed(app: &mut OpenCADStudio, text: &str) {
|
||||
let _ = app.update(Message::CommandInput(text.to_string()));
|
||||
let _ = app.update(Message::CommandSubmit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_undo_preserves_imported_native_networks_when_constraints_are_unchanged() {
|
||||
use codec::objects::{
|
||||
@@ -922,6 +927,182 @@ fn close_all_resumes_after_discarding_a_dirty_block() {
|
||||
assert!(app.pending_tab_closes.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explode_inside_session_edits_the_definition_and_marks_dirty() {
|
||||
let (mut app, reference) = fixture();
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.define_block_raw(
|
||||
"CHILD",
|
||||
Vector3::ZERO,
|
||||
vec![EntityType::Line(Line::from_points(
|
||||
Vector3::ZERO,
|
||||
Vector3::new(3.0, 0.0, 0.0),
|
||||
))],
|
||||
);
|
||||
begin(&mut app, reference);
|
||||
let _ = app.dispatch_command("-INSERT");
|
||||
feed(&mut app, "CHILD");
|
||||
feed(&mut app, "0,5");
|
||||
feed(&mut app, "1");
|
||||
feed(&mut app, "1");
|
||||
feed(&mut app, "0");
|
||||
app.finish_pending_history(i);
|
||||
let before = members(&app);
|
||||
assert_eq!(before.len(), 2, "the nested INSERT joins the definition");
|
||||
let insert_handle = *before.last().unwrap();
|
||||
assert!(
|
||||
matches!(
|
||||
app.tabs[i].scene.document.get_entity(insert_handle),
|
||||
Some(EntityType::Insert(_))
|
||||
),
|
||||
"expected the member just added to be the INSERT"
|
||||
);
|
||||
let depth = main_depth(&app);
|
||||
app.tabs[i].scene.select_entity(insert_handle, true);
|
||||
let _ = app.dispatch_command("EXPLODE");
|
||||
assert!(
|
||||
app.block_edit_dirty(i),
|
||||
"the explode marks the session dirty"
|
||||
);
|
||||
assert_eq!(main_depth(&app), depth, "the main stack is untouched");
|
||||
let _ = app.dispatch_command("BSAVE");
|
||||
let saved = members(&app);
|
||||
assert!(
|
||||
saved
|
||||
.iter()
|
||||
.all(|h| app.tabs[i].scene.document.get_entity(*h).is_some()),
|
||||
"the saved definition carries no dangling member handle: {saved:?}"
|
||||
);
|
||||
assert_eq!(saved.len(), 2, "the INSERT is replaced by its piece");
|
||||
assert!(
|
||||
!saved.contains(&insert_handle),
|
||||
"the exploded INSERT leaves the saved definition"
|
||||
);
|
||||
assert!(
|
||||
saved.iter().any(|h| {
|
||||
matches!(
|
||||
app.tabs[i].scene.document.get_entity(*h),
|
||||
Some(EntityType::Line(_))
|
||||
)
|
||||
}),
|
||||
"the child line lands as a member of the edited block"
|
||||
);
|
||||
let _ = app.dispatch_command("BEDIT_DISCARD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xclip_inside_session_stays_in_the_definition() {
|
||||
let (mut app, reference) = fixture();
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.define_block_raw(
|
||||
"CHILD",
|
||||
Vector3::ZERO,
|
||||
vec![EntityType::Line(Line::from_points(
|
||||
Vector3::ZERO,
|
||||
Vector3::new(3.0, 0.0, 0.0),
|
||||
))],
|
||||
);
|
||||
begin(&mut app, reference);
|
||||
let _ = app.dispatch_command("-INSERT");
|
||||
feed(&mut app, "CHILD");
|
||||
feed(&mut app, "0,5");
|
||||
feed(&mut app, "1");
|
||||
feed(&mut app, "1");
|
||||
feed(&mut app, "0");
|
||||
app.finish_pending_history(i);
|
||||
let before = members(&app);
|
||||
let insert_handle = *before.last().unwrap();
|
||||
let depth = main_depth(&app);
|
||||
app.tabs[i].scene.select_entity(insert_handle, true);
|
||||
let _ = app.dispatch_command("XCLIP");
|
||||
feed(&mut app, "N");
|
||||
feed(&mut app, "R");
|
||||
feed(&mut app, "0,0");
|
||||
feed(&mut app, "10,10");
|
||||
app.finish_pending_history(i);
|
||||
assert!(
|
||||
crate::scene::pick::xclip::filter_handle(&app.tabs[i].scene.document, insert_handle)
|
||||
.is_some(),
|
||||
"the clip filter attaches to the in-block reference"
|
||||
);
|
||||
assert!(
|
||||
members(&app).contains(&insert_handle),
|
||||
"the clipped reference stays a member of the definition"
|
||||
);
|
||||
assert!(app.block_edit_dirty(i), "the clip marks the session dirty");
|
||||
assert_eq!(main_depth(&app), depth, "the main stack is untouched");
|
||||
let _ = app.dispatch_command("BEDIT_DISCARD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attdef_inside_session_lands_in_the_definition_and_undo_stays_local() {
|
||||
let (mut app, reference) = fixture();
|
||||
let i = app.active_tab;
|
||||
let depth = main_depth(&app);
|
||||
begin(&mut app, reference);
|
||||
assert_eq!(members(&app).len(), 1);
|
||||
let _ = app.dispatch_command("-ATTDEF");
|
||||
feed(&mut app, ""); // modes: done
|
||||
feed(&mut app, "TAG1");
|
||||
feed(&mut app, "prompt text");
|
||||
feed(&mut app, ""); // default value
|
||||
feed(&mut app, ""); // single line
|
||||
feed(&mut app, "0,5");
|
||||
feed(&mut app, "2");
|
||||
feed(&mut app, "0");
|
||||
app.finish_pending_history(i);
|
||||
let after = members(&app);
|
||||
assert_eq!(after.len(), 2, "the ATTDEF joins the definition");
|
||||
assert!(
|
||||
matches!(
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*after.last().unwrap()),
|
||||
Some(EntityType::AttributeDefinition(_))
|
||||
),
|
||||
"the new member is the ATTDEF"
|
||||
);
|
||||
assert!(
|
||||
app.block_edit_dirty(i),
|
||||
"the ATTDEF marks the session dirty"
|
||||
);
|
||||
assert_eq!(main_depth(&app), depth, "the main stack is untouched");
|
||||
app.undo_steps(1);
|
||||
assert_eq!(members(&app).len(), 1, "session undo removes the ATTDEF");
|
||||
assert_eq!(main_depth(&app), depth, "the main stack is still untouched");
|
||||
let _ = app.dispatch_command("BEDIT_DISCARD");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_after_session_undo_does_not_write_a_dangling_member() {
|
||||
let (mut app, reference) = fixture();
|
||||
begin(&mut app, reference);
|
||||
circle(&mut app, 1.0);
|
||||
assert_eq!(members(&app).len(), 2);
|
||||
app.undo_steps(1);
|
||||
assert_eq!(members(&app).len(), 1);
|
||||
let depth = main_depth(&app);
|
||||
let _ = app.dispatch_command("BSAVE");
|
||||
assert!(app.tabs[app.active_tab].active_block_edit.is_some());
|
||||
let saved = members(&app);
|
||||
assert!(
|
||||
saved.iter().all(|h| app.tabs[app.active_tab]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(*h)
|
||||
.is_some()),
|
||||
"save after a session undo must not write a member handle whose entity is gone: {saved:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
main_depth(&app),
|
||||
depth,
|
||||
"a clean save commits no new drawing step"
|
||||
);
|
||||
let _ = app.dispatch_command("BEDIT_DISCARD");
|
||||
assert_eq!(members(&app).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked_bedit_no_selection_opens_edit_block_definition_dialog() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
|
||||
Reference in new issue
Block a user