annatomic 0.4.0

The Annatomic annotation editor is intended to be used for the [RIDGES corpus](https://www.linguistik.hu-berlin.de/en/institut-en/professuren-en/korpuslinguistik/research/ridges-projekt). It is based on [graphANNIS](https://github.com/korpling/graphANNIS) and thus is internal data model is in principle suitable for a wide range of annotation concepts. "
Documentation
use annatto::{ExporterStep, ImporterStep, ReadFrom, WriteAs, importer::graphml::GraphMLImporter};
use egui::{Modifiers, accesskit::Role};
use egui_kittest::kittest::Queryable;
use insta::assert_snapshot;
use tempfile::tempdir;

use crate::{
    app::tests::{
        cleanup_test_project, create_app_with_corpus, create_test_harness, wait_until_jobs_finished,
    },
    assert_screenshots,
};

#[test]
fn import_corpus_and_extract_name() {
    let mut app_state = crate::AnnatomicApp::default();
    let storage_dir = tempdir().unwrap();
    app_state.project.corpus_storage_dir = Some(storage_dir.path().to_path_buf());
    let (mut harness, app_state) = create_test_harness(app_state);

    let importer_config = GraphMLImporter::default();
    let import_path = std::path::absolute("tests/data/single_sentence.graphml").unwrap();
    let import_step = ImporterStep::new(ReadFrom::GraphML(importer_config), &import_path);
    {
        let mut app = app_state.write();
        assert_eq!(0, app.project.corpus_locations.len());
        app.project.import(import_step, None).unwrap();
    }

    // Execute the running jobs and check that corpus was importe
    wait_until_jobs_finished(&mut harness, app_state.clone());

    {
        let app = app_state.read();
        let corpora = &app.project.corpus_locations;
        assert_eq!(1, corpora.len());
        assert_eq!("single_sentence", corpora.keys().next().unwrap());
    }
    cleanup_test_project(app_state);
}

#[test]
fn import_corpus_with_custom_name() {
    let mut app_state = crate::AnnatomicApp::default();
    let storage_dir = tempdir().unwrap();
    app_state.project.corpus_storage_dir = Some(storage_dir.path().to_path_buf());

    let (mut harness, app_state) = create_test_harness(app_state);

    let importer_config = GraphMLImporter::default();
    let import_path = std::path::absolute("tests/data/single_sentence.graphml").unwrap();
    let import_step = ImporterStep::new(ReadFrom::GraphML(importer_config), &import_path);
    {
        let mut app = app_state.write();
        assert_eq!(0, app.project.corpus_locations.len());
        app.project
            .import(import_step, Some("my-new-corpus".to_string()))
            .unwrap();
    }

    // Execute the running jobs and check that corpus was importe
    wait_until_jobs_finished(&mut harness, app_state.clone());

    {
        let app = app_state.read();
        let corpora = &app.project.corpus_locations;
        assert_eq!(1, corpora.len());
        assert_eq!("my-new-corpus", corpora.keys().next().unwrap());
    }
    cleanup_test_project(app_state);
}

#[test]
fn export_corpus() {
    let app_state = create_app_with_corpus(
        "single_sentence",
        &include_bytes!("../../../tests/data/single_sentence.graphml")[..],
    );
    let export_location = tempdir().unwrap();

    let (mut harness, app_state) = create_test_harness(app_state);
    // Select the corpus and export it
    app_state
        .write()
        .project
        .select_corpus(Some("single_sentence".to_string()));
    wait_until_jobs_finished(&mut harness, app_state.clone());

    let exporter_config = toml::from_str(
        r#"
        stable_order=true
        guess_vis=false
        "#,
    )
    .unwrap();

    let export_step = ExporterStep::new(WriteAs::GraphML(exporter_config), export_location.path());
    app_state
        .write()
        .project
        .export_selected(export_step)
        .unwrap();

    // Execute the running jobs and check that the file has been created
    wait_until_jobs_finished(&mut harness, app_state.clone());

    let actual_graphml =
        std::fs::read_to_string(export_location.path().join("single_sentence.graphml")).unwrap();
    assert_snapshot!(actual_graphml);
}
#[test]
fn show_import_toml_error() {
    let app_state = create_app_with_corpus(
        "single_sentence",
        &include_bytes!("../../../tests/data/single_sentence.graphml")[..],
    );

    let (mut harness, app_state) = create_test_harness(app_state);
    // Select the corpus and export it
    app_state
        .write()
        .project
        .select_corpus(Some("single_sentence".to_string()));
    wait_until_jobs_finished(&mut harness, app_state.clone());

    harness.get_by_label("Import corpus").click();
    harness.run();

    harness.get_by_role(Role::MultilineTextInput).focus();
    harness
        .get_by_role(Role::MultilineTextInput)
        .type_text("ABC=\"");
    harness.run();
    harness.snapshot("show_import_toml_error");
}

#[test]
fn show_export_toml_error() {
    let app_state = create_app_with_corpus(
        "single_sentence",
        &include_bytes!("../../../tests/data/single_sentence.graphml")[..],
    );

    let (mut harness, app_state) = create_test_harness(app_state);
    // Select the corpus and export it
    app_state
        .write()
        .project
        .select_corpus(Some("single_sentence".to_string()));
    wait_until_jobs_finished(&mut harness, app_state.clone());

    harness.get_by_label("Export corpus").click();
    harness.run();

    harness.get_by_role(Role::MultilineTextInput).focus();
    harness
        .get_by_role(Role::MultilineTextInput)
        .type_text("ABC=\"");
    harness.run();
    harness.snapshot("show_export_toml_error");
}

#[test]
fn switch_import_format() {
    let app_state = create_app_with_corpus(
        "single_sentence",
        &include_bytes!("../../../tests/data/single_sentence.graphml")[..],
    );

    let (mut harness, app_state) = create_test_harness(app_state);
    // Select the corpus and export it
    app_state
        .write()
        .project
        .select_corpus(Some("single_sentence".to_string()));
    wait_until_jobs_finished(&mut harness, app_state.clone());

    harness.get_by_label("Import corpus").click();
    harness.run();

    // Switch to Excel and enter a configuration value
    harness.get_by_role(Role::ComboBox).click();
    harness.run();
    harness.get_by_label("xlsx").scroll_to_me();
    harness.run();
    harness.get_by_label("xlsx").click();
    harness.run();

    harness.get_by_role(Role::MultilineTextInput).focus();
    harness.run();
    harness.key_press_modifiers(Modifiers::COMMAND, egui::Key::A);
    harness.key_press(egui::Key::Delete);
    harness.run();
    harness
        .get_by_role(Role::MultilineTextInput)
        .type_text("metasheet = \"some-meta-sheet\"");
    harness.run();

    let r1 = harness.try_snapshot("switch_import_format_before");

    // Switch to GraphML and then back, makre sure the inserted text is still there
    harness.get_by_role(Role::ComboBox).click();
    harness.run();
    harness.get_by_label("graphml").scroll_to_me();
    harness.run();
    harness.get_by_label("graphml").click();
    harness.run();
    let r2 = harness.try_snapshot("switch_import_format_switched");

    harness.get_by_role(Role::ComboBox).click();
    harness.run();
    harness.get_by_label("xlsx").scroll_to_me();
    harness.run();
    harness.get_by_label("xlsx").click();
    harness.run();

    let r3 = harness.try_snapshot("switch_import_format_after");

    assert_screenshots!(r1, r2, r3);
}

#[test]
fn switch_export_format() {
    let app_state = create_app_with_corpus(
        "single_sentence",
        &include_bytes!("../../../tests/data/single_sentence.graphml")[..],
    );

    let (mut harness, app_state) = create_test_harness(app_state);
    // Select the corpus and export it
    app_state
        .write()
        .project
        .select_corpus(Some("single_sentence".to_string()));
    wait_until_jobs_finished(&mut harness, app_state.clone());

    harness.get_by_label("Export corpus").click();
    harness.run();

    harness.get_by_role(Role::MultilineTextInput).focus();
    harness
        .get_by_role(Role::MultilineTextInput)
        .type_text("zip = true");
    harness.run();

    let r1 = harness.try_snapshot("switch_export_format_before");

    // Switch to Excel and then back, makre sure the inserted text is still there
    harness.get_by_role(Role::ComboBox).click();
    harness.run();
    harness.get_by_label("xlsx").click();
    harness.run();
    let r2 = harness.try_snapshot("switch_export_format_switched");

    harness.get_by_role(Role::ComboBox).click();
    harness.run();
    harness.get_by_label("graphml").click();
    harness.run();

    let r3 = harness.try_snapshot("switch_export_format_after");

    assert_screenshots!(r1, r2, r3);
}

#[test]
fn cleanup_unused_corpus_files() {
    let storage_dir = tempdir().unwrap();

    // Add an empty directory inside the corpus dir
    let empty_corpus_path = storage_dir.path().join("my-empty-corpus");
    std::fs::create_dir(&empty_corpus_path).unwrap();

    // Create the app structure and call the initialization functions
    let mut app_state = crate::AnnatomicApp::default();
    app_state
        .project
        .load_after_init(app_state.notifier.clone(), app_state.jobs.clone())
        .unwrap();
    app_state.project.corpus_storage_dir = Some(storage_dir.path().to_path_buf());
    app_state
        .project
        .cleanup_unused_corpus_files_in_background()
        .unwrap();

    // Start the application and wait for all jobs to finish, one of the jobs should delete the empty folder
    let (mut harness, app_state) = create_test_harness(app_state);
    harness.run_steps(10);
    // Execute the running jobs and check that corpus was importe
    wait_until_jobs_finished(&mut harness, app_state.clone());

    assert_eq!(false, empty_corpus_path.exists());
}