diff options
| author | cashmere <cashmere@autistici.org> | 2026-07-28 20:14:51 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-07-28 20:14:51 +0200 |
| commit | 32438ce25f69de99d2523b98237f62cfa90890d7 (patch) | |
| tree | 3a712c347dec0889bdea3328b4c817c2d9392b27 /rust/crates/graphics/tests/gradient_parser.rs | |
| parent | f5a82152394fde4d8f740f664ab7737b82861618 (diff) | |
| download | opencut-32438ce25f69de99d2523b98237f62cfa90890d7.tar.gz | |
feat: port domain crates to Rust (session 1 complete)
All green: cargo test --workspace (~300 tests, 0 failures).
- timeline (45): placement, resize/ripple, split, snap, retime,
audio separation, scene helpers
- animation (73): bezier solver, channel sampling, keyframe ops,
split/clamp/clone, param definitions
- commands (9): Command trait, BatchCommand, history, 19 concrete
commands, freeze-frame port 1:1
- audio (22): audible collection, range mixing, mastering limiter,
rms/peak buckets
- text (19): cosmic-text layout/measure/raster, backgrounds,
decorations
- graphics (52): full CSS gradient parser, tiny-skia raster,
4 shape definitions
- effect-defs (16): blur definition, registry, pass resolution
- mask-defs (19): all 9 mask renderers via tiny-skia
- renderer (6): node model + scene builder port
- desktop: GPUI projects screen wired to ProjectStore
- scene: factory (web defaults: v32, Main Track, 1920x1080, 30fps)
Diffstat (limited to 'rust/crates/graphics/tests/gradient_parser.rs')
| -rw-r--r-- | rust/crates/graphics/tests/gradient_parser.rs | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/rust/crates/graphics/tests/gradient_parser.rs b/rust/crates/graphics/tests/gradient_parser.rs new file mode 100644 index 00000000..4e905ee9 --- /dev/null +++ b/rust/crates/graphics/tests/gradient_parser.rs @@ -0,0 +1,375 @@ +use graphics::gradient::ast::*; +use graphics::gradient::parser::parse_gradient; + +fn single(code: &str) -> GradientAst { + let ast = parse_gradient(code).unwrap_or_else(|err| panic!("failed to parse {code:?}: {err}")); + assert_eq!(ast.len(), 1, "expected one gradient in {code:?}"); + ast.into_iter().next().unwrap() +} + +#[test] +fn linear_minimal() { + let gradient = single("linear-gradient(red, blue)"); + assert_eq!(gradient.kind, GradientKind::Linear); + assert_eq!(gradient.orientation, None); + assert_eq!( + gradient.color_stops, + vec![ + ColorStop { color: Color::Literal("red".into()), length: None }, + ColorStop { color: Color::Literal("blue".into()), length: None }, + ] + ); +} + +#[test] +fn linear_directional() { + let gradient = single("linear-gradient(to right, #fff, #000)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "right".into() + ))) + ); + assert_eq!(gradient.color_stops[0].color, Color::Hex("fff".into())); +} + +#[test] +fn linear_corner() { + let gradient = single("linear-gradient(to left top, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "left top".into() + ))) + ); + let gradient = single("linear-gradient(to bottom right, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "bottom right".into() + ))) + ); +} + +#[test] +fn linear_angles() { + let gradient = single("linear-gradient(45deg, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + "45".into() + ))) + ); + let gradient = single("linear-gradient(-90deg, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + "-90".into() + ))) + ); + let gradient = single("linear-gradient(0.5rad, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + "0.5".into() + ))) + ); + let gradient = single("linear-gradient(.25deg, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + ".25".into() + ))) + ); +} + +#[test] +fn linear_legacy_direction() { + let gradient = single("linear-gradient(left, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "left".into() + ))) + ); +} + +#[test] +fn repeating_and_vendor_prefix() { + assert_eq!( + single("repeating-linear-gradient(red, blue)").kind, + GradientKind::RepeatingLinear + ); + assert_eq!( + single("repeating-radial-gradient(red, blue)").kind, + GradientKind::RepeatingRadial + ); + assert_eq!( + single("-webkit-linear-gradient(red, blue)").kind, + GradientKind::Linear + ); + assert_eq!( + single("-moz-radial-gradient(red, blue)").kind, + GradientKind::Radial + ); + assert_eq!( + single("LINEAR-GRADIENT(red, blue)").kind, + GradientKind::Linear + ); +} + +#[test] +fn radial_shapes() { + let gradient = single("radial-gradient(circle, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { value: ShapeKind::Circle, style: None, at: None } + )])) + ); + + let gradient = single("radial-gradient(circle 50px, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Circle, + style: Some(ShapeStyle::Distance(Distance::Px("50".into()))), + at: None, + } + )])) + ); + + let gradient = single("radial-gradient(ellipse closest-side, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Ellipse, + style: Some(ShapeStyle::ExtentKeyword("closest-side".into())), + at: None, + } + )])) + ); +} + +#[test] +fn radial_with_position() { + let gradient = single("radial-gradient(circle at 25% 25%, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Circle, + style: None, + at: Some(Position { + x: Some(Distance::Percent("25".into())), + y: Some(Distance::Percent("25".into())), + }), + } + )])) + ); + + let gradient = single("radial-gradient(farthest-corner at 10px 2em, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![ + RadialOrientation::ExtentKeyword { + value: "farthest-corner".into(), + at: Some(Position { + x: Some(Distance::Px("10".into())), + y: Some(Distance::Em("2".into())), + }), + } + ])) + ); + + let gradient = single("radial-gradient(at center, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![ + RadialOrientation::DefaultRadial { + at: Position { + x: Some(Distance::PositionKeyword("center".into())), + y: None, + }, + } + ])) + ); + + let gradient = single("radial-gradient(100px 50px at 10% 20%, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Ellipse, + style: Some(ShapeStyle::Position(Position { + x: Some(Distance::Px("100".into())), + y: Some(Distance::Px("50".into())), + })), + at: Some(Position { + x: Some(Distance::Percent("10".into())), + y: Some(Distance::Percent("20".into())), + }), + } + )])) + ); +} + +#[test] +fn radial_multiple_orientations() { + let gradient = single("radial-gradient(circle, ellipse, red, blue)"); + match gradient.orientation { + Some(GradientOrientation::Radial(list)) => assert_eq!(list.len(), 2), + other => panic!("unexpected orientation: {other:?}"), + } +} + +#[test] +fn color_formats() { + let gradient = single("linear-gradient(#abcdef, rgb(1,2,3), rgba(1,2,3,0.5), hsl(120,50%,50%), hsla(120,50%,50%,0.5), var(--brand), rebeccapurple)"); + assert_eq!(gradient.color_stops[0].color, Color::Hex("abcdef".into())); + assert_eq!( + gradient.color_stops[1].color, + Color::Rgb(vec!["1".into(), "2".into(), "3".into()]) + ); + assert_eq!( + gradient.color_stops[2].color, + Color::Rgba(vec!["1".into(), "2".into(), "3".into(), "0.5".into()]) + ); + assert_eq!( + gradient.color_stops[3].color, + Color::Hsl(["120".into(), "50".into(), "50".into()]) + ); + assert_eq!( + gradient.color_stops[4].color, + Color::Hsla(["120".into(), "50".into(), "50".into(), "0.5".into()]) + ); + assert_eq!(gradient.color_stops[5].color, Color::Var("--brand".into())); + assert_eq!( + gradient.color_stops[6].color, + Color::Literal("rebeccapurple".into()) + ); +} + +#[test] +fn stop_lengths() { + let gradient = single("linear-gradient(red 10%, blue 20px, green 3em, white calc(10% + 5px))"); + assert_eq!( + gradient.color_stops[0].length, + Some(Distance::Percent("10".into())) + ); + assert_eq!( + gradient.color_stops[1].length, + Some(Distance::Px("20".into())) + ); + assert_eq!( + gradient.color_stops[2].length, + Some(Distance::Em("3".into())) + ); + assert_eq!( + gradient.color_stops[3].length, + Some(Distance::Calc("10% + 5px".into())) + ); +} + +#[test] +fn multiple_gradients() { + let ast = parse_gradient("linear-gradient(red, blue), radial-gradient(circle, white, black)") + .unwrap(); + assert_eq!(ast.len(), 2); + assert_eq!(ast[0].kind, GradientKind::Linear); + assert_eq!(ast[1].kind, GradientKind::Radial); +} + +#[test] +fn trailing_semicolon_and_whitespace() { + let ast = parse_gradient(" linear-gradient(red, blue); ").unwrap(); + assert_eq!(ast.len(), 1); +} + +#[test] +fn empty_input_is_empty_list() { + assert_eq!(parse_gradient("").unwrap(), vec![]); +} + +#[test] +fn double_space_directional_quirk() { + let gradient = single("linear-gradient(to right, red, blue)"); + assert_eq!(gradient.orientation, None); + assert_eq!(gradient.color_stops[0].color, Color::Literal("to".into())); + assert_eq!( + gradient.color_stops[0].length, + Some(Distance::PositionKeyword("right".into())) + ); +} + +#[test] +fn error_missing_close_paren() { + let err = parse_gradient("linear-gradient(red, blue").unwrap_err(); + assert_eq!(err.message, "Missing )"); +} + +#[test] +fn error_extra_comma() { + let err = parse_gradient("linear-gradient(red,,blue)").unwrap_err(); + assert_eq!(err.message, "Expected color definition"); +} + +#[test] +fn error_missing_comma_before_color_stops() { + let err = parse_gradient("linear-gradient(to left red, blue)").unwrap_err(); + assert_eq!(err.message, "Missing comma before color stops"); +} + +#[test] +fn error_invalid_input_not_eof() { + let err = parse_gradient("foo(red)").unwrap_err(); + assert_eq!(err.message, "Invalid input not EOF"); + assert_eq!(err.input, "foo(red)"); +} + +#[test] +fn error_hsl_hue_percentage() { + let err = parse_gradient("linear-gradient(hsl(50%, 50%, 50%), red)").unwrap_err(); + assert!(err.message.starts_with("HSL hue value must be a number")); +} + +#[test] +fn error_hsl_missing_percentages() { + let err = parse_gradient("linear-gradient(hsl(120, 50, 50), red)").unwrap_err(); + assert_eq!( + err.message, + "Expected percentage value for saturation and lightness in HSL" + ); +} + +#[test] +fn error_rgb_empty() { + let err = parse_gradient("linear-gradient(rgb(), red)").unwrap_err(); + assert_eq!(err.message, "Expected number"); +} + +#[test] +fn error_calc_unbalanced() { + let err = parse_gradient("linear-gradient(red calc(10%").unwrap_err(); + assert_eq!(err.message, "Missing closing parenthesis in calc() expression"); +} + +#[test] +fn error_var_missing_name() { + let err = parse_gradient("linear-gradient(var(), red)").unwrap_err(); + assert_eq!(err.message, "Expected CSS variable name"); +} + +#[test] +fn error_at_missing_position() { + let err = parse_gradient("radial-gradient(circle at, red, blue)").unwrap_err(); + assert_eq!(err.message, "Missing positioning value"); +} + +#[test] +fn error_display_format() { + let err = parse_gradient("foo()").unwrap_err(); + assert_eq!(err.to_string(), "foo(): Invalid input not EOF"); +} |
