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_commit_scrollbar_reaches_the_oldest_commit_without_opening_it() {
let mut app = overview_app();
app.pull_request_section = PullRequestSection::Commits;
app.pr_commits.loaded = true;
app.pr_commits.snapshot.commits = (0..1908)
.map(|index| crate::git::github::PullRequestCommit {
oid: format!("{index:040x}"),
abbreviated_oid: format!("{index:08x}"),
subject: format!("Commit {index:04}"),
author: "Casey".to_owned(),
..crate::git::github::PullRequestCommit::default()
})
.collect();
app.pr_commits.snapshot.total_commits = 1908;
let mut terminal = Terminal::new(TestBackend::new(100, 24)).unwrap();
scroll_to_end(&mut app, &mut terminal, true);
assert_eq!(app.pr_commits.cursor, 0);
assert!(app.selected_pr_commit().is_none());
assert_eq!(app.pull_request_section, PullRequestSection::Commits);
let scrollbar = app.geometry.sidebar_scrollbar.unwrap();
assert_eq!(scrollbar.max_offset, 3816 - scrollbar.viewport);
assert_eq!(scrollbar.thumb.bottom(), scrollbar.track.bottom());
assert!(rendered_text(terminal.backend().buffer()).contains("Commit 1907"));
assert!(app.geometry.sidebar_hits.iter().all(|hit| {
!matches!(hit.target, SidebarHit::PullRequestCommit(_))
|| hit.area.right() <= scrollbar.track.x
}));
app.pr_commits.snapshot.commits.truncate(2);
app.pr_commits.snapshot.total_commits = 2;
render(&mut app, &mut terminal);
assert!(app.geometry.sidebar_scrollbar.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"));
}