Files
vectorcraft/crates/mcp/src/tests_liquify.rs
T
bflatasticandClaude Opus 5.5 e9abc62f1a M8.4: Liquify keeps going while held, leaves non-paths alone, strokes apply incrementally
Holding the brush still keeps Twirl, Pucker and Bloat applying, scaled by the
time held: a new Tool::tick (time supplied by the host: canvas frames, or
`holdMs` on ui.pointer / pointer_gesture events) repeats the last point every
0.1 s, one more dab there. Liquify never touches graphs, meshes, type, symbols,
images, or envelopes, repeats and blends with their contents, nor guides in
selection mode; those under the brush come back in `skipped` with a `warning`
that the status bar shows (UiRequest::Status). A drag applies only the dabs
each new sample adds (the stroke's state is kept between previews), giving the
same result as the whole stroke, so journal replay still reproduces it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 12:00:52 +07:00

65 lines
3.1 KiB
Rust

//! The Liquify tools through MCP: their options (`tool.setOption`, kept across tool switches) and
//! brush strokes with `pointer_gesture`.
use serde_json::{Value, json};
use crate::{Headless, call_tool};
fn ok(h: &mut Headless, name: &str, args: Value) -> Value {
let r = call_tool(h, name, &args);
assert!(!r.is_error, "{name} {args}: {r:?}");
serde_json::from_str(r.content[0]["text"].as_str().unwrap()).unwrap_or(Value::Null)
}
fn command(h: &mut Headless, command: &str, params: Value) -> Value {
ok(h, "run_command", json!({"command": command, "params": params}))
}
#[test]
fn tool_options_are_set_headless_and_kept_across_tool_switches() {
let mut h = Headless::with_document();
command(&mut h, "tool.select", json!({"tool": "warp"}));
let o = command(&mut h, "tool.setOption", json!({"values": {"width": 64, "detail": 6}}));
assert_eq!((o["width"].as_f64(), o["detail"].as_f64()), (Some(64.0), Some(6.0)));
command(&mut h, "tool.select", json!({"tool": "bloat"}));
let o = command(&mut h, "tool.setOption", json!({}));
assert_eq!((o["tool"].as_str(), o["width"].as_f64(), o["detail"].as_f64()), (Some("bloat"), Some(64.0), Some(2.0)));
command(&mut h, "tool.select", json!({"tool": "warp"}));
assert_eq!(command(&mut h, "tool.setOption", json!({}))["detail"], json!(6.0));
}
#[test]
fn pointer_gestures_carry_pen_pressure() {
let bloat = |pressure: f64| {
let mut h = Headless::with_document();
let id = command(&mut h, "shape.rectangle", json!({"x": 100, "y": 100, "width": 200, "height": 200}))["id"].clone();
command(&mut h, "select.set", json!({"ids": []}));
command(&mut h, "tool.select", json!({"tool": "bloat"}));
command(&mut h, "tool.setOption", json!({"key": "usePressure", "value": true}));
let ev = |kind: &str, y: f64| json!({"kind": kind, "x": 280, "y": y, "pressure": pressure});
ok(&mut h, "pointer_gesture", json!({"events": [ev("down", 150.0), ev("drag", 200.0), ev("up", 250.0)]}));
command(&mut h, "document.node", json!({"id": id}))
};
let (none, full) = (bloat(0.0), bloat(1.0));
assert_eq!(none, bloat(0.0));
assert_ne!(none, full);
}
#[test]
fn holding_still_through_mcp_and_skips_are_reported() {
let pucker = |hold: u64| {
let mut h = Headless::with_document();
let id = command(&mut h, "shape.rectangle", json!({"x": 100, "y": 100, "width": 200, "height": 200}))["id"].clone();
command(&mut h, "text.create", json!({"x": 290, "y": 200, "text": "Hi"}));
command(&mut h, "select.set", json!({"ids": []}));
let ev = |kind: &str| json!({"kind": kind, "x": 290, "y": 200, "holdMs": hold});
let r = ok(&mut h, "pointer_gesture", json!({"tool": "pucker", "events": [ev("down"), ev("up")]}));
(r, command(&mut h, "document.node", json!({"id": id})))
};
let (r, still) = pucker(0);
assert!(r["requests"].as_array().unwrap().iter().any(|q| q["status"].as_str().is_some_and(|m| m.contains("type"))), "{r}");
let (_, held) = pucker(500);
assert_ne!(still, held);
assert_eq!(held, pucker(500).1, "the same hold, the same result");
}