quinjet 0.0.71

A fast, live, keyboard-first Git source-control interface for the terminal
use super::*;
use crate::search::{SearchFile, SearchMode, SearchRequest, SearchSource, SearchTarget};

#[test]
fn name_search_finds_a_change_path_without_reading_contents() {
    let test_repository = TestRepository::new();
    fs::write(test_repository.path.join("secret.txt"), "payload\n").unwrap();
    let repository = test_repository.repository();
    let hits = repository
        .search(&SearchRequest {
            query: "secret".to_owned(),
            mode: SearchMode::Name,
            target: SearchTarget::Changes {
                files: vec![SearchFile {
                    path: PathBuf::from("secret.txt"),
                    source: SearchSource::Worktree,
                }],
            },
        })
        .unwrap();
    assert_eq!(hits.paths, vec!["secret.txt".to_owned()]);
}

#[test]
fn contents_search_matches_worktree_bytes_and_ignores_names() {
    let test_repository = TestRepository::new();
    fs::write(test_repository.path.join("notes.txt"), "unique-token\n").unwrap();
    let repository = test_repository.repository();
    let files = vec![SearchFile {
        path: PathBuf::from("notes.txt"),
        source: SearchSource::Worktree,
    }];
    let by_name = repository
        .search(&SearchRequest {
            query: "unique-token".to_owned(),
            mode: SearchMode::Name,
            target: SearchTarget::Changes {
                files: files.clone(),
            },
        })
        .unwrap();
    assert!(by_name.paths.is_empty(), "{by_name:?}");
    let by_contents = repository
        .search(&SearchRequest {
            query: "unique-token".to_owned(),
            mode: SearchMode::Contents,
            target: SearchTarget::Changes { files },
        })
        .unwrap();
    assert_eq!(by_contents.paths, vec!["notes.txt".to_owned()]);
}

#[test]
fn staged_contents_search_reads_the_index_version() {
    let test_repository = TestRepository::new();
    fs::write(test_repository.path.join("notes.txt"), "staged-token\n").unwrap();
    run_test_git(&test_repository.path, ["add", "notes.txt"]);
    fs::write(test_repository.path.join("notes.txt"), "worktree-token\n").unwrap();
    let repository = test_repository.repository();
    let request = |query: &str| SearchRequest {
        query: query.to_owned(),
        mode: SearchMode::Contents,
        target: SearchTarget::Changes {
            files: vec![SearchFile {
                path: PathBuf::from("notes.txt"),
                source: SearchSource::Index,
            }],
        },
    };
    assert_eq!(
        repository.search(&request("staged-token")).unwrap().paths,
        vec!["notes.txt".to_owned()]
    );
    assert_eq!(
        repository.search(&request("worktree-token")).unwrap().paths,
        Vec::<String>::new()
    );
}

#[test]
fn contents_search_reuses_state_across_worktree_index_and_pull_request_documents() {
    let test_repository = TestRepository::new();
    let documents: &[(&str, &[u8])] = &[
        ("00-empty.txt", b""),
        ("01-hit.txt", b"NEEDLE\n"),
        ("02-miss.txt", b"no match\n"),
        ("03-utf16.txt", b"\xff\xfen\0e\0e\0d\0l\0e\0\n\0"),
        ("04-binary.bin", b"needle\0needle\n"),
        ("05-hit.txt", b"needle\n"),
        ("06-empty.txt", b""),
    ];
    for &(name, contents) in documents {
        fs::write(test_repository.path.join(name), contents).unwrap();
    }
    run_test_git(&test_repository.path, ["add", "."]);
    let repository = test_repository.repository();
    for source in [SearchSource::Worktree, SearchSource::Index] {
        for mode in [SearchMode::Contents, SearchMode::Both] {
            let hits = repository
                .search(&SearchRequest {
                    query: "^needle$".to_owned(),
                    mode,
                    target: SearchTarget::Changes {
                        files: documents
                            .iter()
                            .map(|(name, _)| SearchFile {
                                path: PathBuf::from(name),
                                source,
                            })
                            .collect(),
                    },
                })
                .unwrap();
            assert_eq!(
                hits.paths,
                vec!["01-hit.txt", "03-utf16.txt", "05-hit.txt"],
                "{source:?}: {mode:?}"
            );
        }
    }
    let base_oid = run_test_git(&test_repository.path, ["rev-parse", "HEAD"]);
    run_test_git(
        &test_repository.path,
        [
            "-c",
            "user.name=Quinjet Test",
            "-c",
            "user.email=quinjet@example.com",
            "commit",
            "--message=search fixtures",
        ],
    );
    let workspace = repository
        .prepare_pull_request_diff(
            &github::PullRequest {
                base_oid,
                head_oid: run_test_git(&test_repository.path, ["rev-parse", "HEAD"]),
                ..github::PullRequest::default()
            },
            |_| {},
        )
        .unwrap();
    let paths: Vec<PathBuf> = documents
        .iter()
        .map(|(name, _)| PathBuf::from(name))
        .collect();
    for mode in [SearchMode::Contents, SearchMode::Both] {
        assert_eq!(
            workspace.search_paths("^needle$", mode, &paths).paths,
            vec!["01-hit.txt", "03-utf16.txt", "05-hit.txt"],
            "{mode:?}"
        );
    }
}

#[test]
fn history_contents_search_stays_on_the_loaded_page() {
    let test_repository = TestRepository::new();
    run_test_git(
        &test_repository.path,
        [
            "-c",
            "user.name=Quinjet Test",
            "-c",
            "user.email=quinjet@example.com",
            "commit",
            "--allow-empty",
            "--message=unique-history-body",
        ],
    );
    let repository = test_repository.repository();
    let hits = repository
        .search(&SearchRequest {
            query: "unique-history-body".to_owned(),
            mode: SearchMode::Contents,
            target: SearchTarget::History {
                revision: "HEAD".to_owned(),
                skip: 0,
                limit: 10,
                selected: None,
            },
        })
        .unwrap();
    assert_eq!(hits.commits.len(), 1, "{hits:?}");
}

#[test]
fn history_contents_search_reuses_state_for_messages_and_selected_patch() {
    let test_repository = TestRepository::new();
    let mut expected = Vec::new();
    for message in ["needle body", "no match", "NEEDLE body", "selected patch"] {
        if message == "selected patch" {
            fs::write(test_repository.path.join("notes.txt"), "needle patch\n").unwrap();
            run_test_git(&test_repository.path, ["add", "notes.txt"]);
        }
        run_test_git(
            &test_repository.path,
            [
                "-c",
                "user.name=Quinjet Test",
                "-c",
                "user.email=quinjet@example.com",
                "commit",
                "--allow-empty",
                "--message",
                message,
            ],
        );
        if message != "no match" {
            expected.push(run_test_git(&test_repository.path, ["rev-parse", "HEAD"]));
        }
    }
    let selected = run_test_git(&test_repository.path, ["rev-parse", "HEAD"]);
    expected.sort();
    let repository = test_repository.repository();
    let hits = repository
        .search(&SearchRequest {
            query: "needle".to_owned(),
            mode: SearchMode::Contents,
            target: SearchTarget::History {
                revision: "HEAD".to_owned(),
                skip: 0,
                limit: 10,
                selected: Some(selected),
            },
        })
        .unwrap();
    assert_eq!(hits.commits, expected);
}