//! Integration tests for `help` and `help ` (H3). //! //! Covers: //! - Parse layer: `help` → `Help { topic: None }`; `help insert` //! → `Help { topic: Some("insert") }`. //! - App behaviour: the full `help` ends with the detail hint; //! `help ` renders that command's block (and every //! form sharing the entry word); `help types` renders the type //! reference; an unknown topic gets a friendly pointer back. use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use rdbms_playground::app::App; use rdbms_playground::dsl::{parse_command, AppCommand, Command}; use rdbms_playground::event::AppEvent; const fn key(code: KeyCode) -> AppEvent { AppEvent::Key(KeyEvent { code, modifiers: KeyModifiers::NONE, kind: KeyEventKind::Press, state: crossterm::event::KeyEventState::NONE, }) } fn type_str(app: &mut App, s: &str) { for c in s.chars() { app.update(key(KeyCode::Char(c))); } } /// Submit `input` to a fresh app and collect all output text. fn output_for(input: &str) -> Vec { let mut app = App::new(); type_str(&mut app, input); app.update(key(KeyCode::Enter)); app.output.iter().map(|l| l.text.clone()).collect() } // ================================================================= // Parse layer // ================================================================= #[test] fn bare_help_parses_with_no_topic() { assert_eq!( parse_command("help").expect("parses"), Command::App(AppCommand::Help { topic: None }), ); } #[test] fn help_with_topic_captures_the_word() { assert_eq!( parse_command("help insert").expect("parses"), Command::App(AppCommand::Help { topic: Some("insert".to_string()) }), ); } #[test] fn help_topic_is_a_single_word_multi_word_is_a_parse_error() { // Entry-word topics cover multi-word commands (`help create`), // so a second word is trailing junk, not a longer topic. assert!(parse_command("help foo bar").is_err()); } // ================================================================= // App behaviour // ================================================================= #[test] fn full_help_lists_commands_and_ends_with_the_detail_hint() { let out = output_for("help"); assert!( out.iter().any(|l| l == "Supported commands:"), "intro present: {out:?}", ); assert!( out.iter().any(|l| l.contains("help ")), "detail-hint footer present: {out:?}", ); } #[test] fn help_insert_renders_the_insert_block() { let out = output_for("help insert"); assert!( out.iter().any(|l| l.contains("insert into")), "insert help shown: {out:?}", ); // Focused: it must NOT dump the whole list — the intro header // belongs to the full `help` only. assert!( !out.iter().any(|l| l == "Supported commands:"), "focused help omits the full-list intro: {out:?}", ); } #[test] fn help_create_covers_every_form_sharing_the_entry_word() { // `create` is the entry word for both the DSL `create table` // and the advanced SQL `CREATE TABLE` — `help create` shows // both blocks. let out = output_for("help create"); let joined = out.join("\n"); assert!( joined.contains("create table"), "DSL create form shown: {out:?}", ); assert!( joined.to_lowercase().matches("create").count() >= 2, "more than one create form shown: {out:?}", ); } #[test] fn help_types_renders_the_type_reference() { let out = output_for("help types"); let joined = out.join("\n").to_lowercase(); // The type reference names the playground types. assert!( joined.contains("serial") || joined.contains("shortid"), "type reference shown: {out:?}", ); } #[test] fn help_unknown_topic_points_back_to_the_full_list() { let out = output_for("help wibble"); assert!( out.iter() .any(|l| l.contains("No help for") && l.contains("wibble")), "names the unknown topic: {out:?}", ); assert!( out.iter().any(|l| l.contains("Type `help`")), "points back at the full list: {out:?}", ); }