diff --git a/crates/engine/src/cmd/props.rs b/crates/engine/src/cmd/props.rs index 924b0379d..ffa78185c 100644 --- a/crates/engine/src/cmd/props.rs +++ b/crates/engine/src/cmd/props.rs @@ -220,6 +220,9 @@ fn set_operand_op(doc: &mut Document, id: NodeId, v: &Value, cmd: &str) -> Resul pub(crate) const WIDTH_PROFILE_HELP: &str = "widthProfile: a preset (uniform|wedge|taper|eye|rounded|pinch), points [[t (0-1 along the path), scale (× stroke weight)] | [t, left, right] (one-sided: each side's scale, looking along the path), ...] or [{t, scale} | {t, left, right}], {preset} | {points}, or null (uniform)"; +/// The stroke caps `strokeCap`, `startCap` and `endCap` take (every [`StrokeCap`]). +pub(crate) const STROKE_CAP_HELP: &str = "none|round|square|lineArrow|triangleArrow|reversedTriangle|circleFilled|diamondFilled"; + pub(crate) const BRUSH_HELP: &str = "brush: a preset (taper|ink|ribbon|bristle: stretched; dots|stars|confetti|leaves: scattered), {preset, spacing? (× weight), sizeJitter? (0-1), rotationJitter? (0-1), scatter? (0-4 × weight), seed?}, or null (a plain stroke)"; /// A brush from a preset id, `{preset, spacing?, sizeJitter?, rotationJitter?, scatter?, seed?}` @@ -635,13 +638,13 @@ fn apply_look(cmd: &str, doc: &mut Document, id: NodeId, k: &str, v: &Value) -> st.sides = Some(sides); } "strokeCap" => { - let c: StrokeCap = enum_of(v).ok_or_else(|| bad(cmd, "unknown stroke cap"))?; + let c: StrokeCap = enum_of(v).ok_or_else(|| bad(cmd, format!("strokeCap: {STROKE_CAP_HELP}")))?; let n = doc.get_mut(id)?; n.stroke.cap_start = c; n.stroke.cap_end = c; } - "startCap" => doc.get_mut(id)?.stroke.cap_start = enum_of(v).ok_or_else(|| bad(cmd, "unknown stroke cap"))?, - "endCap" => doc.get_mut(id)?.stroke.cap_end = enum_of(v).ok_or_else(|| bad(cmd, "unknown stroke cap"))?, + "startCap" => doc.get_mut(id)?.stroke.cap_start = enum_of(v).ok_or_else(|| bad(cmd, format!("startCap: {STROKE_CAP_HELP}")))?, + "endCap" => doc.get_mut(id)?.stroke.cap_end = enum_of(v).ok_or_else(|| bad(cmd, format!("endCap: {STROKE_CAP_HELP}")))?, "strokeJoin" => doc.get_mut(id)?.stroke.join = enum_of(v).ok_or_else(|| bad(cmd, "strokeJoin: miter|bevel|round"))?, "miterLimit" => doc.get_mut(id)?.stroke.miter_limit = f64_of(v).unwrap_or(4.0).clamp(1.0, 100.0), "miterAngle" => { diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index e919444e2..f7becee87 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -545,6 +545,8 @@ mod slot_tests; #[cfg(test)] mod stored_ids_tests; #[cfg(test)] +mod stroke_cap_error_tests; +#[cfg(test)] mod stroke_style_tests; #[cfg(test)] mod style_tests; diff --git a/crates/engine/src/stroke_cap_error_tests.rs b/crates/engine/src/stroke_cap_error_tests.rs new file mode 100644 index 000000000..11e414d5c --- /dev/null +++ b/crates/engine/src/stroke_cap_error_tests.rs @@ -0,0 +1,27 @@ +//! An unknown stroke cap is refused with the caps there are, as `strokeAlign` and `strokeJoin` +//! list theirs (#2005). + +use serde_json::json; + +use crate::Session; +use crate::cmd::props::STROKE_CAP_HELP; +use crate::params::id_of; +use figcraft_doc::StrokeCap; + +#[test] +fn an_unknown_cap_lists_every_cap() { + let names: Vec = StrokeCap::ALL.iter().map(|c| serde_json::to_value(c).unwrap().as_str().unwrap().to_string()).collect(); + assert_eq!(STROKE_CAP_HELP.split('|').collect::>(), names, "the list is every cap, in order"); + let mut s = Session::new(); + let v = id_of(&s.execute("vector.create", &json!({"path": "M0 0 L200 0", "x": 0, "y": 0})).unwrap()["id"]).unwrap(); + for key in ["strokeCap", "startCap", "endCap"] { + for junk in [json!("butt"), json!(3), json!("")] { + let e = s.execute("node.set", &json!({"id": v, "props": {key: junk}})).unwrap_err().to_string(); + assert!(e.contains(&format!("{key}: {STROKE_CAP_HELP}")), "{key} {junk}: {e}"); + } + // Every name the error lists is taken. + for name in &names { + s.execute("node.set", &json!({"id": v, "props": {key: name}})).unwrap_or_else(|e| panic!("{key} {name}: {e}")); + } + } +}