mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 03:06:57 -06:00
Merge pull request #987 from Ni-zav/fix/noissue-stroke-input-atomic-s20261010T1630Z-c8d2e1
fix(stroke): reject invalid dash entries and keep defaults atomic
This commit is contained in:
@@ -100,7 +100,12 @@ impl StrokeChange {
|
||||
let dash = match p.get("dash") {
|
||||
None => None,
|
||||
Some(Value::Null) => Some(None),
|
||||
Some(Value::Array(a)) => Some(Some(a.iter().filter_map(Value::as_f64).collect::<Vec<_>>()).filter(|d| !d.is_empty())),
|
||||
Some(Value::Array(a)) => {
|
||||
// Do not silently omit malformed segments and change the user's dash pattern.
|
||||
let values: Vec<f64> =
|
||||
a.iter().map(|v| v.as_f64().ok_or_else(|| bad(cmd, format!("dash lengths must be numbers, got {v}")))).collect::<Result<_>>()?;
|
||||
Some(Some(values).filter(|d| !d.is_empty()))
|
||||
}
|
||||
Some(v) => return Err(bad(cmd, format!("dash must be a list of lengths or null, got {v}"))),
|
||||
};
|
||||
Ok(Self {
|
||||
@@ -167,9 +172,6 @@ fn no_targets(ids: &[vectorcraft_doc::NodeId], p: &Value) -> bool {
|
||||
fn stroke_set(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "stroke.set";
|
||||
let change = StrokeChange::parse(p, C)?;
|
||||
if let Some(w) = change.weight {
|
||||
s.paint.stroke_width = w;
|
||||
}
|
||||
let item = item_target(s, p, C)?.of_kind(s, false);
|
||||
let mut ids = item.targets(s, p)?;
|
||||
let new_art = no_targets(&ids, p);
|
||||
@@ -216,10 +218,6 @@ fn stroke_set(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
st.profile = pr.clone();
|
||||
}
|
||||
};
|
||||
// With nothing selected the Stroke panel sets up the next object drawn.
|
||||
if new_art && let Some(st) = s.new_art_stroke_mut() {
|
||||
set(st);
|
||||
}
|
||||
edit_items(s, &ids, item, C, "Stroke", false, |n, index| {
|
||||
if index.is_none()
|
||||
&& let NodeKind::Text(t) = &mut n.kind
|
||||
@@ -235,6 +233,14 @@ fn stroke_set(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
// Commit the new-art template only after the complete request is validated
|
||||
// and the selected artwork edit has succeeded.
|
||||
if let Some(w) = change.weight {
|
||||
s.paint.stroke_width = w;
|
||||
}
|
||||
if new_art && let Some(st) = s.new_art_stroke_mut() {
|
||||
set(st);
|
||||
}
|
||||
ok()
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,41 @@ fn fill_and_stroke_commands() {
|
||||
s.execute("paint.setFill", &json!({"gradient": {"kind": "radial"}})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stroke_rejects_invalid_dash_components_and_preserves_defaults_on_error() {
|
||||
let mut s = session();
|
||||
let id = rect(&mut s, 0.0, 0.0, 40.0, 30.0);
|
||||
s.execute("stroke.set", &json!({"weight": 3, "dash": [6, 3]})).unwrap();
|
||||
let previous_weight = s.paint.stroke_width;
|
||||
let previous_stroke = s.doc().unwrap().doc.node(id).unwrap().appearance.stroke().cloned();
|
||||
|
||||
for params in [
|
||||
json!({"weight": 10, "dash": [6, "not a number", 3]}),
|
||||
json!({"weight": 10, "ids": "not an array"}),
|
||||
json!({"weight": 10, "item": "invalid"}),
|
||||
json!({"weight": 10, "arrowAlign": "unknown"}),
|
||||
] {
|
||||
assert!(s.execute("stroke.set", ¶ms).is_err(), "{params} must fail");
|
||||
assert_eq!(s.paint.stroke_width, previous_weight, "failed stroke edit changed new-art defaults");
|
||||
assert_eq!(s.doc().unwrap().doc.node(id).unwrap().appearance.stroke().cloned(), previous_stroke);
|
||||
}
|
||||
|
||||
// A valid dash list still updates the selected stroke and new-art default width.
|
||||
s.execute("stroke.set", &json!({"weight": 7, "dash": [4, 2]})).unwrap();
|
||||
assert_eq!(s.paint.stroke_width, 7.0);
|
||||
assert_eq!(s.doc().unwrap().doc.node(id).unwrap().appearance.stroke().unwrap().width, 7.0);
|
||||
|
||||
// With nothing selected the panel sets up the next object drawn: a rejected edit doesn't.
|
||||
s.execute("select.none", &json!({})).unwrap();
|
||||
let next = s.new_art();
|
||||
assert!(s.execute("stroke.set", &json!({"weight": 12, "dash": [2, 2], "arrowAlign": "unknown"})).is_err());
|
||||
assert_eq!(s.new_art(), next);
|
||||
s.execute("stroke.set", &json!({"weight": 12, "dash": [2, 2]})).unwrap();
|
||||
let next = s.new_art();
|
||||
let stroke = next.stroke().unwrap();
|
||||
assert_eq!((stroke.width, stroke.dash.as_ref().map(|d| d.pattern.clone())), (12.0, Some(vec![2.0, 2.0])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejected_paint_edits_leave_new_art_defaults_and_focus_unchanged() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user