mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
Commands: ids leave out an object listed with a group that holds it, as the selection does
Commands that read the selection through edit::selected_roots leave out an object whose group is also selected (edit::roots_of). Fourteen places that read `ids` kept an object listed together with its group. Given ids that list an object and a group that holds it, Make Repeat failed with "no such object", New Symbol failed or stored the object twice in the symbol, and object.transform moved it twice (20 pt for a 10 pt move). These places now pass explicit ids through edit::outermost, which drops an id whose ancestor is also listed, with the filter that roots_of runs on the selection (now edit::without_inner). Nothing else is dropped or replaced: unlike roots_of, it keeps layers and compound-path members. A list without nesting gives the same result as before. Commands that take both ids and the selection as listed, such as paint.setFill without item, are unchanged. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -82,3 +82,89 @@ fn ids_do_what_the_selection_does_in_every_command_that_takes_them() {
|
||||
}
|
||||
assert!(checked.iter().filter(|c| c.1).count() >= 20, "{checked:?}");
|
||||
}
|
||||
|
||||
/// Rectangles `a` and `b` grouped in `inner`, `inner` and a third rectangle grouped in `outer`, and
|
||||
/// a rectangle `d` apart, with nothing selected → `[a, inner, outer, d]`. The document has no
|
||||
/// creation date.
|
||||
fn doc_with_nested_groups() -> (Session, [u64; 4]) {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 200, "height": 200, "created": null})).unwrap();
|
||||
let rect =
|
||||
|s: &mut Session, x: u32| s.execute("shape.rectangle", &json!({"x": x, "y": 10, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap();
|
||||
let group = |s: &mut Session, ids: [u64; 2]| {
|
||||
s.execute("select.set", &json!({ "ids": ids })).unwrap();
|
||||
s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap()
|
||||
};
|
||||
let (a, b) = (rect(&mut s, 10), rect(&mut s, 40));
|
||||
let inner = group(&mut s, [a, b]);
|
||||
let c = rect(&mut s, 70);
|
||||
let outer = group(&mut s, [inner, c]);
|
||||
let d = rect(&mut s, 120);
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
(s, [a, inner, outer, d])
|
||||
}
|
||||
|
||||
/// The params besides `ids` that `cmd` needs to change these objects (`d`: the rectangle apart).
|
||||
fn params_for(cmd: &str, d: u64) -> serde_json::Value {
|
||||
match cmd {
|
||||
"object.transform" => json!({"matrix": [1, 0, 0, 1, 10, 0]}),
|
||||
"object.distort" => json!({"corners": [[0, 0], [150, 0], [150, 60], [0, 60]]}),
|
||||
"object.puppetWarp" => json!({"pins": [[10, 10], [100, 10]], "moved": [[10, 10], [100, 20]]}),
|
||||
"appearance.copyFrom" => json!({ "source": d }),
|
||||
_ => json!({}),
|
||||
}
|
||||
}
|
||||
|
||||
/// `cmd` run with the objects at `picks` of [`doc_with_nested_groups`] as `ids`, and with them
|
||||
/// selected: whether it succeeded, when both give the same result or the same error and the same
|
||||
/// document; else what each gave.
|
||||
fn ids_and_selection(cmd: &str, picks: &[usize]) -> std::result::Result<bool, String> {
|
||||
let (mut given, ids) = doc_with_nested_groups();
|
||||
let (mut selected, _) = doc_with_nested_groups();
|
||||
let list: Vec<u64> = picks.iter().map(|i| ids[*i]).collect();
|
||||
selected.execute("select.set", &json!({ "ids": list })).unwrap();
|
||||
let params = params_for(cmd, ids[3]);
|
||||
let mut with_ids = params.clone();
|
||||
with_ids["ids"] = json!(list);
|
||||
let (a, b) = (given.execute(cmd, &with_ids), selected.execute(cmd, ¶ms));
|
||||
let same_result = match (&a, &b) {
|
||||
(Ok(x), Ok(y)) => x == y,
|
||||
(Err(x), Err(y)) => x.to_string() == y.to_string(),
|
||||
_ => false,
|
||||
};
|
||||
let same_doc = match (given.doc(), selected.doc()) {
|
||||
(Ok(g), Ok(s)) => g.doc == s.doc,
|
||||
(g, s) => g.is_ok() == s.is_ok(),
|
||||
};
|
||||
if same_result && same_doc {
|
||||
return Ok(a.is_ok());
|
||||
}
|
||||
let docs = if same_doc { "" } else { "; the documents differ" };
|
||||
Err(format!("{cmd} with ids {list:?}: {:.160}; with them selected: {:.160}{docs}", format!("{a:?}"), format!("{b:?}")))
|
||||
}
|
||||
|
||||
/// Every command whose `ids` stand in for the selection: given an object together with a group that
|
||||
/// holds it (directly, or inside another group), it does what it does with those objects selected
|
||||
/// (the same result or the same error, and the same document). `edit::roots_of` drops such an
|
||||
/// object from the selection and keeps the group.
|
||||
#[test]
|
||||
fn ids_of_an_object_and_a_group_that_holds_it_do_what_that_selection_does() {
|
||||
let (mut failures, mut succeeded) = (vec![], 0);
|
||||
for spec in command_specs() {
|
||||
// `outer` and `d` are separate objects. Commands that need `ids`, or whose `ids` are Layers
|
||||
// panel rows, images or paths only, differ already there and are left out.
|
||||
if !cmd::params::takes_key(spec.params, "ids") || ids_and_selection(spec.id, &[2, 3]).is_err() {
|
||||
continue;
|
||||
}
|
||||
// Each list holds an object and a group around it: `a` and `inner`; `inner`, `a` and `outer`;
|
||||
// `a`, `outer` and `d`.
|
||||
for picks in [&[0, 1][..], &[1, 0, 2], &[0, 2, 3]] {
|
||||
match ids_and_selection(spec.id, picks) {
|
||||
Ok(ok) => succeeded += usize::from(ok),
|
||||
Err(e) => failures.push(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(failures.is_empty(), "{} differ:\n{}", failures.len(), failures.join("\n"));
|
||||
assert!(succeeded >= 150, "only {succeeded} runs succeeded");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user