quinjet 0.0.58

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

#[test]
fn explicit_refresh_preserves_the_stack_workspace_and_range() {
    let mut app = App::new("/tmp/repo", "repo");
    app.view = View::PullRequests;
    app.pull_request = Some(pull_request(42, "Root", "acme/widget"));
    app.pull_request_exact_number = Some(42);
    app.pull_request_stack = Some(pull_request_stack(2));
    app.pull_request_stack_anchor = Some(1);
    app.pull_request_stack_cursor = Some(2);
    app.pull_request_section = PullRequestSection::Stack;
    app.stack_inspector.section = StackMemberSection::Conversation;
    app.stack_inspector.diff_open = true;
    app.document = DiffDocument::empty("Stack range", "keep this diff");
    app.reconcile_stack_inspector();
    let identity = app.stack_inspector.selected_identity.clone();
    let mut effects = Vec::new();

    app.request_active_refresh(&mut effects);

    assert!(app.pull_request.is_some());
    assert!(app.pull_request_stack.is_some());
    assert_eq!(app.pull_request_stack_range(), Some((1, 2)));
    assert_eq!(app.pull_request_section, PullRequestSection::Stack);
    assert_eq!(
        app.stack_inspector.section,
        StackMemberSection::Conversation
    );
    assert_eq!(app.stack_inspector.selected_identity, identity);
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LookupPullRequest { refresh: true, .. })
    )));

    let generation = app.pull_request_generation;
    app.handle_worker_event(
        WorkerEvent::PullRequestLookup {
            generation,
            result: Err("refresh unavailable".to_owned()),
        },
        Instant::now(),
    );

    assert_eq!(app.document.title, "Stack range");
    assert!(app.stack_inspector.diff_open);
    assert_eq!(
        app.pull_request_stack_error.as_deref(),
        Some("refresh unavailable")
    );
}

#[test]
fn stack_teardown_invalidates_in_flight_composed_diff_work() {
    let mut app = App::new("/tmp/repo", "repo");
    app.view = View::PullRequests;
    app.pull_request = Some(pull_request(42, "Root", "acme/widget"));
    app.pull_request_stack = Some(pull_request_stack(2));
    app.pull_request_section = PullRequestSection::Stack;
    app.stack_inspector.diff_open = true;
    app.diff_generation = 7;
    app.document_loading = true;
    app.sidebar_offset = 2;
    app.sidebar_free_scroll = true;
    app.sidebar_last_cursor = Some(1);
    app.pull_request_progress = Some(PullRequestProgress::FindingMergeBase);
    app.pull_request_checks = vec![check("CI", PullRequestCheckStatus::Passed)];
    app.pull_request_conversation.entries = vec![crate::git::github::ConversationEntry {
        kind: crate::git::github::ConversationKind::Comment,
        actor: "octocat".to_owned(),
        timestamp: "2026-08-20T10:00:00Z".to_owned(),
        detail: String::new(),
        body: "old".to_owned(),
        url: String::new(),
        reference: String::new(),
        context: String::new(),
    }];
    app.pull_request_review.head_oid = "old-head".to_owned();
    let mut effects = Vec::new();

    app.apply_pull_request_stack_snapshot(None, &mut effects);

    assert_eq!(app.diff_generation, 8);
    assert!(!app.document_loading);
    assert_eq!(app.pull_request_section, PullRequestSection::Overview);
    assert!(!app.stack_inspector.diff_open);
    assert!(app.pull_request_progress.is_none());
    assert_eq!(app.sidebar_offset, 0);
    assert!(!app.sidebar_free_scroll);
    assert_eq!(app.sidebar_last_cursor, None);
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadPullRequestChecks { refresh: true, .. })
    )));
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadPullRequestConversation { .. })
    )));
    assert!(effects.iter().any(|effect| matches!(
        effect,
        AppEffect::Git(command)
            if matches!(command.as_ref(), WorkerCommand::LoadPullRequestReview { .. })
    )));
}

#[test]
fn changed_stack_topology_invalidates_member_rows() {
    let mut app = App::new("/tmp/repo", "repo");
    app.view = View::PullRequests;
    let stack = pull_request_stack(2);
    app.sidebar_offset = 2;
    app.sidebar_free_scroll = true;
    app.sidebar_last_cursor = Some(1);
    app.apply_pull_request_stack_snapshot(Some(stack.clone()), &mut Vec::new());
    assert_eq!(app.sidebar_offset, 0);
    assert!(!app.sidebar_free_scroll);
    assert_eq!(app.sidebar_last_cursor, None);
    app.stack_inspector_content_rows_key = Some((StackMemberSection::Summary, 80, 1, 1));
    let mut changed = stack;
    changed.size = 5;
    changed.truncated = true;

    app.apply_pull_request_stack_snapshot(Some(changed), &mut Vec::new());

    assert!(app.stack_inspector_content_rows_key.is_none());
    assert!(app.stack_inspector.tip_identity.is_none());
}

#[test]
fn stack_identity_transition_resets_only_the_pull_request_sidebar() {
    let mut app = App::new("/tmp/repo", "repo");
    app.switch_view(View::PullRequests, &mut Vec::new());
    app.sidebar_offset = 2;
    app.sidebar_free_scroll = true;
    app.sidebar_last_cursor = Some(1);
    app.switch_view(View::History, &mut Vec::new());
    app.sidebar_offset = 7;
    app.sidebar_free_scroll = true;
    app.sidebar_last_cursor = Some(5);

    app.apply_pull_request_stack_snapshot(Some(pull_request_stack(2)), &mut Vec::new());

    assert_eq!(app.sidebar_offset, 7);
    assert!(app.sidebar_free_scroll);
    assert_eq!(app.sidebar_last_cursor, Some(5));

    app.switch_view(View::PullRequests, &mut Vec::new());

    assert_eq!(app.sidebar_offset, 0);
    assert!(!app.sidebar_free_scroll);
    assert_eq!(app.sidebar_last_cursor, None);
}

#[test]
fn failed_stack_refresh_retains_data_until_a_successful_retry() {
    let mut app = App::new("/tmp/repo", "repo");
    app.pull_request_generation = 4;
    app.pull_request = Some(pull_request(42, "Root", "acme/widget"));
    app.pull_request_stack = Some(pull_request_stack(2));

    app.handle_worker_event(
        WorkerEvent::PullRequestStack {
            generation: 4,
            result: Err("stack metadata refresh failed".to_owned()),
        },
        Instant::now(),
    );

    assert!(app.pull_request_stack.is_some());
    assert_eq!(
        app.pull_request_stack_error.as_deref(),
        Some("stack metadata refresh failed")
    );
    assert!(app.pull_request_warnings.iter().any(|warning| {
        warning == "Unable to load pull-request stack: stack metadata refresh failed"
    }));

    app.handle_worker_event(
        WorkerEvent::PullRequestStack {
            generation: 4,
            result: Ok(crate::git::github::PullRequestStackSnapshot {
                stack: Some(pull_request_stack(2)),
                warnings: Vec::new(),
                from_cache: false,
            }),
        },
        Instant::now(),
    );

    assert!(app.pull_request_stack_error.is_none());
    assert!(
        app.pull_request_warnings
            .iter()
            .all(|warning| !warning.starts_with("Unable to load pull-request stack:"))
    );
}

#[test]
fn stale_stack_cache_keeps_the_gate_error_until_network_recovery() {
    let mut app = App::new("/tmp/repo", "repo");
    app.pull_request_generation = 4;
    app.pull_request = Some(pull_request(42, "Root", "acme/widget"));

    app.handle_worker_event(
        WorkerEvent::PullRequestStack {
            generation: 4,
            result: Ok(crate::git::github::PullRequestStackSnapshot {
                stack: Some(pull_request_stack(2)),
                warnings: vec![
                    "GitHub is unavailable; showing stale cached stack data for #42".to_owned(),
                ],
                from_cache: true,
            }),
        },
        Instant::now(),
    );

    assert!(
        app.pull_request_stack_error
            .as_deref()
            .is_some_and(|error| error.contains("stale cached stack data"))
    );

    let current = app.pull_request_stack.clone();
    app.handle_worker_event(
        WorkerEvent::PullRequestStack {
            generation: 4,
            result: Ok(crate::git::github::PullRequestStackSnapshot {
                stack: None,
                warnings: vec![
                    "GitHub is unavailable; showing stale cached stack data for #42".to_owned(),
                ],
                from_cache: true,
            }),
        },
        Instant::now(),
    );

    assert_eq!(app.pull_request_stack, current);
}