mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 19:36:49 -06:00
* 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
105 lines
5.4 KiB
Rust
105 lines
5.4 KiB
Rust
//! The `entities` query (control channel `engine.execute`, MCP `query_entities`) rejects malformed
|
|
//! filters instead of ignoring them and returning everything.
|
|
|
|
use cadcraft_engine::Session;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn malformed_filters_are_errors() {
|
|
let mut s = Session::new();
|
|
s.execute("circle", &json!({"center": [0, 0], "radius": 1})).unwrap();
|
|
s.execute("circle", &json!({"center": [100, 100], "radius": 1})).unwrap();
|
|
for p in [
|
|
json!({"window": [-5, -5, 5, 5]}),
|
|
json!({"window": [[-5, -5]]}),
|
|
json!({"window": [[-5, -5], [5, 5], [9, 9]]}),
|
|
json!({"window": {"min": [-5, -5], "max": [5, 5]}}),
|
|
json!({"window": "x"}),
|
|
json!({"type": 7}),
|
|
json!({"layer": ["0"]}),
|
|
json!({"limit": -1}),
|
|
json!({"offset": "1"}),
|
|
json!({"crossing": "yes"}),
|
|
json!({"selected": "true"}),
|
|
json!({"selected": 1}),
|
|
json!({"selected": []}),
|
|
json!({"selected": {}}),
|
|
] {
|
|
assert!(s.execute("entities", &p).is_err(), "{p} must be rejected");
|
|
}
|
|
let r = s.execute("entities", &json!({"window": [[-5, -5], [5, 5]]})).unwrap();
|
|
assert_eq!(r["count"], 1, "{r}");
|
|
}
|
|
|
|
#[test]
|
|
fn selection_filter_combines_before_paging_without_changing_state() {
|
|
let mut s = Session::new();
|
|
s.execute("layer.new", &json!({"name": "측정", "current": true})).unwrap();
|
|
for x in [0, 10, 20] {
|
|
s.execute("circle", &json!({"center": [x, 0], "radius": 2})).unwrap();
|
|
}
|
|
s.execute("line", &json!({"points": [[0, 0], [3, 4]]})).unwrap();
|
|
let all = s.execute("entities", &json!({})).unwrap();
|
|
let hs: Vec<_> = all["entities"].as_array().unwrap().iter().map(|e| e["handle"].clone()).collect();
|
|
// Leave a redo entry to prove the query doesn't clear redo or create an undo entry.
|
|
s.execute("circle", &json!({"center": [99, 99], "radius": 1})).unwrap();
|
|
s.undo().unwrap();
|
|
s.execute("select", &json!({"handles": [hs[0], hs[2], hs[3]]})).unwrap();
|
|
let before = s.state().unwrap().clone();
|
|
let report = s.execute("drawing.inspect", &json!({})).unwrap();
|
|
let selected = s.execute("entities", &json!({"selected": true})).unwrap();
|
|
assert_eq!(selected["count"], 3);
|
|
assert_eq!(selected["entities"][0]["handle"], hs[0]);
|
|
assert_eq!(selected["entities"][1]["handle"], hs[2]);
|
|
assert_eq!(s.execute("entities", &json!({"selected": false})).unwrap(), all);
|
|
assert_eq!(s.execute("entities", &json!({"selected": null})).unwrap(), all);
|
|
assert_eq!(s.execute("entities", &json!({})).unwrap(), all);
|
|
let page = s.execute("entities", &json!({"selected": true, "type": "circle", "layer": "측정", "limit": 1, "offset": 1})).unwrap();
|
|
assert_eq!(page, json!({"count": 2, "returned": 1, "offset": 1, "entities": [all["entities"][2]]}));
|
|
let window = json!({"selected": true, "type": "circle", "layer": "측정", "window": [[-3, -3], [3, 3]], "crossing": false});
|
|
assert_eq!(s.execute("entities", &window).unwrap()["count"], 1);
|
|
assert_eq!(s.execute("entities", &json!({"selected": true, "layer": "other"})).unwrap()["count"], 0);
|
|
assert_eq!(s.execute("entities", &json!({"selected": true, "limit": 0})).unwrap()["returned"], 0);
|
|
assert_eq!(s.execute("entities", &json!({"selected": true, "offset": 99})).unwrap()["returned"], 0);
|
|
assert!(s.execute("entities", &json!({"selected": "true"})).is_err());
|
|
assert_eq!(s.execute("drawing.inspect", &json!({})).unwrap(), report);
|
|
let after = s.state().unwrap();
|
|
assert!(std::sync::Arc::ptr_eq(&before.doc, &after.doc));
|
|
assert!(std::sync::Arc::ptr_eq(&before.saved, &after.saved));
|
|
assert_eq!(before.revision, after.revision);
|
|
assert_eq!(before.selection, after.selection);
|
|
assert_eq!(before.previous_selection, after.previous_selection);
|
|
assert_eq!(before.is_dirty(), after.is_dirty());
|
|
for (a, b) in [(&before.undo, &after.undo), (&before.redo, &after.redo)] {
|
|
assert_eq!(a.len(), b.len());
|
|
for (a, b) in a.iter().zip(b) {
|
|
assert_eq!(a.label, b.label);
|
|
assert_eq!(a.selection, b.selection);
|
|
assert!(std::sync::Arc::ptr_eq(&a.doc, &b.doc));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn selection_is_empty_by_default_and_limited_to_active_space() {
|
|
use cadcraft_doc::{Handle, Space};
|
|
let mut s = Session::new();
|
|
s.execute("circle", &json!({"center": [0, 0], "radius": 2})).unwrap();
|
|
let model = s.doc().unwrap().model.iter().next().unwrap().handle;
|
|
assert_eq!(s.execute("entities", &json!({"selected": true})).unwrap()["count"], 0);
|
|
s.execute("layout.set", &json!({"name": "Layout1"})).unwrap();
|
|
s.execute("circle", &json!({"center": [10, 10], "radius": 2})).unwrap();
|
|
let paper = s.doc().unwrap().space(&Space::Paper("Layout1".into())).unwrap().iter().last().unwrap().handle;
|
|
// Even stale, duplicate and foreign-space selection handles cannot leak entities.
|
|
s.state_mut().unwrap().selection = vec![model, paper, paper, Handle(u64::MAX)];
|
|
let page = s.execute("entities", &json!({"selected": true})).unwrap();
|
|
assert_eq!(page["count"], 1);
|
|
assert_eq!(page["entities"][0]["handle"], paper.hex());
|
|
s.state_mut().unwrap().space = Space::Model;
|
|
let page = s.execute("entities", &json!({"selected": true})).unwrap();
|
|
assert_eq!(page["count"], 1);
|
|
assert_eq!(page["entities"][0]["handle"], model.hex());
|
|
s.state_mut().unwrap().selection.clear();
|
|
assert_eq!(s.execute("entities", &json!({"selected": true})).unwrap()["entities"], json!([]));
|
|
}
|