vectorcraft/crates/engine/src/tests_targeting.rs

118 lines
5.3 KiB
Rust

//! Targeting through the Layers panel's target circles (`layer.target`) and moving or copying
//! appearances between objects (`appearance.transfer`).
use serde_json::{Value, json};
use vectorcraft_color::{BlendMode, Paint};
use vectorcraft_doc::Node;
use super::*;
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
s
}
fn run(s: &mut Session, id: &str, p: Value) -> Value {
s.execute(id, &p).unwrap_or_else(|e| panic!("{id} {p}: {e}"))
}
fn node(s: &Session, id: NodeId) -> Node {
s.doc().unwrap().doc.node(id).unwrap().clone()
}
fn rect(s: &mut Session, x: f64) -> NodeId {
NodeId(run(s, "shape.rectangle", json!({"x": x, "y": 20, "width": 40, "height": 40}))["id"].as_u64().unwrap())
}
#[test]
fn a_targeted_layer_takes_transparency_appearance_and_effects() {
let mut s = session();
let a = rect(&mut s, 20.0);
let b = rect(&mut s, 100.0);
let layer = s.doc().unwrap().doc.layers[0].id;
let r = run(&mut s, "layer.target", json!({"id": layer.0}));
assert_eq!(r["selected"], json!([a.0, b.0]), "the layer's art is selected");
assert_eq!(s.doc().unwrap().selection.target, Some(layer));
run(&mut s, "transparency.set", json!({"opacity": 40, "blend": "multiply"}));
let l = node(&s, layer);
assert!((l.opacity - 0.4).abs() < 1e-6 && l.blend == BlendMode::Multiply);
assert_eq!(node(&s, a).opacity, 1.0, "the art keeps its own opacity");
assert_eq!(s.execute("transparency.info", &json!({})).unwrap()["ids"], json!([layer.0]));
run(&mut s, "appearance.addStroke", json!({}));
run(&mut s, "effect.apply", json!({"effect": "stylize.dropShadow"}));
let l = node(&s, layer);
assert_eq!(l.appearance.items.len(), 1);
assert_eq!(l.appearance.effects.len(), 1);
// The Appearance panel's active row is a row of the layer's own stack.
run(&mut s, "appearance.setActiveItem", json!({"index": 0}));
assert_eq!(s.appearance_item(), Some(0));
// Undo is one step per command, and any other selection change ends the targeting.
run(&mut s, "edit.undo", json!({}));
assert!(node(&s, layer).appearance.effects.is_empty());
run(&mut s, "select.set", json!({"ids": [a.0]}));
assert_eq!(s.doc().unwrap().selection.target, None);
assert_eq!(s.appearance_item(), None);
run(&mut s, "transparency.set", json!({"opacity": 50}));
assert!((node(&s, a).opacity - 0.5).abs() < 1e-6);
assert!((node(&s, layer).opacity - 0.4).abs() < 1e-6);
}
#[test]
fn targeting_an_object_selects_it_and_locked_layers_cannot_be_targeted() {
let mut s = session();
let a = rect(&mut s, 20.0);
run(&mut s, "select.none", json!({}));
run(&mut s, "layer.target", json!({"id": a.0}));
assert_eq!(s.doc().unwrap().selection.objects, vec![a]);
let layer = s.doc().unwrap().doc.layers[0].id;
run(&mut s, "layer.setProps", json!({"id": layer.0, "locked": true}));
assert!(s.execute("layer.target", &json!({"id": layer.0})).is_err());
assert!(s.execute("layer.target", &json!({"id": 9999})).is_err());
}
#[test]
fn a_targeted_layer_gets_an_opacity_mask() {
let mut s = session();
rect(&mut s, 20.0);
let layer = s.doc().unwrap().doc.layers[0].id;
run(&mut s, "layer.target", json!({"id": layer.0}));
assert_eq!(run(&mut s, "transparency.makeOpacityMask", json!({}))["id"], json!(layer.0));
run(&mut s, "transparency.stopEditingOpacityMask", json!({}));
assert!(node(&s, layer).mask.is_some());
assert_eq!(s.doc().unwrap().selection.target, Some(layer), "the masked layer stays targeted");
}
#[test]
fn transfer_moves_or_copies_the_appearance() {
let mut s = session();
let a = rect(&mut s, 20.0);
let b = rect(&mut s, 100.0);
run(&mut s, "select.set", json!({"ids": [a.0]}));
run(&mut s, "paint.setFill", json!({"color": "#ff0000"}));
run(&mut s, "appearance.addFill", json!({}));
run(&mut s, "effect.apply", json!({"effect": "distort.twist"}));
run(&mut s, "transparency.set", json!({"opacity": 30, "blend": "screen"}));
let src = node(&s, a);
// Copy (Alt-drag): both have it.
run(&mut s, "appearance.transfer", json!({"source": a.0, "target": b.0, "copy": true}));
let (na, nb) = (node(&s, a), node(&s, b));
assert_eq!(na.appearance, src.appearance);
assert_eq!(nb.appearance, src.appearance);
assert!((nb.opacity - 0.3).abs() < 1e-6 && nb.blend == BlendMode::Screen);
// Move onto the layer: the source is left cleared; one undo step brings it back.
let layer = s.doc().unwrap().doc.layers[0].id;
run(&mut s, "appearance.transfer", json!({"source": a.0, "target": layer.0}));
let (na, nl) = (node(&s, a), node(&s, layer));
assert_eq!(nl.appearance.items.len(), src.appearance.items.len());
assert_eq!(nl.appearance.effects.len(), 1);
assert!((nl.opacity - 0.3).abs() < 1e-6);
assert!(na.appearance.effects.is_empty() && na.opacity == 1.0 && na.blend == BlendMode::Normal);
assert_eq!(na.appearance.fill_paint(), Paint::None);
run(&mut s, "edit.undo", json!({}));
assert_eq!(node(&s, a).appearance, src.appearance);
assert!(node(&s, layer).appearance.items.is_empty());
assert!(s.execute("appearance.transfer", &json!({"source": a.0, "target": a.0})).is_err());
assert!(s.execute("appearance.transfer", &json!({"source": a.0})).is_err());
}