use super::*;
use crate::route::pulls;
#[test]
fn a_pr_diff_opened_from_a_cached_pr_fetches_the_pr() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
crate::fixtures::pr_cached(&mut state, &pr, crate::fixtures::pr_detail());
let cmds = state.start_at(Target::Files(DiffOf::Pr(pr.clone())).into());
assert!(
crate::fixtures::asks_pr(&cmds, &pr),
"the cached PR is never refreshed: {cmds:?}"
);
}
#[test]
fn a_commit_diff_has_no_review() {
let mut s = state();
let of = DiffOf::Commit(RepoId::new("o", "r"), Oid::parse(&"a".repeat(40)).unwrap());
let cmds = s.open_diff(of.clone(), None);
assert!(
matches!(
&cmds[..],
[
Cmd::Git(Git::LoadDiff { of: o, base, .. }),
Cmd::Api(Api::Fetch {
key: crate::data::DataKey::CommitComments(..),
..
}),
] if *o == of && *base == crate::diff_job::PrBase::default()
),
"{cmds:?}"
);
s.diffs.insert(of, fixture_diff());
for action in [Action::Comment, Action::ToggleViewed, Action::MarkReviewed] {
s.notices.dismiss();
assert!(apply(&mut s, action).is_empty(), "{action:?}");
says(&s, "pull request");
}
}
#[test]
fn a_merged_prs_diff_is_from_its_own_base() {
let pr = PrRef::parse("o/r#1").unwrap();
for (pr_state, oid) in [
(PrState::Open, None),
(
PrState::Merged,
Some("0123456789abcdef0123456789abcdef01234567"),
),
(
PrState::Closed,
Some("0123456789abcdef0123456789abcdef01234567"),
),
] {
let mut state = state();
let mut detail = crate::fixtures::pr_detail();
detail.state = pr_state;
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
let cmds = state.open_diff(DiffOf::Pr(pr.clone()), None);
let base = cmds.iter().find_map(|c| match c {
Cmd::Git(Git::LoadDiff { base, .. }) => Some(base.clone()),
_ => None,
});
assert_eq!(base.and_then(|b| b.oid).as_deref(), oid, "{pr_state:?}");
}
}
#[test]
fn an_empty_diff_of_a_pr_with_changes_is_an_error() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let detail = crate::fixtures::pr_detail();
let head = crate::git::Oid::parse(&detail.head_oid).unwrap();
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
let cmds = state.open_diff(DiffOf::Pr(pr.clone()), None);
let job = cmds
.iter()
.find_map(|c| match c {
Cmd::Git(Git::LoadDiff { job, .. }) => Some(*job),
_ => None,
})
.unwrap();
let refs = crate::git::repo::PrRefs {
head,
base: crate::git::Oid::parse(&"b".repeat(40)).unwrap(),
merge_base: crate::git::Oid::parse(&"b".repeat(40)).unwrap(),
};
let files = crate::diff_job::DiffFiles {
refs,
files: Vec::new(),
generated: std::collections::HashSet::new(),
};
let msg = DiffMsg::Job(job, crate::diff_job::JobMsg::Files(Box::new(files)));
let _ = diff_msg(&mut state, &pr, msg);
let error = state.diffs[&DiffOf::Pr(pr)].error.clone();
assert!(error.is_some_and(|e| e.contains("GitHub says 7 files changed")));
}
fn start_pr_diff(state: &mut State, pr: &PrRef) -> crate::diff_job::JobId {
let cmds = state.open_diff(DiffOf::Pr(pr.clone()), None);
cmds.iter()
.find_map(|c| match c {
Cmd::Git(Git::LoadDiff { job, .. }) => Some(*job),
_ => None,
})
.unwrap()
}
fn no_files(job: crate::diff_job::JobId, head: &str) -> DiffMsg {
let refs = crate::git::repo::PrRefs {
head: crate::git::Oid::parse(head).unwrap(),
base: crate::git::Oid::parse(&"b".repeat(40)).unwrap(),
merge_base: crate::git::Oid::parse(&"b".repeat(40)).unwrap(),
};
let files = crate::diff_job::DiffFiles {
refs,
files: Vec::new(),
generated: std::collections::HashSet::new(),
};
DiffMsg::Job(job, crate::diff_job::JobMsg::Files(Box::new(files)))
}
#[test]
fn outdated_threads_that_beat_the_diff_are_mapped_when_it_lists() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let detail = crate::fixtures::pr_detail();
let head = detail.head_oid.clone();
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
let job = start_pr_diff(&mut state, &pr);
let early = diff_msg(
&mut state,
&pr,
DiffMsg::ThreadsLoaded(Ok(vec![thread("old", None, false, true)])),
);
assert!(
!early
.iter()
.any(|c| matches!(c, Cmd::Git(Git::MapOutdated(_)))),
"no head to map onto yet"
);
let cmds = diff_msg(&mut state, &pr, no_files(job, &head));
assert!(
cmds.iter()
.any(|c| matches!(c, Cmd::Git(Git::MapOutdated(j)) if j.get().3.len() == 1)),
"the outdated thread is never mapped: {cmds:?}"
);
}
#[test]
fn a_diff_listed_under_a_cached_pr_is_checked_when_the_pr_refreshes() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let detail = crate::fixtures::pr_detail();
let head = detail.head_oid.clone();
crate::fixtures::pr_cached(&mut state, &pr, detail.clone());
let job = start_pr_diff(&mut state, &pr);
let _ = diff_msg(&mut state, &pr, no_files(job, &head));
assert!(
state.diffs[&DiffOf::Pr(pr.clone())].error.is_none(),
"a cached PR isn't trusted for the check"
);
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
let error = state.diffs[&DiffOf::Pr(pr.clone())].error.clone();
assert!(
error
.as_deref()
.is_some_and(|e| e.contains("GitHub says 7 files changed")),
"the empty diff was never checked against the fresh PR: error = {error:?}"
);
}
#[test]
fn refreshing_a_pr_diff_refetches_the_pr() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let detail = crate::fixtures::pr_detail();
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
let job = start_pr_diff(&mut state, &pr);
let _ = diff_msg(&mut state, &pr, no_files(job, &"c".repeat(40)));
warns(&state, "moved while loading");
let cmds = act(&mut state, Action::Refresh);
assert!(
crate::fixtures::asks_pr(&cmds, &pr),
"r reloads the diff but not the PR it's checked against: {cmds:?}"
);
}
#[test]
fn refreshing_a_pr_diff_waits_for_the_pr() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let mut detail = crate::fixtures::pr_detail();
detail.state = PrState::Open;
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail.clone()));
let _ = start_pr_diff(&mut state, &pr);
let load = |cmds: &[Cmd]| {
cmds.iter().find_map(|c| match c {
Cmd::Git(Git::LoadDiff { base, .. }) => Some(base.clone()),
_ => None,
})
};
let cmds = act(&mut state, Action::Refresh);
assert_eq!(load(&cmds), None, "started from the PR it had: {cmds:?}");
detail.state = PrState::Merged;
detail.base_oid = "c".repeat(40);
let cmds = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
assert_eq!(
load(&cmds).and_then(|b| b.oid),
Oid::parse(&"c".repeat(40)).ok()
);
}
#[test]
fn a_hidden_diff_is_checked_when_it_shows_again() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let mut detail = crate::fixtures::pr_detail();
detail.changed_files = 0;
let head = detail.head_oid.clone();
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail.clone()));
let job = start_pr_diff(&mut state, &pr);
let _ = diff_msg(&mut state, &pr, no_files(job, &head));
let _ = state.back();
state.notices.dismiss();
detail.head_oid = "d".repeat(40);
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
assert_eq!(state.notices.shown(), None, "a diff off screen was checked");
let _ = state.open_diff(DiffOf::Pr(pr.clone()), None);
let _ = state.settle();
warns(&state, "moved while loading");
}
#[test]
fn since_review_asked_on_a_diff_you_left_is_dropped() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let mut detail = crate::fixtures::pr_detail();
detail.changed_files = 0;
let head = detail.head_oid.clone();
let _ = crate::fixtures::pr_fetched(&mut state, &pr, Ok(detail));
let job = start_pr_diff(&mut state, &pr);
let _ = diff_msg(
&mut state,
&pr,
DiffMsg::LastReview(Ok(Some("a".repeat(40)))),
);
let _ = act(&mut state, Action::ToggleSinceReview);
says(&state, "once the diff has loaded");
let _ = state.back();
let _ = diff_msg(&mut state, &pr, no_files(job, &head));
let mut cmds = state.open_diff(DiffOf::Pr(pr.clone()), None);
cmds.extend(state.settle());
assert!(
!cmds
.iter()
.any(|c| matches!(c, Cmd::Git(Git::SinceReview(_)))),
"a request from before you left came back: {cmds:?}"
);
}
#[test]
fn files_tab_opens_the_diff_once_the_pr_loads() {
let mut state = state();
let pr = PrRef::parse("o/r#1").unwrap();
let _ = state.push(Route::pr(pr.clone()));
assert!(press(&mut state, "4gt").is_empty(), "waits for the PR");
assert!(matches!(state.screen(), Screen::Diff(_)));
assert_eq!(state.chrome().active, Some(3));
assert_eq!(state.busy().as_deref(), Some("Loading o/r#1"));
let cmds = crate::fixtures::pr_fetched(&mut state, &pr, Ok(crate::fixtures::pr_detail()));
assert!(
cmds.iter()
.any(|c| matches!(c, Cmd::Git(Git::LoadDiff { .. }))),
"{cmds:?}"
);
press(&mut state, "2");
assert_eq!(
route(&state),
Route::Pulls(pulls::Pr {
pr,
tab: PrTab::Commits
})
);
assert_eq!(state.screens.len(), 2, "back on the PR page, not a new one");
}