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/commands/tests/freeze_frame.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/commands/tests/freeze_frame.rs')
| -rw-r--r-- | rust/crates/commands/tests/freeze_frame.rs | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/rust/crates/commands/tests/freeze_frame.rs b/rust/crates/commands/tests/freeze_frame.rs new file mode 100644 index 00000000..614b73f7 --- /dev/null +++ b/rust/crates/commands/tests/freeze_frame.rs @@ -0,0 +1,155 @@ +mod common; + +use commands::{ + BatchCommand, Command, InsertElementCommand, InsertElementPlacement, RetainSide, + ShiftSplitRemainderCommand, SplitElementsCommand, +}; +use common::{ + FREEZE_SECONDS, SPLIT_SECONDS, VIDEO_SECONDS, build_freeze_image_element, seconds, + seed_project_with_video, +}; +use scene::{Element, ElementRef, Project}; +use time::MediaTime; + +fn build_freeze_batch(track_id: &str) -> BatchCommand { + let split = SplitElementsCommand::new( + vec![ElementRef { + track_id: track_id.to_string(), + element_id: "video-1".to_string(), + }], + seconds(SPLIT_SECONDS), + RetainSide::Both, + ); + let right_side = split.right_side_handle(); + let shift = ShiftSplitRemainderCommand::new( + right_side, + seconds(SPLIT_SECONDS) + seconds(FREEZE_SECONDS), + ); + let insert = InsertElementCommand::new( + build_freeze_image_element(), + InsertElementPlacement::Explicit { + track_id: track_id.to_string(), + }, + ); + BatchCommand::new(vec![Box::new(split), Box::new(shift), Box::new(insert)]) +} + +fn sorted_main_elements(project: &Project) -> Vec<Element> { + let scene = project + .scenes + .iter() + .find(|scene| scene.id == project.current_scene_id) + .expect("active scene"); + let mut elements = scene.tracks.main.elements().to_vec(); + elements.sort_by_key(|element| element.base().start_time); + elements +} + +fn element_type_name(element: &Element) -> &'static str { + match element { + Element::Video(_) => "video", + Element::Image(_) => "image", + Element::Text(_) => "text", + Element::Audio(_) => "audio", + Element::Sticker(_) => "sticker", + Element::Graphic(_) => "graphic", + Element::Effect(_) => "effect", + } +} + +#[test] +fn splits_shifts_right_side_and_inserts_into_gap() { + let (mut project, track_id) = seed_project_with_video(); + + build_freeze_batch(&track_id) + .execute(&mut project) + .expect("execute"); + + let elements = sorted_main_elements(&project); + let type_names: Vec<&str> = elements.iter().map(element_type_name).collect(); + assert_eq!(type_names, ["video", "image", "video"]); + + let [left, image, right] = &elements[..] else { + panic!("expected three elements"); + }; + assert_eq!(left.base().start_time, MediaTime::ZERO); + assert_eq!(left.base().duration, seconds(SPLIT_SECONDS)); + assert_eq!(image.base().start_time, seconds(SPLIT_SECONDS)); + assert_eq!(image.base().duration, seconds(FREEZE_SECONDS)); + assert_eq!( + right.base().start_time, + seconds(SPLIT_SECONDS) + seconds(FREEZE_SECONDS) + ); + assert_eq!( + right.base().duration, + seconds(VIDEO_SECONDS - SPLIT_SECONDS) + ); +} + +#[test] +fn undo_restores_original_element_as_single_step() { + let (mut project, track_id) = seed_project_with_video(); + let mut batch = build_freeze_batch(&track_id); + + batch.execute(&mut project).expect("execute"); + batch.undo(&mut project); + + let scene = project + .scenes + .iter() + .find(|scene| scene.id == project.current_scene_id) + .expect("active scene"); + let elements = scene.tracks.main.elements(); + assert_eq!(elements.len(), 1); + assert_eq!(elements[0].base().id, "video-1"); + assert_eq!(elements[0].base().start_time, MediaTime::ZERO); + assert_eq!(elements[0].base().duration, seconds(VIDEO_SECONDS)); +} + +#[test] +fn redo_after_undo_reapplies_shift_to_resplit_elements() { + let (mut project, track_id) = seed_project_with_video(); + let mut batch = build_freeze_batch(&track_id); + + batch.execute(&mut project).expect("execute"); + batch.undo(&mut project); + batch.redo(&mut project).expect("redo"); + + let elements = sorted_main_elements(&project); + let type_names: Vec<&str> = elements.iter().map(element_type_name).collect(); + assert_eq!(type_names, ["video", "image", "video"]); + + let right = elements.last().expect("right element"); + assert!(matches!(right, Element::Video(_))); + assert_eq!( + right.base().start_time, + seconds(SPLIT_SECONDS) + seconds(FREEZE_SECONDS) + ); +} + +#[test] +fn no_op_when_split_produces_no_right_side() { + let (mut project, track_id) = seed_project_with_video(); + + let split = SplitElementsCommand::new( + vec![ElementRef { + track_id: track_id.to_string(), + element_id: "video-1".to_string(), + }], + MediaTime::ZERO, + RetainSide::Both, + ); + let right_side = split.right_side_handle(); + let shift = ShiftSplitRemainderCommand::new(right_side, seconds(FREEZE_SECONDS)); + let mut batch = BatchCommand::new(vec![Box::new(split), Box::new(shift)]); + + batch.execute(&mut project).expect("execute"); + batch.undo(&mut project); + + let scene = project + .scenes + .iter() + .find(|scene| scene.id == project.current_scene_id) + .expect("active scene"); + assert_eq!(scene.tracks.main.elements().len(), 1); +} |
