use super::*;
use crate::api::model::Capped;
use crate::route::{issues, people, pulls};
#[test]
fn long_conversations_say_what_is_left_out() {
let mut issue = fixtures::issue();
let shown = issue.timeline.len() as u64;
issue.timeline = Capped::new(issue.timeline.items, shown + 116);
issue.comment_count = 120;
let mut state = with_issue();
let route = Route::Issues(issues::Issue {
repo: ghtui(),
number: 14,
});
open(&mut state, route, Data::Issue(Some(Box::new(issue))));
assert!(page_text(&state).contains("… 116 earlier comments and events on GitHub"));
assert!(page_text(&state).contains("120 comments"));
let pr = PrRef::parse("gold-silver-copper/ghtui#12").unwrap();
let mut activity = fixtures::activity();
let shown = activity.timeline.len() as u64;
activity.timeline = Capped::new(activity.timeline.items, shown + 1);
activity.comment_count = 101;
activity.commits = Capped::new(activity.commits.items, 250);
let mut state = with_pr();
fetched(
&mut state,
DataKey::PrActivity(pr),
Data::PrActivity(Box::new(activity)),
);
let page = page_text(&state);
assert!(
page.contains("… 1 earlier comment or event on GitHub"),
"{page}"
);
let tabs = state.chrome().tabs;
let count = |label: &str| {
tabs.iter()
.find(|(t, _)| t.label == label)
.and_then(|(t, _)| t.count)
};
assert_eq!(
(count("Conversation"), count("Commits")),
(Some(101), Some(250))
);
press(&mut state, "2");
assert!(page_text(&state).contains("… 248 earlier commits"));
}
#[test]
fn a_dismissed_review_reads_as_dismissed() {
let pr = PrRef::parse("gold-silver-copper/ghtui#12").unwrap();
let mut activity = fixtures::activity();
let dismissed = fixtures::review(
9,
"hubot",
crate::api::model::ReviewState::Dismissed,
"",
"2026-10-01T10:00:00Z",
);
activity.timeline = vec![crate::api::timeline::Item::Review(dismissed)].into();
let mut state = with_pr();
fetched(
&mut state,
DataKey::PrActivity(pr),
Data::PrActivity(Box::new(activity)),
);
let page = page_text(&state);
let hubot: Vec<&str> = page.lines().filter(|l| l.contains("hubot")).collect();
assert!(
page.contains("hubot reviewed (dismissed)"),
"hubot's lines: {hubot:?}"
);
}
#[test]
fn a_failed_home_section_says_why_and_the_others_show() {
let mut state = with_home();
let key = state.home[1].search.clone().unwrap().key();
state.data.remove(&key);
let _ = state.load_visible(false);
let err = ApiError::Network("connection refused".into());
update(
&mut state,
Msg::Fetched {
key,
result: Err(err),
cached_at: None,
source: None,
},
);
let page = page_text(&state);
let why = "Couldn't load your pull requests: network error: connection refused";
assert!(page.contains(why), "{page}");
assert!(page.contains("Add a virtualized list widget"), "{page}");
assert!(page.contains("gold-silver-copper/fux"), "{page}");
}
#[test]
fn a_failed_refresh_keeps_what_was_loaded() {
let mut state = with_pr();
let pr = PrRef::parse("gold-silver-copper/ghtui#12").unwrap();
crate::fixtures::pr_fetched(&mut state, &pr, Err(ApiError::RateLimited(42)));
let page = page_text(&state);
let why = "Couldn't refresh: rate limited by GitHub; retry in 42s.";
assert!(page.contains(why), "{page}");
assert!(
page.contains("Derives keyword, string and type colors"),
"{page}"
);
}
#[test]
fn tabs_that_dont_fit_say_how_many_are_hidden() {
let mut state = with_tabs(12);
let _ = state.switch_to_tab(6);
state.notices.dismiss();
let state = sized(state, 80, 24);
let screen = screen_text(&state);
let header = screen.lines().next().unwrap_or_default();
assert!(header.contains("‹ 5") && header.contains("6 ›"), "{screen}");
}
#[test]
fn a_link_to_lines_opens_on_them() {
let mut state = with_repo();
let text: String = (1..=80).map(|n| format!("let x{n} = {n};\n")).collect();
let blob = crate::fixtures::file("src/main.rs", &text);
let url = "https://github.com/gold-silver-copper/ghtui/blob/main/src/main.rs#L60-L62";
let crate::route::Target::Page(route) = Dest::from_url(url).target else {
panic!("{url}");
};
let route = route.split(None).unwrap_or(route);
open(&mut state, route, Data::Blob(Box::new(blob)));
let screen = screen_text(&state);
assert!(screen.contains("let x62 = 62;"), "{screen}");
assert!(!screen.contains("let x1 = 1;"), "{screen}");
}
#[test]
fn a_link_to_a_steps_line_opens_its_log() {
let state = sized(with_job(Some((2, 2))), 100, 40);
let screen = screen_text(&state);
assert!(
screen.contains("Syncing repository: gold-silver-copper/ghtui"),
"{screen}"
);
}
#[test]
fn a_filter_is_edited_in_place() {
let mut state = with_repo();
press(&mut state, "2/label:bug");
let Some(Overlay::Search(sb)) = &state.overlay else {
panic!("not filtering");
};
let (field, in_place) = state.search_field(sb);
assert!(in_place);
let buf = draw(&state);
let row = |r: ratatui::layout::Rect| -> String {
(r.left()..r.right())
.map(|x| buf[(x, r.y)].symbol())
.collect()
};
assert!(row(field).contains("⌕ is:open label:bug"), "{}", row(field));
assert!(!row(state.header_search()).contains("label:bug"));
press(&mut state, "<Enter>");
let query = state.route().and_then(|r| r.family().query());
assert_eq!(query, Some("is:open label:bug"));
}
#[test]
fn a_search_selects_the_row_it_finds() {
let mut s = with_pr();
press(&mut s, "/Fixes #12<Enter><Enter>");
assert!(
matches!(
s.route(),
Some(Route::Issues(issues::Issue { number: 12, .. }))
) || matches!(s.route(), Some(Route::Pulls(pulls::Pr { pr, .. })) if pr.number == 12),
"{:?}",
s.route()
);
}
#[test]
fn unlisted_stargazers_say_why() {
let mut state = with_repo();
let mut none = fixtures::people();
(none.items, none.next) = (Vec::new(), None);
open(
&mut state,
Route::People(people::Stargazers(ghtui())),
Data::Users(Box::new(none)),
);
let page = page_text(&state);
assert!(
page.contains("GitHub doesn't list this repository's"),
"{page}"
);
}