quinjet 0.0.73

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

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

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

fn wide_app(layout: DiffLayout) -> App {
    let mut app = App::new("/example/project", "example");
    app.diff_layout = layout;
    app.set_document(DiffDocument {
        lines: std::iter::once(test_file_header("example.rs", 200, 0))
            .chain(std::iter::repeat_n(
                test_line(
                    DiffLineKind::Added,
                    &format!("{}end marker", "界0123456789".repeat(30)),
                ),
                200,
            ))
            .collect(),
        ..DiffDocument::default()
    });
    app
}

#[test]
fn horizontal_grab_keeps_its_position_across_redraws_and_release_outside_the_track() {
    for layout in [DiffLayout::Unified, DiffLayout::SideBySide] {
        let mut app = wide_app(layout);
        let mut terminal = Terminal::new(TestBackend::new(160, 32)).unwrap();
        app.horizontal_scroll = 57;
        render(&mut app, &mut terminal);
        let bar = app.geometry.horizontal_scrollbar.unwrap();
        let column = bar.thumb.right() - 1;
        assert!(
            mouse(
                &mut app,
                MouseEventKind::Down(MouseButton::Left),
                column,
                bar.thumb.y
            )
            .is_empty()
        );
        render(&mut app, &mut terminal);
        assert!(mouse(&mut app, MouseEventKind::Drag(MouseButton::Left), column, 0).is_empty());
        assert_eq!(app.horizontal_scroll, 57);
        assert!(mouse(&mut app, MouseEventKind::Up(MouseButton::Left), column, 0).is_empty());
        assert_eq!(app.horizontal_scroll, 57);
        assert!(app.scrollbar_drag.is_none());
        drop(mouse(
            &mut app,
            MouseEventKind::Down(MouseButton::Left),
            column,
            bar.thumb.y,
        ));
        drop(mouse(
            &mut app,
            MouseEventKind::Drag(MouseButton::Left),
            column + 2,
            0,
        ));
        render(&mut app, &mut terminal);
        let moved = app.geometry.horizontal_scrollbar.unwrap();
        assert_eq!(moved.thumb.x, bar.thumb.x + 2);
        assert_eq!(app.content_scroll, 0);
        assert!(app.text_selection.is_none());
    }
}

#[test]
fn horizontal_drag_reaches_both_ends_after_resize_and_mouse_up_applies_the_final_position() {
    for layout in [DiffLayout::Unified, DiffLayout::SideBySide] {
        let mut app = wide_app(layout);
        let mut terminal = Terminal::new(TestBackend::new(160, 32)).unwrap();
        render(&mut app, &mut terminal);
        let bar = app.geometry.horizontal_scrollbar.unwrap();
        drop(mouse(
            &mut app,
            MouseEventKind::Down(MouseButton::Left),
            bar.thumb.x,
            bar.thumb.y,
        ));
        terminal.backend_mut().resize(100, 24);
        render(&mut app, &mut terminal);
        let resized = app.geometry.horizontal_scrollbar.unwrap();
        assert!(resized.max_offset > bar.max_offset);
        drop(mouse(
            &mut app,
            MouseEventKind::Drag(MouseButton::Left),
            u16::MAX,
            0,
        ));
        render(&mut app, &mut terminal);
        assert_eq!(app.horizontal_scroll, resized.max_offset);
        assert!(rendered_text(terminal.backend().buffer()).contains("end marker"));
        assert!(mouse(&mut app, MouseEventKind::Up(MouseButton::Left), 0, 0).is_empty());
        render(&mut app, &mut terminal);
        assert_eq!(app.horizontal_scroll, 0);
        assert!(app.scrollbar_drag.is_none());
        assert!(app.text_selection.is_none());
    }
}

#[test]
fn horizontal_drags_cancel_when_keyboard_overlay_mouse_capture_or_content_changes() {
    for transition in 0..7 {
        let mut app = wide_app(DiffLayout::Unified);
        let mut terminal = Terminal::new(TestBackend::new(100, 24)).unwrap();
        render(&mut app, &mut terminal);
        let bar = app.geometry.horizontal_scrollbar.unwrap();
        drop(mouse(
            &mut app,
            MouseEventKind::Down(MouseButton::Left),
            bar.thumb.x,
            bar.thumb.y,
        ));
        assert!(app.scrollbar_drag.is_some());
        match transition {
            0 => {
                drop(app.handle_key(
                    KeyEvent::new(KeyCode::Char('0'), KeyModifiers::NONE),
                    Instant::now(),
                ));
            }
            1 => {
                app.modal = Some(Modal::Help {
                    selected: 0,
                    scroll: 0,
                    hover: None,
                });
            }
            2 => app.scm_menu_open = true,
            3 => app.pr_menu_open = true,
            4 => app.configure_mouse_capture(false),
            5 => terminal.backend_mut().resize(60, 12),
            _ => app.set_document(DiffDocument::empty("short", "fits")),
        }
        render(&mut app, &mut terminal);
        assert!(app.scrollbar_drag.is_none());
        assert!(
            mouse(
                &mut app,
                MouseEventKind::Up(MouseButton::Left),
                bar.track.right(),
                0
            )
            .is_empty()
        );
        assert_eq!(app.horizontal_scroll, 0);
        assert_eq!(app.content_scroll, 0);
    }
}

#[test]
fn vertical_and_horizontal_scrollbars_use_independent_axes_with_one_drag_state() {
    let mut app = wide_app(DiffLayout::Unified);
    let mut terminal = Terminal::new(TestBackend::new(160, 32)).unwrap();
    render(&mut app, &mut terminal);
    let horizontal = app.geometry.horizontal_scrollbar.unwrap();
    drop(mouse(
        &mut app,
        MouseEventKind::Down(MouseButton::Left),
        horizontal.track.right() - 1,
        horizontal.track.y,
    ));
    render(&mut app, &mut terminal);
    assert_eq!(app.horizontal_scroll, horizontal.max_offset);
    let vertical = app.geometry.content_scrollbar.unwrap();
    drop(mouse(
        &mut app,
        MouseEventKind::Down(MouseButton::Left),
        vertical.thumb.x,
        vertical.thumb.y,
    ));
    drop(mouse(
        &mut app,
        MouseEventKind::Drag(MouseButton::Left),
        0,
        u16::MAX,
    ));
    render(&mut app, &mut terminal);
    assert_eq!(app.content_scroll, vertical.max_offset);
    assert_eq!(app.horizontal_scroll, horizontal.max_offset);
    assert!(mouse(&mut app, MouseEventKind::Up(MouseButton::Left), 0, u16::MAX).is_empty());
    assert!(app.scrollbar_drag.is_none());
    assert!(app.text_selection.is_none());
}