Files
bflatastic 8362223932 Appearance panel: a stroke colour on an unpainted zero-width stroke restores 1 pt too (#1253)
The case #1297 also covered: appearance.setItem with a colour and no weight.
2026-10-12 07:25:31 +07:00

120 lines
5.3 KiB
Rust

//! Painting a previously unpainted zero-width stroke restores the existing 1 pt default.
#![allow(clippy::unwrap_used)]
use serde_json::json;
use vectorcraft_doc::NodeId;
use vectorcraft_engine::{Session, ViewInfo};
use vectorcraft_tools::{PointerEvent, PointerKind};
fn fixture() -> (Session, NodeId) {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
let id = rectangle(&mut s, 10.0);
(s, id)
}
fn rectangle(s: &mut Session, x: f64) -> NodeId {
NodeId(s.execute("shape.rectangle", &json!({"x": x, "y": 10, "width": 50, "height": 40})).unwrap()["id"].as_u64().unwrap())
}
fn width(s: &Session, id: NodeId) -> f64 {
s.doc().unwrap().doc.node(id).unwrap().appearance.stroke().unwrap().width
}
#[test]
fn adding_a_stroke_after_sampling_strokeless_art_makes_it_visible_in_one_undo_step() {
let (mut s, _) = fixture();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
s.execute("select.set", &json!({"ids": []})).unwrap();
s.select_tool("eyedropper", ViewInfo::default()).unwrap();
s.pointer(&PointerEvent::new(PointerKind::Down, 25.0, 25.0), ViewInfo::default()).unwrap();
let id = rectangle(&mut s, 100.0);
assert_eq!(width(&s, id), 0.0, "sampled None paint is a normal source of a zero-width stroke");
let st = s.doc().unwrap();
let before = st.doc.clone();
let sel = st.selection.clone();
let history = st.history.undo.len();
let geometry = st.doc.node(id).unwrap().geometric_bounds();
s.execute("paint.setStroke", &json!({"color": "#ee2222"})).unwrap();
assert_eq!(width(&s, id), 1.0);
let st = s.doc().unwrap();
assert_eq!(st.doc.node(id).unwrap().geometric_bounds(), geometry);
assert_eq!(json!(st.selection), json!(sel));
assert_eq!(st.history.undo.len(), history + 1);
let after = st.doc.clone();
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(json!(s.doc().unwrap().doc), json!(before));
s.execute("edit.redo", &json!({})).unwrap();
assert_eq!(json!(s.doc().unwrap().doc), json!(after));
}
#[test]
fn adding_stroke_paint_to_the_defaults_gives_the_next_shape_a_visible_stroke() {
let (mut s, _) = fixture();
s.execute("select.set", &json!({"ids": []})).unwrap();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
s.execute("stroke.set", &json!({"weight": 0})).unwrap();
s.execute("paint.setStroke", &json!({"color": "#ee2222"})).unwrap();
assert_eq!(s.new_art_stroke().width, 1.0);
let id = rectangle(&mut s, 100.0);
assert_eq!(width(&s, id), 1.0);
}
#[test]
fn swapping_fill_into_an_unpainted_zero_width_stroke_makes_it_visible() {
let (mut s, id) = fixture();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
s.execute("stroke.set", &json!({"weight": 0})).unwrap();
let fill = s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint();
s.execute("paint.swap", &json!({})).unwrap();
let n = s.doc().unwrap().doc.node(id).unwrap();
assert!(n.appearance.fill_paint().is_none());
assert_eq!(n.appearance.stroke_paint(), fill);
assert_eq!(width(&s, id), 1.0);
assert_eq!(s.new_art_stroke().width, 1.0);
}
#[test]
fn a_targeted_stroke_is_restored_without_changing_the_other_stroke() {
let (mut s, id) = fixture();
s.execute("stroke.set", &json!({"weight": 5})).unwrap();
s.execute("appearance.addStroke", &json!({})).unwrap();
s.execute("paint.setStroke", &json!({"item": 1, "none": true})).unwrap();
s.execute("stroke.set", &json!({"item": 1, "weight": 0})).unwrap();
s.execute("paint.setStroke", &json!({"item": 1, "color": "#ee2222"})).unwrap();
let ap = &s.doc().unwrap().doc.node(id).unwrap().appearance;
assert_eq!(ap.stroke_at(Some(1)).unwrap().width, 1.0);
assert_eq!(ap.stroke_at(Some(2)).unwrap().width, 5.0);
}
#[test]
fn existing_weights_and_explicitly_zero_painted_strokes_keep_their_widths() {
for (was_none, weight) in [(true, 5.0), (false, 5.0), (false, 0.0)] {
let (mut s, id) = fixture();
if was_none {
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
}
s.execute("stroke.set", &json!({"weight": weight})).unwrap();
s.execute("paint.setStroke", &json!({"color": "#ee2222"})).unwrap();
assert_eq!(width(&s, id), weight);
}
let (mut s, id) = fixture();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
s.execute("stroke.set", &json!({"weight": 0})).unwrap();
s.execute("paint.setFill", &json!({"color": "#ffaa00"})).unwrap();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
assert_eq!(width(&s, id), 0.0, "fill edits and removing paint do not enable a stroke");
}
#[test]
fn appearance_panel_stroke_colour_restores_an_unpainted_zero_width_stroke() {
let (mut s, id) = fixture();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
s.execute("stroke.set", &json!({"weight": 0})).unwrap();
s.execute("appearance.setItem", &json!({"index": 1, "color": "#00ff00"})).unwrap();
assert_eq!(width(&s, id), 1.0);
s.execute("appearance.setItem", &json!({"index": 1, "weight": 0})).unwrap();
s.execute("appearance.setItem", &json!({"index": 1, "color": "#0000ff"})).unwrap();
assert_eq!(width(&s, id), 0.0, "a painted stroke keeps an explicit zero width");
}