summaryrefslogtreecommitdiff
path: root/rust/crates/renderer/tests
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 20:14:51 +0200
committercashmere <cashmere@autistici.org>2026-07-28 20:14:51 +0200
commit32438ce25f69de99d2523b98237f62cfa90890d7 (patch)
tree3a712c347dec0889bdea3328b4c817c2d9392b27 /rust/crates/renderer/tests
parentf5a82152394fde4d8f740f664ab7737b82861618 (diff)
downloadopencut-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/renderer/tests')
-rw-r--r--rust/crates/renderer/tests/scene_builder.rs228
1 files changed, 228 insertions, 0 deletions
diff --git a/rust/crates/renderer/tests/scene_builder.rs b/rust/crates/renderer/tests/scene_builder.rs
new file mode 100644
index 00000000..1d65c092
--- /dev/null
+++ b/rust/crates/renderer/tests/scene_builder.rs
@@ -0,0 +1,228 @@
+use std::path::PathBuf;
+
+use renderer::node::{MediaSource, MediaType, Node};
+use renderer::{BuildSceneParams, build_scene};
+use scene::{
+ Background, BaseTimelineElement, CanvasSize, Element, ImageElement, ParamValues, SceneTracks,
+ TextElement, Track, VideoElement,
+};
+use time::MediaTime;
+
+fn base(id: &str, start: i64) -> BaseTimelineElement {
+ BaseTimelineElement {
+ id: id.to_string(),
+ name: id.to_string(),
+ duration: MediaTime::from_ticks(1000),
+ start_time: MediaTime::from_ticks(start),
+ trim_start: MediaTime::ZERO,
+ trim_end: MediaTime::ZERO,
+ source_duration: None,
+ animations: None,
+ params: ParamValues::new(),
+ }
+}
+
+fn video_element(id: &str, start: i64, media_id: &str) -> Element {
+ Element::Video(VideoElement {
+ base: base(id, start),
+ media_id: media_id.to_string(),
+ is_source_audio_enabled: None,
+ hidden: None,
+ retime: None,
+ effects: None,
+ masks: None,
+ })
+}
+
+fn image_element(id: &str, start: i64, media_id: &str) -> Element {
+ Element::Image(ImageElement {
+ base: base(id, start),
+ media_id: media_id.to_string(),
+ hidden: None,
+ effects: None,
+ masks: None,
+ })
+}
+
+fn text_element(id: &str, start: i64, hidden: Option<bool>) -> Element {
+ Element::Text(TextElement {
+ base: base(id, start),
+ hidden,
+ effects: None,
+ })
+}
+
+fn video_track(id: &str, hidden: bool, elements: Vec<Element>) -> Track {
+ Track::Video {
+ id: id.to_string(),
+ name: id.to_string(),
+ elements,
+ muted: false,
+ hidden,
+ }
+}
+
+fn media(id: &str, media_type: MediaType) -> MediaSource {
+ MediaSource {
+ id: id.to_string(),
+ media_type,
+ path: PathBuf::from(format!("/media/{id}")),
+ }
+}
+
+fn build(tracks: &SceneTracks, media: &[MediaSource], background: &Background) -> Vec<Node> {
+ let params = BuildSceneParams {
+ canvas_size: CanvasSize {
+ width: 1920,
+ height: 1080,
+ },
+ tracks,
+ media,
+ duration: MediaTime::from_ticks(10_000),
+ background,
+ is_preview: false,
+ };
+ match build_scene(&params) {
+ Node::Root { children, .. } => children,
+ _ => panic!("expected root"),
+ }
+}
+
+fn transparent() -> Background {
+ Background::Color {
+ color: "transparent".into(),
+ }
+}
+
+#[test]
+fn skips_elements_without_media_and_mismatched_types() {
+ let tracks = SceneTracks {
+ overlay: vec![],
+ main: video_track(
+ "main",
+ false,
+ vec![
+ video_element("v1", 0, "m1"),
+ video_element("v2", 0, "missing"),
+ image_element("i1", 0, "m1"),
+ ],
+ ),
+ audio: vec![],
+ };
+ let assets = [media("m1", MediaType::Video)];
+ let children = build(&tracks, &assets, &transparent());
+
+ assert_eq!(children.len(), 1);
+ assert!(matches!(&children[0], Node::Video { media_id, .. } if media_id == "m1"));
+}
+
+#[test]
+fn main_track_renders_below_overlay() {
+ let tracks = SceneTracks {
+ overlay: vec![video_track("overlay", false, vec![text_element("t1", 0, None)])],
+ main: video_track("main", false, vec![video_element("v1", 0, "m1")]),
+ audio: vec![],
+ };
+ let assets = [media("m1", MediaType::Video)];
+ let children = build(&tracks, &assets, &transparent());
+
+ assert_eq!(children.len(), 2);
+ assert!(matches!(&children[0], Node::Video { .. }));
+ assert!(matches!(&children[1], Node::Text(_)));
+}
+
+#[test]
+fn hidden_elements_and_tracks_are_skipped() {
+ let tracks = SceneTracks {
+ overlay: vec![video_track("hidden-track", true, vec![text_element("t2", 0, None)])],
+ main: video_track(
+ "main",
+ false,
+ vec![video_element("v1", 0, "m1"), text_element("t1", 0, Some(true))],
+ ),
+ audio: vec![],
+ };
+ let assets = [media("m1", MediaType::Video)];
+ let children = build(&tracks, &assets, &transparent());
+
+ assert_eq!(children.len(), 1);
+ assert!(matches!(&children[0], Node::Video { .. }));
+}
+
+#[test]
+fn elements_sort_by_start_time_then_id() {
+ let tracks = SceneTracks {
+ overlay: vec![],
+ main: video_track(
+ "main",
+ false,
+ vec![
+ video_element("b", 100, "m1"),
+ video_element("a", 100, "m1"),
+ video_element("z", 50, "m1"),
+ ],
+ ),
+ audio: vec![],
+ };
+ let assets = [media("m1", MediaType::Video)];
+ let children = build(&tracks, &assets, &transparent());
+
+ let starts: Vec<i64> = children
+ .iter()
+ .map(|node| match node {
+ Node::Video { visual, .. } => visual.time_offset.as_ticks(),
+ _ => panic!("expected video"),
+ })
+ .collect();
+ assert_eq!(starts, [50, 100, 100]);
+}
+
+#[test]
+fn color_background_comes_first_unless_transparent() {
+ let tracks = SceneTracks {
+ overlay: vec![],
+ main: video_track("main", false, vec![video_element("v1", 0, "m1")]),
+ audio: vec![],
+ };
+ let assets = [media("m1", MediaType::Video)];
+
+ let with_color = build(
+ &tracks,
+ &assets,
+ &Background::Color {
+ color: "#112233".into(),
+ },
+ );
+ assert!(matches!(&with_color[0], Node::Color { color } if color == "#112233"));
+ assert_eq!(with_color.len(), 2);
+
+ let transparent_children = build(&tracks, &assets, &transparent());
+ assert_eq!(transparent_children.len(), 1);
+}
+
+#[test]
+fn blur_background_emits_backdrop_nodes_for_main_media() {
+ let tracks = SceneTracks {
+ overlay: vec![],
+ main: video_track("main", false, vec![video_element("v1", 0, "m1")]),
+ audio: vec![],
+ };
+ let assets = [media("m1", MediaType::Video)];
+ let children = build(
+ &tracks,
+ &assets,
+ &Background::Blur {
+ blur_intensity: 12.0,
+ },
+ );
+
+ assert_eq!(children.len(), 2);
+ match &children[0] {
+ Node::BlurBackground(params) => {
+ assert_eq!(params.blur_intensity, 12.0);
+ assert_eq!(params.media_id, "m1");
+ }
+ _ => panic!("expected blur background first"),
+ }
+ assert!(matches!(&children[1], Node::Video { .. }));
+}