quinjet 0.0.51

A fast, live, keyboard-first Git source-control interface for the terminal
use super::*;

fn resolves(path: &[&str]) -> bool {
    let root = Cli::command();
    let mut command = &root;
    let mut found = Vec::new();
    for name in path {
        let Some(child) = command.find_subcommand(name) else {
            return false;
        };
        found.push(child);
        command = found.last().unwrap();
    }
    true
}

macro_rules! operation_routes {
        ($($pattern:pat => $sample:expr => [$($path:literal),+];)+) => {
            const fn verb_for(operation: &GitOperation) -> &'static [&'static str] {
                match operation {
                    $($pattern => &[$($path),+],)+
                }
            }

            #[test]
            fn every_operation_the_interface_performs_has_a_verb() {
                let operations = [$($sample),+];
                for operation in &operations {
                    let path = verb_for(operation);
                    assert!(
                        resolves(path),
                        "{operation:?} names the verb {path:?}, which the command tree does not have"
                    );
                }
                let kinds: HashSet<_> = operations.iter().map(std::mem::discriminant).collect();
                assert_eq!(
                    kinds.len(),
                    operations.len(),
                    "every operation variant must have one route fixture"
                );
            }
        };
    }

operation_routes! {
    GitOperation::Stage(_) => GitOperation::Stage(Vec::new()) => ["stage"];
    GitOperation::StageAll => GitOperation::StageAll => ["stage"];
    GitOperation::Unstage(_) => GitOperation::Unstage(Vec::new()) => ["unstage"];
    GitOperation::UnstageAll => GitOperation::UnstageAll => ["unstage"];
    GitOperation::Discard(_) => GitOperation::Discard(Vec::new()) => ["discard"];
    GitOperation::Remove(_) => GitOperation::Remove(Vec::new()) => ["remove"];
    GitOperation::Commit { .. } => GitOperation::Commit { message: String::new(), amend: false } => ["commit"];
    GitOperation::Fetch => GitOperation::Fetch => ["fetch"];
    GitOperation::Pull => GitOperation::Pull => ["pull"];
    GitOperation::Push => GitOperation::Push => ["push"];
    GitOperation::Sync => GitOperation::Sync => ["sync"];
    GitOperation::Checkout(_) => GitOperation::Checkout(String::new()) => ["branch", "switch"];
    GitOperation::CreateBranch { .. } => GitOperation::CreateBranch { name: String::new(), start: None } => ["branch", "create"];
    GitOperation::RenameBranch { .. } => GitOperation::RenameBranch { old: String::new(), new: String::new() } => ["branch", "rename"];
    GitOperation::DeleteBranch(_) => GitOperation::DeleteBranch(String::new()) => ["branch", "delete"];
    GitOperation::StashPush { .. } => GitOperation::StashPush { message: String::new(), include_untracked: false, staged: false, paths: Vec::new() } => ["stash", "push"];
    GitOperation::StashApply(_) => GitOperation::StashApply(String::new()) => ["stash", "apply"];
    GitOperation::StashPop(_) => GitOperation::StashPop(None) => ["stash", "pop"];
    GitOperation::StashDrop(_) => GitOperation::StashDrop(String::new()) => ["stash", "drop"];
    GitOperation::StashClear => GitOperation::StashClear => ["stash", "clear"];
    GitOperation::ResolveConflict { .. } => GitOperation::ResolveConflict { path: PathBuf::new(), choice: ConflictChoice::Ours } => ["resolve"];
    GitOperation::CherryPick(_) => GitOperation::CherryPick(String::new()) => ["cherry-pick"];
    GitOperation::Revert(_) => GitOperation::Revert(String::new()) => ["revert"];
}

#[test]
fn the_read_only_views_have_verbs_too() {
    for path in [
        ["status"].as_slice(),
        ["diff"].as_slice(),
        ["log"].as_slice(),
        ["show"].as_slice(),
        ["branch", "list"].as_slice(),
        ["branch", "compare"].as_slice(),
        ["stash", "list"].as_slice(),
        ["stash", "show"].as_slice(),
        ["worktree", "list"].as_slice(),
        ["project", "list"].as_slice(),
        ["repos"].as_slice(),
        ["pr", "view"].as_slice(),
        ["pr", "files"].as_slice(),
        ["pr", "diff"].as_slice(),
        ["pr", "checks"].as_slice(),
        ["pr", "conversation"].as_slice(),
        ["pr", "logs"].as_slice(),
        ["pr", "open"].as_slice(),
        ["pr", "reviews", "show"].as_slice(),
        ["tui"].as_slice(),
        ["completions"].as_slice(),
        ["man"].as_slice(),
        ["capabilities"].as_slice(),
    ] {
        assert!(resolves(path), "the command tree is missing {path:?}");
    }
}