From 06940575afe283c870b559076bc8b41055db061d Mon Sep 17 00:00:00 2001 From: Sam Tay Date: Wed, 15 Jul 2020 00:43:55 -0700 Subject: Add some basic TUI tests --- Cargo.lock | 1 + Cargo.toml | 1 + src/tui/app.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/tui/views.rs | 1 - 4 files changed, 109 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f540818..c0ec5d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2052,6 +2052,7 @@ version = "0.4.2" dependencies = [ "clap", "criterion", + "crossbeam-channel", "crossterm", "cursive", "directories", diff --git a/Cargo.toml b/Cargo.toml index d60e8db..51e30e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ travis-ci = { repository = "samtay/so", branch = "master" } [dev-dependencies] criterion = "0.3" +crossbeam-channel = "0.4.2" [[bench]] name = "html_parsing" diff --git a/src/tui/app.rs b/src/tui/app.rs index bdd4665..70e3fc9 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -23,8 +23,13 @@ pub const NAME_HELP_VIEW: &str = "help_view"; pub fn run(qs: Vec>) -> Result<()> { let mut siv = cursive::default(); - siv.load_theme_file(Config::theme_file_path()?).unwrap(); // TODO dont unwrap + siv.load_theme_file(Config::theme_file_path()?).unwrap(); + mk_tui(&mut siv, qs); + siv.run(); + Ok(()) +} +fn mk_tui(mut siv: &mut Cursive, qs: Vec>) { let question_map: HashMap> = qs.clone().into_iter().map(|q| (q.id, q)).collect(); let question_map = Arc::new(question_map); @@ -71,7 +76,7 @@ pub fn run(qs: Vec>) -> Result<()> { if let Some(pos) = s.screen_mut().find_layer_from_name(NAME_HELP_VIEW) { s.screen_mut().remove_layer(pos); } else { - s.add_layer(help()); + s.add_layer(help().add_vim_bindings()); } }); // Reload theme @@ -79,8 +84,6 @@ pub fn run(qs: Vec>) -> Result<()> { s.load_theme_file(Config::theme_file_path().unwrap()) .unwrap() }); - siv.run(); - Ok(()) } fn question_selected_callback( @@ -169,5 +172,103 @@ pub fn help() -> Dialog { .title("Help") } -// TODO see cursive/examples/src/bin/select_test.rs for how to test the interface! -// maybe see if we can conditionally run when --nocapture is passed? +#[cfg(test)] +pub mod tests { + + use cursive::backends::puppet::observed::ObservedScreen; + use cursive::event::Event; + use cursive::*; + use std::cell::RefCell; + + use super::*; + + pub struct BasicTest { + siv: Cursive, + screen_stream: crossbeam_channel::Receiver, + input: crossbeam_channel::Sender>, + last_screen: RefCell>, + } + impl BasicTest { + pub fn new() -> Self { + let size = Vec2::new(80, 20); + let backend = backends::puppet::Backend::init(Some(size)); + let sink = backend.stream(); + let input = backend.input(); + let mut siv = Cursive::new(|| backend); + + // TODO stub out some q/a + mk_tui( + &mut siv, + vec![Question { + id: 1, + score: 64, + answers: vec![], + title: String::from("Is a hamburger a sanwich?"), + body: markdown::parse("For **real** though"), + }], + ); + + input.send(Some(Event::Refresh)).unwrap(); + siv.step(); + + BasicTest { + siv, + screen_stream: sink, + input, + last_screen: RefCell::new(None), + } + } + + pub fn last_screen(&self) -> Option { + while let Ok(screen) = self.screen_stream.try_recv() { + self.last_screen.replace(Some(screen)); + } + + self.last_screen.borrow().clone() + } + + /// Run `cargo test -- --nocapture` to see debug screens + pub fn dump_debug(&self) { + self.last_screen().as_ref().map(|s| s.print_stdout()); + } + + pub fn hit_keystroke(&mut self, key: Event) { + self.input.send(Some(key)).unwrap(); + self.siv.step(); + } + } + + #[test] + fn test_basic() { + let s = BasicTest::new(); + s.dump_debug(); + + // Can see question + let screen = s.last_screen().unwrap(); + assert_eq!(screen.find_occurences("Is a hamburger a sanwich").len(), 1); + assert_eq!(screen.find_occurences("For real though").len(), 1); + } + + #[test] + fn test_help() { + let mut s = BasicTest::new(); + + // Get help + s.hit_keystroke(Event::Char('?')); + s.dump_debug(); + let screen = s.last_screen().unwrap(); + assert_eq!(screen.find_occurences("Panes").len(), 1); + + // Scroll to bottom + s.hit_keystroke(Event::Char('G')); + s.dump_debug(); + let screen = s.last_screen().unwrap(); + assert_eq!(screen.find_occurences("Toggle this help menu").len(), 1); + + // Close help + s.hit_keystroke(Event::Char('?')); + s.dump_debug(); + let screen = s.last_screen().unwrap(); + assert_eq!(screen.find_occurences("Toggle this help menu").len(), 0); + } +} diff --git a/src/tui/views.rs b/src/tui/views.rs index 7d1593d..3d1dbc3 100644 --- a/src/tui/views.rs +++ b/src/tui/views.rs @@ -19,7 +19,6 @@ pub const NAME_QUESTION_VIEW: &str = "question_view"; pub const NAME_ANSWER_VIEW: &str = "answer_view"; pub const NAME_FULL_LAYOUT: &str = "full_layout"; -// TODO this seems pointless; probably should be removed pub enum Name { QuestionList, AnswerList, -- cgit v1.2.3