Files
cadcraft/crates/engine/tests/it/xdata_follows_entity.rs
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

121 lines
5.0 KiB
Rust

//! Other applications' xdata is part of its object, as in AutoCAD: COPY, MIRROR, ARRAY and paste
//! give the copies the same xdata, so it is saved on them too; the pieces of an exploded object
//! don't keep it.
use cadcraft_engine::Session;
use cadcraft_engine::doc::{Drawing, EntityKind, Handle, RawTag};
use serde_json::json;
/// A LINE with a hyperlink (`PE_URL`) and another application's data, and an LWPOLYLINE with
/// the same, read from DXF.
const DXF: &str = "0\nSECTION\n2\nENTITIES\n\
0\nLINE\n5\n2A\n8\n0\n10\n0\n20\n0\n30\n0\n11\n10\n21\n0\n31\n0\n\
1001\nPE_URL\n1000\nhttps://example.com/door-7\n1002\n{\n1000\nDoor 7\n1002\n}\n\
1001\nACME_TAGS\n1000\nD-07\n1070\n3\n1040\n2.5\n\
0\nLWPOLYLINE\n5\n2B\n8\n0\n90\n3\n70\n0\n10\n0\n20\n5\n10\n5\n20\n5\n10\n5\n20\n10\n\
1001\nACME_TAGS\n1000\nP-01\n\
0\nENDSEC\n0\nEOF\n";
fn tags(xdata: &[RawTag]) -> Vec<(i32, String)> {
xdata.iter().map(|t| (t.code, t.value.trim().to_string())).collect()
}
fn session() -> (Session, Handle, Handle) {
let d = cadcraft_io::read_dxf(DXF.as_bytes()).unwrap();
let mut s = Session::empty();
s.open_drawing(d, "xdata", None);
let doc = s.doc().unwrap();
let find = |line: bool| doc.model.iter().find(|e| matches!(e.kind, EntityKind::Line(_)) == line).map(|e| e.handle).unwrap();
let (l, p) = (find(true), find(false));
(s, l, p)
}
fn xdata_of(d: &Drawing, h: Handle) -> Vec<(i32, String)> {
tags(&d.entity(h).unwrap().common.xdata)
}
fn new_handles(s: &Session, before: &[Handle]) -> Vec<Handle> {
s.doc().unwrap().model.iter().map(|e| e.handle).filter(|h| !before.contains(h)).collect()
}
fn model_handles(s: &Session) -> Vec<Handle> {
s.doc().unwrap().model.iter().map(|e| e.handle).collect()
}
#[test]
fn reading_puts_xdata_on_the_entity() {
let (s, l, p) = session();
let d = s.doc().unwrap();
let x = xdata_of(d, l);
assert_eq!(x.first(), Some(&(1001, "PE_URL".to_string())));
assert!(x.contains(&(1001, "ACME_TAGS".to_string())) && x.contains(&(1000, "D-07".to_string())));
assert_eq!(xdata_of(d, p), vec![(1001, "ACME_TAGS".to_string()), (1000, "P-01".to_string())]);
assert!(d.foreign.entities.values().all(|x| x.xdata.is_empty()), "entity xdata is not kept twice");
}
#[test]
fn copies_keep_the_xdata() {
let (mut s, l, _) = session();
let want = xdata_of(s.doc().unwrap(), l);
let before = model_handles(&s);
s.execute("copy", &json!({ "handles": [l.hex()], "delta": [0, 20] })).unwrap();
let copied = new_handles(&s, &before);
assert_eq!(copied.len(), 1);
assert_eq!(xdata_of(s.doc().unwrap(), copied[0]), want, "COPY");
let before = model_handles(&s);
s.execute("mirror", &json!({ "handles": [l.hex()], "p1": [0, -1], "p2": [10, -1] })).unwrap();
let mirrored = new_handles(&s, &before);
assert_eq!(mirrored.len(), 1);
assert_eq!(xdata_of(s.doc().unwrap(), mirrored[0]), want, "MIRROR");
let before = model_handles(&s);
s.execute("arrayrect", &json!({ "handles": [l.hex()], "rows": 1, "cols": 3, "colSpacing": 20, "associative": false })).unwrap();
let arrayed = new_handles(&s, &before);
assert_eq!(arrayed.len(), 2);
for h in arrayed {
assert_eq!(xdata_of(s.doc().unwrap(), h), want, "ARRAYRECT");
}
s.execute("copyclip", &json!({ "handles": [l.hex()] })).unwrap();
let before = model_handles(&s);
s.execute("pasteclip", &json!({ "at": [100, 100] })).unwrap();
let pasted = new_handles(&s, &before);
assert_eq!(pasted.len(), 1);
assert_eq!(xdata_of(s.doc().unwrap(), pasted[0]), want, "PASTECLIP");
// Undo takes the copy away; the original keeps its xdata.
s.execute("u", &json!({})).unwrap();
assert!(s.doc().unwrap().entity(pasted[0]).is_none());
assert_eq!(xdata_of(s.doc().unwrap(), l), want);
}
#[test]
fn a_copy_saves_its_xdata() {
let (mut s, l, _) = session();
s.execute("copy", &json!({ "handles": [l.hex()], "delta": [0, 20] })).unwrap();
let text = cadcraft_io::write_dxf(s.doc().unwrap());
let groups: Vec<&str> = text.lines().map(str::trim).collect();
let count = |code: &str, value: &str| groups.chunks(2).filter(|g| *g == [code, value]).count();
assert_eq!(count("1001", "PE_URL"), 2, "on the line and on its copy");
assert_eq!(count("1000", "https://example.com/door-7"), 2);
assert_eq!((count("2", "PE_URL"), count("2", "ACME_TAGS")), (1, 1), "the applications stay registered");
let back = cadcraft_io::read_dxf(text.as_bytes()).unwrap();
let lines: Vec<_> = back.model.iter().filter(|e| matches!(e.kind, EntityKind::Line(_))).collect();
assert_eq!(lines.len(), 2);
assert_eq!(tags(&lines[0].common.xdata), tags(&lines[1].common.xdata));
}
#[test]
fn exploded_pieces_do_not_keep_the_xdata() {
let (mut s, _, p) = session();
let before = model_handles(&s);
s.execute("explode", &json!({ "handles": [p.hex()] })).unwrap();
let pieces = new_handles(&s, &before);
assert_eq!(pieces.len(), 2);
for h in pieces {
assert!(s.doc().unwrap().entity(h).unwrap().common.xdata.is_empty());
}
}