use super::*;
use crate::route::{app, code, search};
#[test]
fn sections_go_both_ways() {
let mut state = with_prs(2);
crate::fixtures::section(&mut state, 0, mine(2, 2));
press(&mut state, "<Home>");
let first = page(&state).selected;
press(&mut state, "]");
assert!(page(&state).selected > first, "{:?}", page(&state).selected);
press(&mut state, "[");
assert_eq!(page(&state).selected, first);
}
#[test]
fn home_selects_the_first_row_and_opens_it() {
let mut state = with_prs(3);
assert_eq!(route(&state), Route::App(app::Home));
assert!(
selected_text(&state).contains("PR 1"),
"a fresh page selects its first row"
);
press(&mut state, "<Down>");
assert!(selected_text(&state).contains("PR 2"));
press(&mut state, "<Up>");
let cmds = fetches(press(&mut state, "<Enter>"));
let pr = PrRef::parse("o/r#1").unwrap();
assert_eq!(
cmds,
vec![
crate::fixtures::fetch_pr(&pr),
fetch(DataKey::InProjects(pr.repo.clone(), pr.number)),
fetch(DataKey::PrActivity(pr.clone()))
]
);
assert_eq!(route(&state), Route::pr(pr.clone()));
assert_eq!(state.busy().as_deref(), Some("Loading"));
crate::fixtures::pr_fetched(&mut state, &pr, Err(ApiError::Network("down".into())));
let text = page_text(&state);
assert!(text.contains("network error: down"), "{text}");
assert_eq!(
press(&mut state, "r"),
vec![crate::fixtures::fetch_pr(&pr)],
"the activity is still loading"
);
press(&mut state, "<BS>");
assert_eq!(route(&state), Route::App(app::Home));
assert!(
selected_text(&state).contains("PR 1"),
"the selection survives"
);
act(&mut state, Action::Forward);
assert_eq!(route(&state), Route::pr(pr), "forward again");
press(&mut state, ":q<Enter>");
assert!(state.quit, ":q quits");
state.quit = false;
press(&mut state, "q");
assert!(state.quit);
}
#[test]
fn a_section_opens_the_rest_of_its_list() {
let mut state = state();
crate::fixtures::section(&mut state, 1, mine(30, 31));
state.home[1].search.as_mut().unwrap().rows = 5;
state.shown_gen += 1;
state.sync_page();
let text = page_text(&state);
assert!(text.contains("Your pull requests 5 of 31"), "{text:?}");
press(&mut state, "<End>");
assert_eq!(selected_text(&state).trim(), "… 26 more");
let key = mine_key(&state);
press(&mut state, "<Enter>");
let DataKey::Search(kind, query) = key.clone() else {
panic!("{key:?}")
};
assert_eq!(route(&state), Route::Search(search::Search { kind, query }));
assert!(selected_text(&state).contains("PR 1"), "the same list");
press(&mut state, "<BS>");
let cmds = press(&mut state, "o");
let [Cmd::OpenUrl(url)] = cmds.as_slice() else {
panic!("{cmds:?}")
};
assert!(url.contains("author:@me"), "{url}");
}
#[test]
fn home_sections_are_changed_in_the_config() {
use crate::home::{Edit, EditConfig};
let mut state = with_prs(2);
press(&mut state, "<Down>");
assert!(matches!(state.section_here(), Some((1, _))));
let job = fixtures::sent::<EditConfig>(&act(&mut state, Action::MoveSectionUp));
assert_eq!(job.edit, Edit::Move { at: 1, up: true });
assert_eq!(job.shown, crate::home::defaults());
assert!(
act(&mut state, Action::MoveSectionUp).is_empty(),
"one change at a time"
);
let mut moved = crate::home::defaults();
moved.swap(0, 1);
fixtures::done(&mut state, job, Ok((moved.clone(), None)));
assert_eq!(state.home, moved);
assert!(
matches!(state.section_here(), Some((0, _))),
"the selection moved with it"
);
let job = fixtures::sent::<EditConfig>(&press(&mut state, "<Delete>"));
assert_eq!(job.edit, Edit::Remove { at: 0 });
let (removed, table) = (moved[1..].to_vec(), "[[home]]\ntitle = \"x\"".to_owned());
fixtures::done(&mut state, job, Ok((removed, Some(table.clone()))));
let restore = Edit::Restore { at: 0, table };
let job = fixtures::sent::<EditConfig>(&press(&mut state, "<C-z>"));
assert_eq!(job.edit, restore);
let refused = fixtures::failure("config.toml changed since ghtui read it");
fixtures::done(&mut state, job, Err(refused));
warns(&state, "changed since");
let job = fixtures::sent::<EditConfig>(&press(&mut state, "<C-z>"));
assert_eq!(job.edit, restore);
}
#[test]
fn a_list_is_saved_to_home() {
let mut state = state();
let _ = state.push(Route::Search(search::Search {
kind: crate::api::browse::SearchKind::Issues,
query: "repo:o/r is:pr label:bug".into(),
}));
let _ = act(&mut state, Action::SaveSection);
let cmds = press(&mut state, "<C-u>Bugs<Enter>");
assert!(state.overlay.is_none(), "the prompt closed");
assert_eq!(
cmds,
vec![fixtures::start(crate::home::EditConfig {
path: "config.toml".into(),
shown: crate::home::defaults(),
edit: crate::home::Edit::Add {
title: "Bugs".into(),
kind: crate::api::browse::SearchKind::Pulls,
query: "repo:o/r is:pr label:bug".into(),
},
what: "Added “Bugs” to Home".into(),
})]
);
let _ = state.push(Route::user("octocat"));
let _ = act(&mut state, Action::SaveSection);
assert!(state.overlay.is_none());
}
#[test]
fn home_stays_fresh_only_on_screen() {
static NOW: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1000);
let mut state = state();
state.clock = || NOW.load(std::sync::atomic::Ordering::Relaxed);
let _ = state.load_visible(false);
let cmds = timers(&mut state);
let home = Cmd::Timer(Timer::Home, crate::home::FRESH_SECS * 1000);
assert!(cmds.contains(&home), "{cmds:?}");
assert!(!timers(&mut state).contains(&home), "one timer");
NOW.store(
1000 + crate::home::FRESH_SECS,
std::sync::atomic::Ordering::Relaxed,
);
assert!(update(&mut state, Msg::Timer(Timer::Home)).is_empty());
crate::fixtures::section(&mut state, 1, mine(1, 1));
NOW.store(
1000 + 2 * crate::home::FRESH_SECS,
std::sync::atomic::Ordering::Relaxed,
);
let cmds = update(&mut state, Msg::Timer(Timer::Home));
assert_eq!(cmds.len(), 1, "{cmds:?}");
assert!(crate::fixtures::asks(&cmds, &mine_key(&state)), "{cmds:?}");
let _ = state.push(Route::user("octocat"));
crate::fixtures::section(&mut state, 1, mine(1, 1));
NOW.store(
1000 + 4 * crate::home::FRESH_SECS,
std::sync::atomic::Ordering::Relaxed,
);
assert!(update(&mut state, Msg::Timer(Timer::Home)).is_empty());
assert!(!timers(&mut state).contains(&home));
let cmds = state.back();
assert!(crate::fixtures::asks(&cmds, &mine_key(&state)), "{cmds:?}");
}
#[test]
fn going_back_home_fetches_cached_sections() {
let mut state = state();
let key = mine_key(&state);
let (value, fetched_at) = (Data::Search(Box::new(mine(1, 1))), 0);
let cached = Remote::cached(Some(crate::store::Cached { value, fetched_at }));
state.data.insert(key.clone(), cached);
let _ = state.start_at(Target::Page(Route::Code(code::Repo(ghtui()))).into());
let cmds = state.back();
assert!(crate::fixtures::asks(&cmds, &key), "{cmds:?}");
}