use crate::api::gist::{FileChange, MadeGist};
use crate::api::release::{Made, NewRelease, TagLook};
use crate::api::watch::{Subscription, SubscriptionState, Watched};
use crate::route::{gists, releases, wiki};
use ratatui_textarea::CursorMove;
use super::*;
use crate::fixtures::{self};
fn composer(s: &mut State) -> &mut crate::compose::Compose {
match &mut s.overlay {
Some(Overlay::Compose(c)) => c,
_ => panic!("no composer"),
}
}
fn write(s: &mut State, text: &str) {
let c = composer(s);
c.input.select_all();
c.input.cut();
c.input.insert_str(text);
}
#[test]
fn a_release_is_made_from_a_new_tag_and_its_page_follows() {
let mut s = with_repo();
let cmds = act(&mut s, Action::NewRelease);
let repo = ghtui();
assert_eq!(
s.route(),
Some(&Route::Releases(releases::NewRelease(repo.clone())))
);
assert!(
crate::fixtures::asks(&cmds, &DataKey::Tags(repo.clone())),
"{cmds:?}"
);
let tags = Data::Tags(Box::new(fixtures::tags()));
fetched(&mut s, DataKey::Tags(repo.clone()), tags);
assert!(
matches!(&s.overlay, Some(Overlay::Picker(_))),
"the tag first"
);
let cmds = press(&mut s, "v9.0<Enter>");
let look = DataKey::TagLook(repo.clone(), "v9.0".into());
assert!(fixtures::asks(&cmds, &look), "{cmds:?}");
let new_tag = TagLook {
exists: false,
release: None,
};
fixtures::fetched(&mut s, look, Data::TagLook(Box::new(new_tag)));
assert!(
page_text(&s).contains("v9.0 is a new tag"),
"the form behind the composer says what GitHub found"
);
write(&mut s, "Nine\n\nWhat changed.");
press(&mut s, "<Esc>");
press(&mut s, "<C-s>");
assert_eq!(overlay!(s, Ask).choices.len(), 2, "publish or save a draft");
let sent = press(&mut s, "<Enter>");
let new = NewRelease {
repo: repo.clone(),
tag: "v9.0".into(),
target: None,
name: "Nine".into(),
notes: "What changed.".into(),
draft: false,
prerelease: false,
latest: true,
};
let route = Route::Releases(releases::NewRelease(repo.clone()));
let create = crate::release::Save {
route,
id: None,
new,
};
assert_eq!(sent, [fixtures::start(create.clone())]);
let made = Made {
tag_name: "v9.0".into(),
draft: false,
html_url: String::new(),
};
fixtures::done(&mut s, create, Ok(made));
let page = Route::Releases(releases::Release {
repo: repo.clone(),
tag: "v9.0".into(),
});
assert_eq!(s.route(), Some(&page));
says(&s, "Published v9.0");
assert!(s.busy().is_some_and(|b| b.contains("Waiting for GitHub")));
let release = Data::Release(Box::new(fixtures::release(true)));
fetched(&mut s, DataKey::Release(repo, "v9.0".into()), release);
assert!(s.following.is_none(), "GitHub shows it");
}
#[test]
fn a_tag_with_a_release_is_refused() {
let mut s = with_repo();
act(&mut s, Action::NewRelease);
press(&mut s, "v0.2.0<Enter>");
let taken = TagLook {
exists: true,
release: Some((false, String::new())),
};
let look = DataKey::TagLook(ghtui(), "v0.2.0".into());
fixtures::fetched(&mut s, look, Data::TagLook(Box::new(taken)));
let sent = press(&mut s, "<C-s>");
assert_eq!(sent, []);
assert_eq!(
composer(&mut s).sent.error.as_deref(),
Some("v0.2.0 has a release already")
);
}
#[test]
fn a_thread_is_unsubscribed_and_followed() {
let mut s = with_issue();
let of = Watched::Thread(ghtui(), 14);
let cmds = act(&mut s, Action::Watch);
assert!(
crate::fixtures::asks(&cmds, &DataKey::Subscription(of.clone())),
"{cmds:?}"
);
let now = Data::Subscription(Box::new(fixtures::subscription()));
fetched(&mut s, DataKey::Subscription(of.clone()), now);
let Some(Overlay::Picker(p)) = &s.overlay else {
panic!("no picker");
};
let rows = s.picker_rows(p);
assert!(rows[0].0.hint.starts_with("current"), "{:?}", rows[0].0);
let sent = press(&mut s, "Unsub<Enter>");
let work = crate::watch::Subscribe {
of: of.clone(),
id: crate::api::model::NodeId::new("I_sub"),
state: SubscriptionState::Unsubscribed,
};
assert_eq!(sent, [fixtures::start(work.clone())]);
let cmds = fixtures::done(&mut s, work, Ok(()));
says(&s, "Unsubscribed from gold-silver-copper/ghtui#14");
assert!(
crate::fixtures::asks(&cmds, &DataKey::Subscription(of.clone())),
"{cmds:?}"
);
let shown = Subscription {
state: Some(SubscriptionState::Unsubscribed),
..fixtures::subscription()
};
fetched(
&mut s,
DataKey::Subscription(of),
Data::Subscription(Box::new(shown)),
);
assert!(s.following.is_none());
}
#[test]
fn your_gist_is_edited_and_its_page_follows() {
let mut s = with_gist();
press(&mut s, "e");
let route = Route::Gists(gists::EditGist {
owner: "octocat".into(),
id: "6cad326836d38bd3a7ae".into(),
});
assert_eq!(s.route(), Some(&route));
let text = page_text(&s);
assert!(
text.contains("hello.rs") && text.contains("README.md"),
"{text}"
);
press(&mut s, "c");
composer(&mut s).input.move_cursor(CursorMove::Bottom);
composer(&mut s).input.move_cursor(CursorMove::End);
composer(&mut s).input.insert_str("\n// more");
let sent = press(&mut s, "<C-s>");
let update = fixtures::sent::<crate::gist::Update>(&sent);
let change = &update.change;
assert_eq!(update.route, route);
let text = "fn main() {\n println!(\"hello\");\n}\n// more";
assert_eq!(
change.changed,
[FileChange {
was: "hello.rs".into(),
now: Some(("hello.rs".into(), text.into())),
}]
);
assert!(change.added.is_empty());
fixtures::done(&mut s, update, Ok(()));
let page = Route::Gists(gists::Gist {
owner: Some("octocat".into()),
id: "6cad326836d38bd3a7ae".into(),
});
assert_eq!(s.route(), Some(&page), "back on the gist");
assert_eq!(s.screens.len(), 2, "the form isn't left behind");
assert!(s.following.is_some());
}
#[test]
fn someone_elses_gist_says_why_it_cant_be_changed() {
let mut s = with_gist();
s.viewer = Some("hubot".into());
press(&mut s, "D");
says(&s, "It's @octocat's gist: only they can change it");
assert!(s.overlay.is_none());
}
#[test]
fn a_gist_is_made_then_deleted() {
let mut s = with_gist();
act(&mut s, Action::NewGist);
assert_eq!(s.route(), Some(&Route::Gists(gists::NewGist)));
write(&mut s, "hi");
press(&mut s, "<C-s>");
let labels: Vec<&str> = overlay!(s, Ask)
.choices
.iter()
.map(|(l, _)| l.as_str())
.collect();
assert_eq!(labels, ["Create secret gist", "Create public gist"]);
let sent = press(&mut s, "l<Enter>");
let new = crate::api::gist::NewGist {
description: String::new(),
public: true,
files: vec![("gistfile1.txt".into(), "hi".into())],
};
let create = crate::gist::Create {
route: Route::Gists(gists::NewGist),
new,
};
assert_eq!(sent, [fixtures::start(create.clone())]);
let made = MadeGist {
id: "aa5a315d61ae9438b18d".into(),
owner: None,
};
fixtures::done(&mut s, create, Ok(made));
assert_eq!(
s.route(),
Some(&Route::Gists(gists::Gist {
owner: Some("octocat".into()),
id: "aa5a315d61ae9438b18d".into(),
}))
);
let mut s = with_gist();
press(&mut s, "D");
assert_eq!(overlay!(s, Ask).title, "Delete this gist?");
let sent = press(&mut s, "<Enter>");
let delete = crate::gist::Delete {
id: "6cad326836d38bd3a7ae".into(),
owner: "octocat".into(),
};
assert_eq!(sent, [fixtures::start(delete.clone())]);
fixtures::done(&mut s, delete, Ok(()));
assert_eq!(
s.route(),
Some(&Route::Gists(gists::Gists("octocat".into())))
);
}
#[test]
fn a_wiki_page_is_edited_never_over_a_newer_one() {
let mut s = with_wiki();
let cmds = press(&mut s, "e");
let route = Route::Wiki(wiki::WikiEdit {
repo: ghtui(),
page: Some("Home".into()),
});
assert_eq!(s.route(), Some(&route));
let key = DataKey::Wiki(ghtui(), Some("Home".into()));
assert!(crate::fixtures::asks(&cmds, &key), "{cmds:?}");
fetched(&mut s, key, Data::Wiki(Box::new(fixtures::wiki())));
assert!(composer(&mut s).text().starts_with("Welcome to the wiki."));
write(&mut s, "Welcome, all.");
let sent = press(&mut s, "<C-s>");
let wiki = fixtures::wiki();
let save = crate::wiki::Save {
repo: ghtui(),
title: "Home".into(),
file: wiki.file.clone(),
base: wiki.commit,
text: "Welcome, all.".into(),
login: Some("octocat".into()),
};
let save = crate::wiki::form::Save { route, save };
assert_eq!(sent, [fixtures::start(save.clone())]);
let changed = "Home changed on GitHub since you opened it";
fixtures::done(&mut s, save.clone(), Err(fixtures::failure(changed)));
let c = composer(&mut s);
assert!(!c.sent.sending);
assert_eq!(c.sent.error.as_deref(), Some(changed));
assert_eq!(c.text(), "Welcome, all.");
fixtures::done(&mut s, save, Ok("Home".into()));
let home = Route::Wiki(wiki::Wiki {
repo: ghtui(),
page: None,
});
assert_eq!(s.route(), Some(&home), "back on the page");
says(&s, "Saved Home");
assert!(s.following.is_some(), "until GitHub shows the text");
}