quinjet 0.0.50

A fast, live, keyboard-first Git source-control interface for the terminal
use super::refresh_preview::{
    loaded_changes_preview, loaded_two_file_changes_preview, parsed_change_document,
    refresh_with_status,
};
use super::*;

fn index_entry(path: PathBuf, status: &str) -> crate::git::diff::DiffFileIndexEntry {
    crate::git::diff::DiffFileIndexEntry {
        path,
        old_path: None,
        status: status.to_owned(),
        counts: None,
    }
}

#[test]
fn expanding_all_during_refresh_loads_new_files_before_the_atomic_swap() {
    let (mut app, [main_path, readme_path], mut index, loaded) = loaded_two_file_changes_preview();
    app.files_collapsed = false;
    app.collapse_preference_set = false;
    app.expanded_preview_files.clear();
    app.collapsed_preview_files = HashSet::from([main_path.clone(), readme_path.clone()]);
    let now = Instant::now();
    let new_path = PathBuf::from("src/new.rs");
    let mut status = app.status.clone();
    status.changes.push(Change {
        path: new_path.clone(),
        original_path: None,
        area: ChangeArea::Unstaged,
        status: ChangeStatus::Added,
    });
    drop(refresh_with_status(&mut app, status, now));
    index.files.push(index_entry(new_path.clone(), "added"));
    drop(app.handle_worker_event(
        WorkerEvent::LocalDiffIndex {
            generation: app.diff_generation,
            result: Ok(index),
        },
        now,
    ));

    let effects = app.handle_key(KeyEvent::new(KeyCode::Char('e'), KeyModifiers::NONE), now);

    assert!(effects.is_empty());
    assert_eq!(app.document, loaded);
    assert!(app.local_diff_pending_paths.contains(&main_path));
    assert!(app.local_diff_pending_paths.contains(&new_path));
    let generation = app.diff_generation;
    let replacement_readme =
        parsed_change_document(&readme_path, "Current diff", "replacement readme");
    let effects = app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: generation,
            path: readme_path.clone(),
            result: Ok(replacement_readme),
        },
        now,
    );
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadLocalDiffFile { path, .. }
                if path == &main_path)
    )));
    let replacement_main = parsed_change_document(&main_path, "Current diff", "replacement main");
    let effects = app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: generation,
            path: main_path.clone(),
            result: Ok(replacement_main),
        },
        now,
    );
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadLocalDiffFile { path, .. }
                if path == &new_path)
    )));
    let replacement_new = parsed_change_document(&new_path, "Current diff", "replacement new");
    drop(app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: generation,
            path: new_path.clone(),
            result: Ok(replacement_new),
        },
        now,
    ));

    assert!(!app.document_loading);
    assert_eq!(app.document.file_count(), 3);
    assert!(
        app.document
            .lines
            .iter()
            .all(|line| line.text() != "Loading diff…")
    );
    assert!(!app.preview_file_collapsed(&main_path.to_string_lossy()));
    assert!(!app.preview_file_collapsed(&readme_path.to_string_lossy()));
    assert!(!app.preview_file_collapsed(&new_path.to_string_lossy()));
}

#[test]
fn space_toggles_the_rendered_file_before_the_refreshed_index_arrives() {
    let (mut app, [_, readme_path], _, loaded) = loaded_two_file_changes_preview();
    app.focus = Focus::Content;
    let now = Instant::now();
    let status = app.status.clone();
    drop(refresh_with_status(&mut app, status, now));

    assert!(app.local_diff_index.is_none());
    let effects = app.handle_key(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE), now);

    assert!(effects.is_empty());
    assert_eq!(app.document, loaded);
    assert!(app.preview_file_collapsed(&readme_path.to_string_lossy()));
    assert!(!app.expanded_preview_files.contains(&readme_path));
}

#[test]
fn superseding_a_refresh_clears_its_queue_and_rejects_old_workspace_loads() {
    let (mut app, _, mut index, loaded) = loaded_two_file_changes_preview();
    app.files_collapsed = false;
    app.collapse_preference_set = true;
    app.collapsed_preview_files.clear();
    app.expanded_preview_files.clear();
    let now = Instant::now();
    let new_path = PathBuf::from("src/new.rs");
    let mut status = app.status.clone();
    status.changes.push(Change {
        path: new_path.clone(),
        original_path: None,
        area: ChangeArea::Unstaged,
        status: ChangeStatus::Added,
    });
    drop(refresh_with_status(&mut app, status, now));
    index.files.push(index_entry(new_path.clone(), "added"));
    drop(app.handle_worker_event(
        WorkerEvent::LocalDiffIndex {
            generation: app.diff_generation,
            result: Ok(index),
        },
        now,
    ));
    assert!(app.local_diff_pending_paths.contains(&new_path));

    let mut refresh_effects = Vec::new();
    app.filesystem_changed(&mut refresh_effects);

    assert!(app.local_diff_loading_path.is_none());
    assert!(app.local_diff_pending_paths.is_empty());
    drop(app.handle_worker_event(
        WorkerEvent::Status {
            generation: app.status_generation,
            result: Err("refresh interrupted".to_owned()),
        },
        now,
    ));
    let mut load_effects = Vec::new();
    app.request_local_diff_file(new_path, &mut load_effects);
    assert!(load_effects.is_empty());
    assert_eq!(app.document, loaded);
    assert!(!app.local_diff_preserving_document);
    assert!(app.local_diff_index.is_none());
    assert!(app.local_diff_workspace_generation.is_none());
}

#[test]
fn a_preserved_index_failure_keeps_the_rendered_diff() {
    let (mut app, [main_path, readme_path], _, loaded) = loaded_two_file_changes_preview();
    let selected = app.selected_preview_file.clone();
    let expanded = app.expanded_preview_files.clone();
    let now = Instant::now();
    let status = app.status.clone();
    drop(refresh_with_status(&mut app, status, now));

    let effects = app.handle_worker_event(
        WorkerEvent::LocalDiffIndex {
            generation: app.diff_generation,
            result: Err("unable to refresh index".to_owned()),
        },
        now,
    );

    assert!(effects.is_empty());
    assert_eq!(app.document, loaded);
    assert_eq!(app.selected_preview_file, selected);
    assert_eq!(app.expanded_preview_files, expanded);
    assert!(!app.document_loading);
    assert!(!app.local_diff_preserving_document);
    assert_eq!(
        app.local_diff_preserved_paths,
        HashSet::from([main_path, readme_path])
    );
    assert!(app.local_diff_request.is_some());
    assert!(app.local_diff_index.is_none());
}

#[test]
fn a_preserved_file_failure_keeps_the_rendered_diff() {
    let (mut app, path, index, loaded) = loaded_changes_preview();
    let now = Instant::now();
    let status = app.status.clone();
    drop(refresh_with_status(&mut app, status, now));
    let generation = app.diff_generation;
    drop(app.handle_worker_event(
        WorkerEvent::LocalDiffIndex {
            generation,
            result: Ok(index),
        },
        now,
    ));

    let effects = app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: generation,
            path: path.clone(),
            result: Err("unable to refresh file".to_owned()),
        },
        now,
    );

    assert!(effects.is_empty());
    assert_eq!(app.document, loaded);
    assert_eq!(app.selected_preview_file, Some(path.clone()));
    assert!(!app.document_loading);
    assert!(!app.local_diff_preserving_document);
    assert_eq!(app.local_diff_preserved_paths, HashSet::from([path]));
    assert!(app.local_diff_request.is_some());
    assert!(app.local_diff_index.is_none());
}

#[test]
fn expanding_a_lazy_file_after_a_failed_refresh_uses_the_retained_workspace() {
    let (mut app, [_, readme_path], ..) = loaded_two_file_changes_preview();
    app.focus = Focus::Content;
    app.files_collapsed = false;
    app.collapse_preference_set = true;
    app.collapsed_preview_files = HashSet::from([readme_path.clone()]);
    app.expanded_preview_files.clear();
    drop(app.local_diff_documents.remove(&readme_path));
    app.rebuild_local_diff_document();
    app.selected_preview_file = Some(readme_path.clone());
    app.preview_file_cursor = 1;
    let retained = app.document.clone();
    let workspace_generation = app.local_diff_workspace_generation;
    let now = Instant::now();
    drop(app.handle_key(KeyEvent::new(KeyCode::Char('r'), KeyModifiers::NONE), now));
    let generation = app.diff_generation;
    drop(app.handle_worker_event(
        WorkerEvent::Status {
            generation: app.status_generation,
            result: Err("unable to refresh status".to_owned()),
        },
        now,
    ));

    assert_eq!(app.document, retained);
    assert_eq!(app.local_diff_workspace_generation, workspace_generation);
    let effects = app.handle_key(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE), now);
    assert!(matches!(
        effects.as_slice(),
        [AppEffect::Git(command)] if matches!(
            command.as_ref(),
            WorkerCommand::LoadLocalDiffFile {
                generation: command_generation,
                workspace_generation: command_workspace,
                path,
            } if *command_generation == generation
                && Some(*command_workspace) == workspace_generation
                && path == &readme_path
        )
    ));
    assert_eq!(app.local_diff_loading_path, Some(readme_path.clone()));
    assert!(
        app.document
            .lines
            .iter()
            .any(|line| line.text() == "Loading diff…")
    );

    let replacement = parsed_change_document(&readme_path, "Current diff", "replacement readme");
    drop(app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: workspace_generation.unwrap_or_default(),
            path: readme_path,
            result: Ok(replacement),
        },
        now,
    ));

    assert!(
        app.document
            .lines
            .iter()
            .any(|line| line.text().contains("replacement readme"))
    );
    assert!(
        app.document
            .lines
            .iter()
            .all(|line| line.text() != "Loading diff…")
    );
}

#[test]
fn a_background_status_failure_does_not_abort_a_preserved_refresh() {
    let (mut app, [main_path, readme_path], index, retained) = loaded_two_file_changes_preview();
    let selected = app.selected_preview_file.clone();
    let expanded = app.expanded_preview_files.clone();
    let now = Instant::now();
    let status = app.status.clone();
    drop(refresh_with_status(&mut app, status, now));
    let generation = app.diff_generation;
    let mut effects = Vec::new();
    app.periodic_refresh(&mut effects);
    drop(app.handle_worker_event(
        WorkerEvent::Status {
            generation: app.status_generation,
            result: Err("background refresh failed".to_owned()),
        },
        now,
    ));

    assert!(app.local_diff_preserving_document);
    assert!(app.document_loading);
    assert_eq!(app.document, retained);
    assert_eq!(app.selected_preview_file, selected);
    assert_eq!(app.expanded_preview_files, expanded);
    let effects = app.handle_worker_event(
        WorkerEvent::LocalDiffIndex {
            generation,
            result: Ok(index),
        },
        now,
    );

    assert_eq!(app.document, retained);
    assert_eq!(app.selected_preview_file, selected);
    assert_eq!(app.expanded_preview_files, expanded);
    assert!(app.local_diff_preserving_document);
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadLocalDiffFile { path, .. }
                if path == &main_path)
    )));
    assert!(app.local_diff_pending_paths.contains(&readme_path));

    let replacement_main = parsed_change_document(&main_path, "Current diff", "replacement main");
    let effects = app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: generation,
            path: main_path,
            result: Ok(replacement_main),
        },
        now,
    );
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadLocalDiffFile { path, .. }
                if path == &readme_path)
    )));
    let replacement_readme =
        parsed_change_document(&readme_path, "Current diff", "replacement readme");
    drop(app.handle_worker_event(
        WorkerEvent::LocalDiffFile {
            generation,
            workspace_generation: generation,
            path: readme_path,
            result: Ok(replacement_readme),
        },
        now,
    ));

    assert!(!app.local_diff_preserving_document);
    assert!(!app.document_loading);
    assert_eq!(app.selected_preview_file, selected);
    assert_eq!(app.expanded_preview_files, expanded);
    assert!(
        app.document
            .lines
            .iter()
            .any(|line| line.text().contains("replacement readme"))
    );
}