summaryrefslogtreecommitdiff
path: root/rust/crates/commands/tests/framework.rs
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/commands/tests/framework.rs
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/commands/tests/framework.rs')
-rw-r--r--rust/crates/commands/tests/framework.rs162
1 files changed, 162 insertions, 0 deletions
diff --git a/rust/crates/commands/tests/framework.rs b/rust/crates/commands/tests/framework.rs
new file mode 100644
index 00000000..e3defbad
--- /dev/null
+++ b/rust/crates/commands/tests/framework.rs
@@ -0,0 +1,162 @@
+mod common;
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use commands::{
+ BatchCommand, Command, CommandError, CommandHistory, CommandResult, DeleteElementsCommand,
+ PreviewTracker,
+};
+use common::{VIDEO_SECONDS, seconds, seed_project_with_video};
+use scene::{ElementRef, Project};
+
+struct RecordingCommand {
+ label: &'static str,
+ log: Rc<RefCell<Vec<&'static str>>>,
+ selection: Option<Vec<ElementRef>>,
+}
+
+impl RecordingCommand {
+ fn new(label: &'static str, log: &Rc<RefCell<Vec<&'static str>>>) -> Self {
+ Self {
+ label,
+ log: Rc::clone(log),
+ selection: None,
+ }
+ }
+
+ fn with_selection(mut self, selection: Vec<ElementRef>) -> Self {
+ self.selection = Some(selection);
+ self
+ }
+}
+
+impl Command for RecordingCommand {
+ fn execute(&mut self, _project: &mut Project) -> Result<Option<CommandResult>, CommandError> {
+ self.log.borrow_mut().push(self.label);
+ Ok(self
+ .selection
+ .clone()
+ .map(CommandResult::element_selection))
+ }
+
+ fn undo(&mut self, _project: &mut Project) {
+ self.log.borrow_mut().push(self.label);
+ }
+}
+
+#[test]
+fn batch_executes_in_order_and_undoes_in_reverse() {
+ let (mut project, _) = seed_project_with_video();
+ let log = Rc::new(RefCell::new(Vec::new()));
+
+ let mut batch = BatchCommand::new(vec![
+ Box::new(RecordingCommand::new("first", &log)),
+ Box::new(RecordingCommand::new("second", &log)),
+ Box::new(RecordingCommand::new("third", &log)),
+ ]);
+
+ batch.execute(&mut project).expect("execute");
+ assert_eq!(*log.borrow(), vec!["first", "second", "third"]);
+
+ log.borrow_mut().clear();
+ batch.undo(&mut project);
+ assert_eq!(*log.borrow(), vec!["third", "second", "first"]);
+}
+
+#[test]
+fn batch_returns_latest_selection_result() {
+ let (mut project, _) = seed_project_with_video();
+ let log = Rc::new(RefCell::new(Vec::new()));
+
+ let selected = vec![ElementRef {
+ track_id: "main-track-1".to_string(),
+ element_id: "video-1".to_string(),
+ }];
+ let mut batch = BatchCommand::new(vec![
+ Box::new(
+ RecordingCommand::new("selecting", &log).with_selection(selected.clone()),
+ ),
+ Box::new(RecordingCommand::new("plain", &log)),
+ ]);
+
+ let result = batch.execute(&mut project).expect("execute");
+ let selection = result
+ .and_then(|result| result.selection)
+ .expect("selection result");
+ assert_eq!(selection.selected_elements, selected);
+}
+
+#[test]
+fn history_undo_redo_roundtrip() {
+ let (mut project, track_id) = seed_project_with_video();
+ let mut history = CommandHistory::new();
+
+ assert!(!history.can_undo());
+ assert!(!history.can_redo());
+
+ history
+ .execute(
+ Box::new(DeleteElementsCommand::new(vec![ElementRef {
+ track_id: track_id.clone(),
+ element_id: "video-1".to_string(),
+ }])),
+ &mut project,
+ )
+ .expect("execute");
+
+ let scene = common::active_scene;
+ assert!(scene(&project).tracks.main.elements().is_empty());
+ assert!(history.can_undo());
+ assert!(!history.can_redo());
+
+ history.undo(&mut project);
+ assert_eq!(scene(&project).tracks.main.elements().len(), 1);
+ assert!(!history.can_undo());
+ assert!(history.can_redo());
+
+ history.redo(&mut project).expect("redo");
+ assert!(scene(&project).tracks.main.elements().is_empty());
+ assert!(history.can_undo());
+ assert!(!history.can_redo());
+}
+
+#[test]
+fn history_new_execute_clears_redo_stack() {
+ let (mut project, track_id) = seed_project_with_video();
+ let mut history = CommandHistory::new();
+
+ let delete = || {
+ Box::new(DeleteElementsCommand::new(vec![ElementRef {
+ track_id: track_id.clone(),
+ element_id: "video-1".to_string(),
+ }])) as Box<dyn Command>
+ };
+
+ history.execute(delete(), &mut project).expect("execute");
+ history.undo(&mut project);
+ assert!(history.can_redo());
+
+ history.execute(delete(), &mut project).expect("execute");
+ assert!(!history.can_redo());
+
+ history.clear();
+ assert!(!history.can_undo());
+ assert!(!history.can_redo());
+}
+
+#[test]
+fn preview_tracker_keeps_first_snapshot_until_end() {
+ let mut tracker = PreviewTracker::new();
+ assert!(!tracker.is_active());
+ assert_eq!(tracker.end(), None);
+
+ tracker.begin(&seconds(VIDEO_SECONDS));
+ tracker.begin(&seconds(1.0));
+ assert!(tracker.is_active());
+ assert_eq!(tracker.snapshot(), Some(&seconds(VIDEO_SECONDS)));
+
+ assert_eq!(tracker.end(), Some(seconds(VIDEO_SECONDS)));
+ assert!(!tracker.is_active());
+ assert_eq!(tracker.snapshot(), None);
+}