Rewrite Curator in Rust with an egui GUI
This commit is contained in:
+236
@@ -0,0 +1,236 @@
|
||||
//! 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::<Vec<_>>()
|
||||
);
|
||||
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,
|
||||
"<movie><title>Avatar</title><uniqueid type=\"tmdb\">19995</uniqueid></movie>",
|
||||
)
|
||||
.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);
|
||||
}
|
||||
Reference in New Issue
Block a user