quinjet 0.0.73

A fast, live, keyboard-first Git source-control interface for the terminal
use std::fmt::Write;

use crossterm::event::{MouseButton, MouseEventKind};
use ratatui::Terminal;
use ratatui::backend::TestBackend;

use super::scrollbars::{mouse, render};
use super::*;

fn scroll_to_end(app: &mut App, terminal: &mut Terminal<TestBackend>, sidebar: bool) {
    render(app, terminal);
    let scrollbar = if sidebar {
        app.geometry.sidebar_scrollbar.unwrap()
    } else {
        app.geometry.content_scrollbar.unwrap()
    };
    assert!(
        mouse(
            app,
            MouseEventKind::Down(MouseButton::Left),
            scrollbar.thumb.x,
            scrollbar.thumb.y,
        )
        .is_empty()
    );
    assert!(
        mouse(
            app,
            MouseEventKind::Drag(MouseButton::Left),
            scrollbar.track.x,
            scrollbar.track.bottom() - 1,
        )
        .is_empty()
    );
    render(app, terminal);
    assert_eq!(
        if sidebar {
            app.sidebar_offset
        } else {
            app.content_scroll
        },
        scrollbar.max_offset
    );
    assert!(
        mouse(
            app,
            MouseEventKind::Up(MouseButton::Left),
            scrollbar.track.x,
            scrollbar.track.bottom() - 1,
        )
        .is_empty()
    );
    assert!(app.scrollbar_drag.is_none());
    assert!(app.text_selection.is_none());
}

#[test]
fn pull_request_file_tree_scrollbar_preserves_the_selected_file() {
    let mut app = overview_app();
    app.pull_request_section = PullRequestSection::Files;
    app.pull_request_files = (0..80)
        .map(|index| PullRequestFile {
            path: format!("file-{index:02}.rs").into(),
            old_path: None,
            status: PullRequestFileStatus::Modified,
            counts: None,
        })
        .collect();
    let mut terminal = Terminal::new(TestBackend::new(100, 24)).unwrap();
    scroll_to_end(&mut app, &mut terminal, true);
    assert_eq!(app.pull_request_tree_cursor, 0);
    assert!(rendered_text(terminal.backend().buffer()).contains("file-79.rs"));
}

#[test]
fn pull_request_check_list_scrollbar_preserves_the_selected_check() {
    let mut app = overview_app();
    let check = app.pull_request_checks.first().unwrap().clone();
    app.pull_request_checks = (0..80)
        .map(|index| PullRequestCheck {
            name: format!("Check {index:02}"),
            ..check.clone()
        })
        .collect();
    let selected = app.pull_request_check_cursor;
    let mut terminal = Terminal::new(TestBackend::new(100, 24)).unwrap();
    scroll_to_end(&mut app, &mut terminal, true);
    assert_eq!(app.pull_request_check_cursor, selected);
    assert!(rendered_text(terminal.backend().buffer()).contains("Check 79"));
}

#[test]
fn conversation_scrollbar_reaches_the_last_comment() {
    let mut app = overview_app();
    app.pull_request_conversation.entries = (0..60)
        .map(|index| ConversationEntry {
            kind: ConversationKind::Comment,
            actor: "reviewer".to_owned(),
            timestamp: "2026-08-02T11:00:00Z".to_owned(),
            detail: String::new(),
            body: format!("Comment {index:02}"),
            url: String::new(),
            reference: String::new(),
            context: String::new(),
        })
        .collect();
    let mut terminal = Terminal::new(TestBackend::new(100, 24)).unwrap();
    scroll_to_end(&mut app, &mut terminal, false);
    assert!(app.content_at_bottom);
    assert!(rendered_text(terminal.backend().buffer()).contains("Comment 59"));
}

#[test]
fn check_log_scrollbar_reaches_the_last_line_without_folding_the_step() {
    let mut app = overview_app();
    app.pull_request_check_cursor = Some(0);
    app.pull_request_step_cursor = 1;
    app.expanded_check_steps.insert(1);
    app.pull_request_check_log = Some(crate::git::github::CheckRunLog {
        truncated: false,
        unavailable: None,
        log_pending: false,
        loose_lines: Vec::new(),
        steps: vec![CheckStep {
            number: 1,
            name: "Run tests".to_owned(),
            status: PullRequestCheckStatus::Passed,
            conclusion: "success".to_owned(),
            started_at: "2026-08-02T10:00:00Z".to_owned(),
            completed_at: "2026-08-02T10:02:30Z".to_owned(),
            lines: (0..100)
                .map(|index| CheckLogLine {
                    timestamp: String::new(),
                    text: format!("Log line {index:02}"),
                    severity: CheckLogSeverity::Normal,
                })
                .collect(),
        }],
    });
    let mut terminal = Terminal::new(TestBackend::new(100, 24)).unwrap();
    scroll_to_end(&mut app, &mut terminal, false);
    assert!(app.expanded_check_steps.contains(&1));
    assert_eq!(app.pull_request_step_cursor, 1);
    assert!(rendered_text(terminal.backend().buffer()).contains("Log line 99"));
}

#[test]
fn stack_member_scrollbar_uses_the_detail_body_below_the_header() {
    let mut app = stack::stack_app();
    app.stack_inspector.section = StackMemberSection::Summary;
    let description = &mut app
        .stack_inspector
        .selected_pull_request
        .as_mut()
        .unwrap()
        .description;
    description.clear();
    for index in 0..100 {
        writeln!(description, "Detail {index:02}\n").unwrap();
    }
    let mut terminal = Terminal::new(TestBackend::new(140, 32)).unwrap();
    scroll_to_end(&mut app, &mut terminal, false);
    let scrollbar = app.geometry.content_scrollbar.unwrap();
    assert!(scrollbar.track.y > app.geometry.content.y + 3);
    assert!(app.content_at_bottom);
    assert!(rendered_text(terminal.backend().buffer()).contains("Detail 99"));
}