diff --git a/crates/host/src/lib.rs b/crates/host/src/lib.rs index 5235feca..45e850ff 100644 --- a/crates/host/src/lib.rs +++ b/crates/host/src/lib.rs @@ -220,6 +220,35 @@ mod tests { assert!(bytes.starts_with(b"GIF89a")); } + /// importFile, layers.add(footage) and project save/open from a script. + #[test] + fn script_imports_footage_and_saves() { + let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/test-out/host-script-import"); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let q = |n: &str| serde_json::to_string(&dir.join(n).to_string_lossy()).unwrap(); + let mut s = super::session(); + let code = format!( + r#" + var c = app.project.items.addComp("Src", 32, 32, 1, 1, 10); + c.layers.addSolid([0, 1, 0], "G", 32, 32, 1); + app.run("comp.saveFrameAs", {{comp: c.id, path: {png}}}); + var still = app.project.importFile(new ImportOptions(new File({png}))); + var main = app.project.items.addComp("Main", 64, 64, 1, 1, 10); + var l = main.layers.add(still); + app.project.save(new File({proj})); + [still instanceof FootageItem, still.width, still.mainSource.isStill, l.source.name, app.project.file.name] + "#, + png = q("frame.png"), + proj = q("p.ecproj") + ); + let r = s.execute("script.run", json!({"code": code})).unwrap(); + assert_eq!(r["ok"], json!(true), "{r}"); + assert_eq!(r["result"], json!([true, 32, true, "frame.png", "p.ecproj"]), "{r}"); + let r = s.execute("script.run", json!({"code": format!("app.open(new File({})); app.project.numItems", q("p.ecproj"))})).unwrap(); + assert_eq!(r["result"], json!(5), "{r}"); // Src, Solids, G, frame.png, Main + } + #[test] fn expression_syntax_errors_disable_the_expression() { let mut s = super::session(); diff --git a/crates/script/src/prelude.js b/crates/script/src/prelude.js index 4f359926..c4f256f4 100644 --- a/crates/script/src/prelude.js +++ b/crates/script/src/prelude.js @@ -1571,6 +1571,7 @@ Project.prototype = { else __call("file.save", {}); return true; }, + saveAs: function (file) { return this.save(file); }, saveWithDialog: function () { if (this.file) return this.save(); return false; }, close: function (opt) { if (opt === CloseOptions.SAVE_CHANGES && this.file) this.save(); diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index f4a4fcb6..458f2b41 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -390,3 +390,59 @@ fn schedule_task_runs_after_the_script() { ); assert_eq!(o.output, vec!["now", "later"]); } + +#[test] +fn layer_switches_timing_and_comp_settings() { + let mut s = session(); + let o = ok( + &mut s, + r#" + var c = app.project.items.addComp("S", 320, 240, 1, 5, 30); + var matte = c.layers.addText("M"); + var fill = c.layers.addSolid([1, 1, 1], "Fill", 320, 240, 1); + fill.moveToEnd(); + fill.setTrackMatte(matte, TrackMatteType.LUMA); + var t1 = fill.trackMatteType == TrackMatteType.LUMA && fill.hasTrackMatte && fill.trackMatteLayer.name == "M"; + fill.stretch = 200; + fill.label = 3; + c.width = 400; + c.duration = 8; + c.bgColor = [0.1, 0.2, 0.3]; + c.time = 2; + c.workAreaStart = 1; + c.workAreaDuration = 3; + var extra = c.layers.addNull(); + extra.remove(); + fill.selected = true; + var sel = c.selectedLayers.length; + fill.transform.opacity.expression = "thisLayer.nope("; + var err = fill.transform.opacity.expressionError; + fill.transform.opacity.expressionEnabled = false; + var copy = c.duplicate(); + var f = app.project.items.addFolder("Bin"); + copy.parentFolder = f; + [t1, fill.stretch, fill.label, c.width, c.duration, c.bgColor[2], c.time, c.workAreaStart, c.workAreaDuration, + c.numLayers, sel, err.length > 0, fill.transform.opacity.expressionEnabled, copy.name, f.numItems, copy.parentFolder.name] + "#, + ); + let v = o.result; + assert_eq!(v[0], json!(true), "{v}"); + assert_eq!(v[1], json!(200)); + assert_eq!(v[2], json!(3)); + assert_eq!(v[3], json!(400)); + assert_eq!(v[4], json!(8)); + assert!((v[5].as_f64().unwrap() - 0.3).abs() < 1e-6); + assert_eq!(v[6], json!(2)); + assert_eq!(v[7], json!(1)); + assert_eq!(v[8], json!(3)); + assert_eq!(v[9], json!(2)); + assert_eq!(v[10], json!(1)); + assert_eq!(v[11], json!(true)); + assert_eq!(v[12], json!(false)); + assert_eq!(v[13], json!("S 2")); + assert_eq!(v[14], json!(1)); + assert_eq!(v[15], json!("Bin")); + // Removing the copy through the object model. + let o = ok(&mut s, "var n = app.project.numItems; app.project.item(1).remove(); n - app.project.numItems"); + assert_eq!(o.result, json!(1)); +} diff --git a/crates/ui-egui/tests/ui_script_console.rs b/crates/ui-egui/tests/ui_script_console.rs index a8068c9b..526a5293 100644 --- a/crates/ui-egui/tests/ui_script_console.rs +++ b/crates/ui-egui/tests/ui_script_console.rs @@ -42,3 +42,27 @@ fn script_console_runs_scripts() { click(&mut h, "scriptConsole.clear"); assert!(h.state().ui.script_console.log.is_empty()); } + +/// Headless look at the panel (wgpu offscreen); run with +/// `SC_SNAPSHOT=/abs/out.png cargo test -p effectcraft-ui-egui --test ui_script_console -- --ignored`. +#[test] +#[ignore] +fn script_console_snapshot() { + let s = effectcraft_host::session(); + let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_| EffectcraftApp::new(s)); + h.state_mut().show_panel(PanelKind::ScriptConsole); + h.run_steps(2); + for code in ["var c = app.project.items.addComp('Console', 320, 180, 1, 2, 24); writeLn('made ' + c.name); c.id", "c.layers.addText('Hi').name", "nope()"] { + h.state_mut().ui.script_console.input = code.into(); + h.run_steps(1); + click(&mut h, "scriptConsole.run"); + } + h.state_mut().ui.script_console.input = "app.project.numItems".into(); + h.run_steps(3); + let img = h.render().expect("render"); + let out = std::env::var("SC_SNAPSHOT").unwrap_or_else(|_| concat!(env!("CARGO_MANIFEST_DIR"), "/../../target/test-out/script_console.png").into()); + if let Some(d) = std::path::Path::new(&out).parent() { + std::fs::create_dir_all(d).unwrap(); + } + img.save(&out).unwrap(); +}