//! End-to-end UI tests: drive the egui window headlessly (egui_kittest) the way //! a user would — open a dialog, click buttons — and check the pool on disk. use std::fs; use std::path::{Path, PathBuf}; use std::time::{Duration, Instant}; use curator::core::file_manager::FileManager; use curator::ui::app::CuratorApp; use egui_kittest::Harness; use egui_kittest::kittest::Queryable; struct Env { dir: tempfile::TempDir, } impl Env { fn new() -> Self { Self { dir: tempfile::tempdir().unwrap(), } } fn pool(&self) -> PathBuf { self.dir.path().join("pool") } fn manager(&self) -> FileManager { let mut fm = FileManager::with_config_path(self.dir.path().join("cfg.json")); fm.set_pool_dir(&self.pool()).unwrap(); fm.load_pool_movies().unwrap(); fm } fn source(&self, name: &str) -> PathBuf { let p = self.dir.path().join("src").join(name); fs::create_dir_all(p.parent().unwrap()).unwrap(); fs::write(&p, name).unwrap(); p } } fn harness(fm: FileManager) -> Harness<'static, CuratorApp> { Harness::builder() .with_size(egui::vec2(1300.0, 800.0)) .allow_missing_glyphs() .with_accessibility_check(false) .build_eframe(|cc| CuratorApp::new(fm, cc.egui_ctx.clone())) } /// Step until no background task runs (or time out). fn wait_idle(h: &mut Harness<'static, CuratorApp>) { let start = Instant::now(); loop { h.step(); if !h.state().busy() { h.run_steps(3); return; } assert!( start.elapsed() < Duration::from_secs(20), "task did not finish" ); std::thread::sleep(Duration::from_millis(20)); } } fn exists(p: &Path) -> bool { p.exists() } #[test] fn test_import_movie_through_the_dialog() { let env = Env::new(); let src = env.source("Matrix.1999.1080p.mkv"); let mut h = harness(env.manager()); h.state_mut().open_import(vec![src.clone()]); h.run_steps(3); h.get_by_label("OK").click(); h.run_steps(2); wait_idle(&mut h); let pooled = env.pool().join("Filmy/Matrix.1999.1080p (1999).mkv"); assert!( exists(&pooled), "pool: {:?}", fs::read_dir(env.pool().join("Filmy")) .unwrap() .collect::>() ); assert!(exists(&src), "copy keeps the source"); // the summary message is shown and can be dismissed h.get_by_label("OK").click(); h.run_steps(2); let fm = h.state().file_manager().unwrap(); assert_eq!(fm.filelist.len(), 1); } #[test] fn test_import_requires_a_year() { let env = Env::new(); let src = env.source("Bez roku.mkv"); let mut h = harness(env.manager()); h.state_mut().open_import(vec![src]); h.run_steps(3); h.get_by_label("OK").click(); h.run_steps(3); // a warning appears and nothing is imported assert!(h.query_by_label_contains("čtyřmístný rok").is_some()); assert!( fs::read_dir(env.pool().join("Filmy")) .unwrap() .next() .is_none() ); } #[test] fn test_import_existing_film_as_a_version() { let env = Env::new(); let mut fm = env.manager(); let opts = curator::core::file_manager::ImportOptions { year: Some("2009".into()), tmdb_id: Some("19995".into()), ..Default::default() }; fm.import_movie(&env.source("a.mkv"), "Avatar", opts) .unwrap(); // a second copy of the same film, named so title/year/id are prefilled let src = env.source("Avatar (2009).3D.HSBS.mkv"); let nfo = src.with_extension("nfo"); fs::write( &nfo, "Avatar19995", ) .unwrap(); let mut h = harness(fm); h.state_mut().open_import(vec![src]); h.run_steps(3); h.get_by_label("OK").click(); h.run_steps(3); // conflict question → add as a version h.get_by_label("Přidat jako verzi").click(); h.run_steps(3); // the popisek prompt is pre-filled from the 3D tag h.get_by_label("OK").click(); h.run_steps(2); wait_idle(&mut h); let folder = env.pool().join("Filmy/Avatar (2009) [tmdbid-19995]"); assert!(folder.join("Avatar (2009) [tmdbid-19995].mkv").exists()); assert!( folder .join("Avatar (2009) [tmdbid-19995] - 3D HSBS.mkv") .exists() ); } fn imported(env: &Env, fm: &mut FileManager, name: &str, title: &str) -> PathBuf { fm.import_movie( &env.source(name), title, curator::core::file_manager::ImportOptions::default(), ) .unwrap() .unwrap() } #[test] fn test_edit_movie_renames_with_year_and_id() { let env = Env::new(); let mut fm = env.manager(); let p = imported(&env, &mut fm, "x.mkv", "Matrix"); let mut h = harness(fm); h.state_mut().select_movies(&[p]); h.state_mut().debug_action("edit"); h.run_steps(3); // the editor's year and TMDb fields (hint texts identify them) for (label, text) in [("Rok:", "1999"), ("TMDb ID:", "603")] { h.get_by_label(label).focus(); h.run_steps(1); h.get_by_label(label).type_text(text); h.run_steps(1); } h.run_steps(2); h.get_by_label("OK").click(); h.run_steps(3); assert!( env.pool() .join("Filmy/Matrix (1999) [tmdbid-603].mkv") .exists() ); } #[test] fn test_remove_movie_after_confirmation() { let env = Env::new(); let mut fm = env.manager(); let p = imported(&env, &mut fm, "x.mkv", "Matrix"); let mut h = harness(fm); h.state_mut().select_movies(std::slice::from_ref(&p)); h.run_steps(2); h.key_press(egui::Key::Delete); h.run_steps(3); h.get_by_label("Ano").click(); h.run_steps(3); assert!(!p.exists()); assert!(h.state().file_manager().unwrap().filelist.is_empty()); } #[test] fn test_assign_a_new_tag_through_the_dialog() { let env = Env::new(); let mut fm = env.manager(); let p = imported(&env, &mut fm, "x.mkv", "Matrix"); let mut h = harness(fm); h.state_mut().select_movies(std::slice::from_ref(&p)); h.state_mut().debug_action("tags"); h.run_steps(3); h.get_by_label("➕ Nový štítek…").click(); h.run_steps(3); // pick the "Žánr" category, then type the tag name h.get_all_by_label("Žánr").last().unwrap().click(); h.run_steps(2); h.get_all_by_label("OK").last().unwrap().click(); h.run_steps(3); h.get_all_by_role(egui::accesskit::Role::TextInput) .last() .unwrap() .type_text("Sci-Fi"); h.run_steps(2); h.get_all_by_label("OK").last().unwrap().click(); h.run_steps(3); h.get_by_label("OK").click(); h.run_steps(3); let f = h.state().file_manager().unwrap().file(&p).unwrap().clone(); assert!(f.tag_paths().contains("Žánr/Sci-Fi"), "{:?}", f.tags); }