Tests: a gradient spanning the selection (#2127)

This commit is contained in:
Gert Pellin
2026-10-16 04:44:50 +07:00
committed by bflatastic
parent e7aacec852
commit 265552d5f4
2 changed files with 153 additions and 0 deletions
+2
View File
@@ -1894,6 +1894,8 @@ mod tests_gradient;
#[cfg(test)]
mod tests_gradpanel;
#[cfg(test)]
mod tests_gradspan;
#[cfg(test)]
mod tests_halftone;
#[cfg(test)]
mod tests_history_mem;
+151
View File
@@ -0,0 +1,151 @@
//! `paint.setFill` / `paint.setStroke` `span: "selection"`: one gradient placed on the union of
//! the targets' bounds, so the ramp runs across them (#2127).
use serde_json::{Value, json};
use vectorcraft_color::{GradientPaint, Paint};
use vectorcraft_geom::{Affine, Point};
use super::*;
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 200})).unwrap();
s
}
fn rect(s: &mut Session, x: f64, y: f64, w: f64, h: f64) -> NodeId {
let r = s.execute("shape.rectangle", &json!({"x": x, "y": y, "width": w, "height": h})).unwrap();
NodeId(r["id"].as_u64().unwrap())
}
fn fill_gradient(s: &Session, id: NodeId) -> GradientPaint {
match s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint() {
Paint::Gradient(g) => *g,
p => panic!("expected a gradient fill, got {p:?}"),
}
}
fn stroke_gradient(s: &Session, id: NodeId) -> GradientPaint {
match &s.doc().unwrap().doc.node(id).unwrap().appearance.stroke().unwrap().paint {
Paint::Gradient(g) => (**g).clone(),
p => panic!("expected a gradient stroke, got {p:?}"),
}
}
fn near(a: Point, b: Point) -> bool {
(a - b).hypot() < 1e-6
}
fn black_to_white() -> Value {
json!({"kind": "linear", "stops": [{"offset": 0, "color": "#000000"}, {"offset": 1, "color": "#ffffff"}]})
}
/// The red channel of the rendered document at (x, y).
fn red_at(s: &Session, x: u32, y: u32) -> u8 {
let doc = s.doc().unwrap().doc.clone();
let img = vectorcraft_render::Renderer::new().render(&doc, 400, 200, Affine::IDENTITY, &Default::default());
img.pixel(x, y)[0]
}
#[test]
fn a_linear_gradient_spans_the_union_of_the_targets() {
let mut s = session();
let a = rect(&mut s, 0.0, 50.0, 100.0, 100.0);
let b = rect(&mut s, 100.0, 0.0, 100.0, 200.0);
let c = rect(&mut s, 300.0, 50.0, 100.0, 100.0);
s.execute("paint.setFill", &json!({"ids": [a.0, b.0, c.0], "gradient": black_to_white(), "span": "selection"})).unwrap();
// One vector for all three, across the union 0..400 × 0..200 at angle 0.
for id in [a, b, c] {
let g = fill_gradient(&s, id);
let geom = g.geom.expect("placed in document coordinates");
assert!(near(geom.start, Point::new(0.0, 100.0)) && near(geom.end, Point::new(400.0, 100.0)), "{id:?}: {geom:?}");
}
// The ramp runs on across the objects: dark on the left one, light on the right one, and the
// edge the first two share is the same colour on both sides.
let (left, right) = (red_at(&s, 5, 100), red_at(&s, 395, 100));
assert!(left < 20 && right > 235, "{left} {right}");
let (edge_a, edge_b) = (i32::from(red_at(&s, 98, 100)), i32::from(red_at(&s, 102, 100)));
assert!((edge_a - edge_b).abs() <= 4 && (50..80).contains(&edge_a), "{edge_a} {edge_b}");
}
#[test]
fn without_span_each_object_fits_its_own_box() {
let mut s = session();
let a = rect(&mut s, 0.0, 50.0, 100.0, 100.0);
let b = rect(&mut s, 100.0, 50.0, 100.0, 100.0);
for span in [json!(null), json!("object")] {
let mut p = json!({"ids": [a.0, b.0], "gradient": black_to_white()});
if !span.is_null() {
p["span"] = span;
}
s.execute("paint.setFill", &p).unwrap();
assert_eq!(fill_gradient(&s, a).geom, None);
// Each ramp starts dark again on its own object.
assert!(red_at(&s, 102, 100) < 20, "{p}");
}
}
#[test]
fn the_selection_is_the_default_target_and_angle_and_radial_are_kept() {
let mut s = session();
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
let b = rect(&mut s, 200.0, 100.0, 100.0, 100.0);
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
let mut g = black_to_white();
g["angle"] = json!(-90);
s.execute("paint.setFill", &json!({"gradient": g, "span": "selection"})).unwrap();
// Vertical, top to bottom, over the union 0..300 × 0..200.
let geom = fill_gradient(&s, b).geom.unwrap();
assert!(near(geom.start, Point::new(150.0, 0.0)) && near(geom.end, Point::new(150.0, 200.0)), "{geom:?}");
assert_eq!(fill_gradient(&s, a).geom, Some(geom));
// Radial: centred on the union, radius half its larger side, aspect kept.
s.execute("paint.setFill", &json!({"gradient": {"kind": "radial", "aspect": 50, "stops": black_to_white()["stops"]}, "span": "selection"})).unwrap();
let geom = fill_gradient(&s, a).geom.unwrap();
assert!(near(geom.start, Point::new(150.0, 100.0)), "{geom:?}");
assert!(((geom.end - geom.start).hypot() - 150.0).abs() < 1e-6, "{geom:?}");
assert!((geom.aspect - 0.5).abs() < 1e-9, "{geom:?}");
assert_eq!(fill_gradient(&s, b).geom, Some(geom));
}
#[test]
fn strokes_span_the_targets_too() {
let mut s = session();
let a = rect(&mut s, 0.0, 50.0, 100.0, 100.0);
let b = rect(&mut s, 300.0, 50.0, 100.0, 100.0);
s.execute("paint.setStroke", &json!({"ids": [a.0, b.0], "gradient": black_to_white(), "span": "selection"})).unwrap();
let (ga, gb) = (stroke_gradient(&s, a).geom.unwrap(), stroke_gradient(&s, b).geom.unwrap());
assert_eq!(ga, gb);
assert!(ga.start.x < 0.0 && ga.end.x > 400.0, "the stroke's box, half its weight out: {ga:?}");
}
#[test]
fn the_new_art_default_stays_unplaced() {
let mut s = session();
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
s.execute("paint.setFill", &json!({"ids": [a.0], "gradient": black_to_white(), "span": "selection"})).unwrap();
s.execute("select.set", &json!({"ids": []})).unwrap();
let n = rect(&mut s, 200.0, 0.0, 50.0, 50.0);
assert_eq!(fill_gradient(&s, n).geom, None, "the next object fits the gradient to itself");
}
#[test]
fn bad_spans_are_refused_and_change_nothing() {
let mut s = session();
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
let b = rect(&mut s, 200.0, 0.0, 100.0, 100.0);
let before = s.doc().unwrap().doc.clone();
let mut with_vector = black_to_white();
with_vector["start"] = json!([0, 0]);
with_vector["end"] = json!([10, 0]);
for bad in [
json!({"ids": [a.0, b.0], "gradient": black_to_white(), "span": "page"}),
json!({"ids": [a.0, b.0], "gradient": black_to_white(), "span": true}),
json!({"ids": [a.0, b.0], "color": "#ff0000", "span": "selection"}),
json!({"ids": [a.0, b.0], "gradient": {"kind": "freeform"}, "span": "selection"}),
json!({"ids": [a.0, b.0], "gradient": with_vector, "span": "selection"}),
] {
let e = s.execute("paint.setFill", &bad).unwrap_err().to_string();
assert!(e.contains("span"), "{bad}: {e}");
assert_eq!(s.doc().unwrap().doc, before, "{bad}");
}
}