Files
cadcraft/crates/engine/tests/it/break_join_locked_layers.rs
T
aiongg 9cf115e808 CI: one test binary per crate, nextest, line tables only (#1989)
* Build: line tables only in dev, no debug info for dependencies

* Tests: one integration-test binary per crate

Each crate's tests/*.rs becomes a module of tests/it/main.rs. Files that
install process-wide state (the file io hooks, user aliases, SAVETIME and
SAVEFILEPATH, user linetype and pattern files, registered fonts) stay their
own test binaries.

* xtask ci: nextest, tests without examples, one wasm check call
2026-10-23 13:34:26 -04:00

136 lines
5.7 KiB
Rust

//! BREAK, BREAKATPOINT and JOIN leave objects on locked layers alone (#727).
use cadcraft_engine::geom::Vec2;
use cadcraft_engine::{Input, Session};
use serde_json::{Value, json};
fn h(r: &Value) -> String {
r["handle"].as_str().or_else(|| r["handles"][0].as_str()).unwrap().to_string()
}
/// Everything in the drawing a locked-layer edit could disturb.
fn snapshot(s: &Session) -> String {
let d = s.doc().unwrap();
format!("{:?}", d.model.iter().map(|e| (e.handle, e.common.layer.clone(), e.kind.clone())).collect::<Vec<_>>())
}
fn set_lock(s: &mut Session, locked: bool) {
s.execute("layer.set", &json!({"name": "0", "locked": locked})).unwrap();
}
fn line(s: &mut Session, a: [f64; 2], b: [f64; 2]) -> String {
h(&s.execute("line", &json!({"points": [a, b]})).unwrap())
}
fn lines_ab(s: &mut Session) -> (String, String) {
(line(s, [0.0, 0.0], [10.0, 0.0]), line(s, [10.0, 0.0], [20.0, 0.0]))
}
#[test]
fn break_and_breakatpoint_refuse_a_locked_line() {
let mut s = Session::new();
let a = line(&mut s, [0.0, 0.0], [10.0, 0.0]);
let control = line(&mut s, [0.0, 5.0], [10.0, 5.0]);
set_lock(&mut s, true);
let before = snapshot(&s);
assert!(s.execute("break", &json!({"handle": a, "p1": [3, 0], "p2": [7, 0]})).is_err());
assert!(s.execute("breakatpoint", &json!({"handle": a, "at": [5, 0]})).is_err());
// Selection fallback reaches the same guard.
s.execute("select", &json!({"handles": [a]})).unwrap();
assert!(s.execute("break", &json!({"p1": [3, 0], "p2": [7, 0]})).is_err());
assert!(s.execute("breakatpoint", &json!({"at": [5, 0]})).is_err());
assert_eq!(snapshot(&s), before);
// Unlocked, the same edits work, and undo and redo them.
set_lock(&mut s, false);
let unlocked = snapshot(&s);
let r = s.execute("break", &json!({"handle": a, "p1": [3, 0], "p2": [7, 0]})).unwrap();
assert_eq!(r["handles"].as_array().unwrap().len(), 2);
assert!(s.doc().unwrap().entity(cadcraft_engine::doc::Handle::parse_hex(&a).unwrap()).is_none());
assert!(s.doc().unwrap().entity(cadcraft_engine::doc::Handle::parse_hex(&control).unwrap()).is_some());
let broken = snapshot(&s);
s.execute("undo", &json!({})).unwrap();
assert_eq!(snapshot(&s), unlocked);
s.execute("redo", &json!({})).unwrap();
assert_eq!(snapshot(&s), broken);
}
#[test]
fn break_refuses_a_locked_arc_and_circle() {
let mut s = Session::new();
let arc = h(&s.execute("arc", &json!({"center": [0, 0], "radius": 5, "start": 0, "end": 90})).unwrap());
let circle = h(&s.execute("circle", &json!({"center": [20, 0], "radius": 3})).unwrap());
set_lock(&mut s, true);
let before = snapshot(&s);
assert!(s.execute("break", &json!({"handle": arc, "p1": [5, 0], "p2": [0, 5]})).is_err());
assert!(s.execute("break", &json!({"handle": circle, "p1": [23, 0], "p2": [20, 3]})).is_err());
assert_eq!(snapshot(&s), before);
}
#[test]
fn join_refuses_when_any_line_is_locked() {
let mut s = Session::new();
let (a, b) = lines_ab(&mut s);
set_lock(&mut s, true);
let before = snapshot(&s);
assert!(s.execute("join", &json!({"handles": [a, b]})).is_err());
s.execute("select", &json!({"handles": [a, b]})).unwrap();
assert!(s.execute("join", &json!({})).is_err());
assert_eq!(snapshot(&s), before);
set_lock(&mut s, false);
let r = s.execute("join", &json!({"handles": [a, b]})).unwrap();
assert!(r["handle"].is_string());
assert_eq!(s.doc().unwrap().model.len(), 1);
}
#[test]
fn join_keeps_a_locked_original_in_a_mixed_layer_pair() {
for locked_first in [true, false] {
let mut s = Session::new();
s.execute("layer.new", &json!({"name": "L"})).unwrap();
// The first line goes on the soon-locked layer 0, the second on "L" (or the reverse).
let on_l = |s: &mut Session, a: [f64; 2], b: [f64; 2]| {
s.execute("layer.current", &json!({"name": "L"})).unwrap();
let r = line(s, a, b);
s.execute("layer.current", &json!({"name": "0"})).unwrap();
r
};
let (a, b) = if locked_first {
(line(&mut s, [0.0, 0.0], [10.0, 0.0]), on_l(&mut s, [10.0, 0.0], [20.0, 0.0]))
} else {
(on_l(&mut s, [0.0, 0.0], [10.0, 0.0]), line(&mut s, [10.0, 0.0], [20.0, 0.0]))
};
let locked = if locked_first { &a } else { &b };
set_lock(&mut s, true);
let before = snapshot(&s);
assert!(s.execute("join", &json!({"handles": [a, b]})).is_err());
assert_eq!(snapshot(&s), before, "locked_first = {locked_first}");
assert!(s.doc().unwrap().entity(cadcraft_engine::doc::Handle::parse_hex(locked).unwrap()).is_some());
}
}
#[test]
fn interactive_break_and_join_refuse_locked_lines() {
let mut s = Session::new();
let (a, b) = lines_ab(&mut s);
set_lock(&mut s, true);
let before = snapshot(&s);
// BREAK: pick the line, then the second break point.
s.start("break").unwrap();
s.input(Input::Point(Vec2::new(3.0, 0.0))).unwrap();
let _ = s.input(Input::Point(Vec2::new(7.0, 0.0)));
assert!(s.log.last().is_some_and(|m| m.contains("locked layer")), "{:?}", s.log.last());
assert_eq!(snapshot(&s), before);
// JOIN on a preselected pair reports the refusal on the command line.
s.cancel();
s.execute("select", &json!({"handles": [a, b]})).unwrap();
s.start("join").unwrap();
assert!(s.log.last().is_some_and(|m| m.contains("locked layer")), "{:?}", s.log.last());
assert_eq!(snapshot(&s), before);
// Unlocked, the interactive commands still edit.
set_lock(&mut s, false);
s.start("break").unwrap();
s.input(Input::Point(Vec2::new(3.0, 0.0))).unwrap();
s.input(Input::Point(Vec2::new(7.0, 0.0))).unwrap();
assert_ne!(snapshot(&s), before);
}