From 80a84a3c6e4f66c2fb7cadc0a928891f5115650c Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Fri, 22 Jul 2022 13:00:09 +0200 Subject: [PATCH] status_bar: Implement unit tests --- default-plugins/status-bar/src/first_line.rs | 405 ++++++++++++++++++ default-plugins/status-bar/src/main.rs | 48 +-- default-plugins/status-bar/src/second_line.rs | 241 +++++++++++ 3 files changed, 667 insertions(+), 27 deletions(-) diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index a5afec2215..ae8fc70e73 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -434,3 +434,408 @@ pub fn first_line(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart key_indicators(max_len, &default_keys, colored_elements, separator, help) } + +#[cfg(test)] +/// Unit tests. +/// +/// Note that we cheat a little here, because the number of things one may want to test is endless, +/// and creating a Mockup of [`ModeInfo`] by hand for all these testcases is nothing less than +/// torture. Hence, we test the most atomic units thoroughly ([`long_mode_shortcut`] and +/// [`short_mode_shortcut`]) and then test the public API ([`first_line`]) to ensure correct +/// operation. +mod tests { + use super::*; + + fn colored_elements() -> ColoredElements { + let palette = Palette::default(); + color_elements(palette, false) + } + + // Strip style information from `LinePart` and return a raw String instead + fn unstyle(line_part: LinePart) -> String { + let string = line_part.to_string(); + + let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + let string = re.replace_all(&string, "".to_string()); + + string.to_string() + } + + #[test] + fn long_mode_shortcut_selected_with_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Displayed like selected(alternate), but different styling + fn long_mode_shortcut_unselected_with_binding() { + let key = KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Treat exactly like "unselected" variant + fn long_mode_shortcut_unselected_alternate_with_binding() { + let key = KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // KeyShortcuts without binding are only displayed when "disabled" (for locked mode indications) + fn long_mode_shortcut_selected_without_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + // First tile doesn't print a starting separator + fn long_mode_shortcut_selected_with_binding_first_tile() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, true); + let ret = unstyle(ret); + + assert_eq!(ret, " <0> SESSION +".to_string()); + } + + #[test] + // Modifier is the superkey, mustn't appear in angled brackets + fn long_mode_shortcut_selected_with_ctrl_binding_shared_superkey() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", true, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Modifier must be in the angled brackets + fn long_mode_shortcut_selected_with_ctrl_binding_no_shared_superkey() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ SESSION +".to_string()); + } + + #[test] + // Must be displayed as usual, but it is styled to be greyed out which we don't test here + fn long_mode_shortcut_disabled_with_binding() { + let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + // Must be displayed but without keybinding + fn long_mode_shortcut_disabled_without_binding() { + let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, None); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <> SESSION +".to_string()); + } + + #[test] + // Test all at once + // Note that when "shared_super" is true, the tile **cannot** be the first on the line, so we + // ignore **first** here. + fn long_mode_shortcut_selected_with_ctrl_binding_and_shared_super_and_first_tile() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = long_mode_shortcut(&key, color, "+", true, true); + let ret = unstyle(ret); + + assert_eq!(ret, "+ <0> SESSION +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_ctrl_binding_no_shared_super() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ Ctrl+0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_ctrl_binding_shared_super() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", true, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_with_binding_first_tile() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, true); + let ret = unstyle(ret); + + assert_eq!(ret, " 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_with_binding() { + let key = KeyShortcut::new( + KeyMode::Unselected, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_alternate_with_binding() { + let key = KeyShortcut::new( + KeyMode::UnselectedAlternate, + KeyAction::Session, + Some(Key::Char('0')), + ); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_disabled_with_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "+ 0 +".to_string()); + } + + #[test] + fn short_mode_shortcut_selected_without_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_without_binding() { + let key = KeyShortcut::new(KeyMode::Unselected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + fn short_mode_shortcut_unselected_alternate_without_binding() { + let key = KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + fn short_mode_shortcut_disabled_without_binding() { + let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); + let color = colored_elements(); + + let ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = unstyle(ret); + + assert_eq!(ret, "".to_string()); + } + + #[test] + // Observe: Modes missing in between aren't displayed! + fn first_line_default_layout_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 500, ">"); + let ret = unstyle(ret); + + assert_eq!( + ret, + " Ctrl + >> PANE >> RESIZE >> MOVE >".to_string() + ); + } + + #[test] + fn first_line_default_layout_no_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Char('c'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 500, ">"); + let ret = unstyle(ret); + + assert_eq!( + ret, + " PANE >> RESIZE >> MOVE >".to_string() + ); + } + + #[test] + fn first_line_default_layout_unprintables() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), + (Key::Backspace, vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]), + (Key::Char('\t'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Left, vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 500, ">"); + let ret = unstyle(ret); + + assert_eq!( + ret, + " LOCK >> PANE >> TAB >> RESIZE >> <←> MOVE >" + .to_string() + ); + } + + #[test] + fn first_line_short_layout_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Tab)]), + (Key::Ctrl('d'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Ctrl('e'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 50, ">"); + let ret = unstyle(ret); + + assert_eq!(ret, " Ctrl + >> a >> b >> c >> d >> e >".to_string()); + } + + #[test] + fn first_line_short_simplified_ui_shared_super() { + #[rustfmt::skip] + let mode_info = ModeInfo{ + mode: InputMode::Normal, + keybinds : vec![ + (InputMode::Normal, vec![ + (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), + (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), + (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), + ]), + ], + ..ModeInfo::default() + }; + + let ret = first_line(&mode_info, 30, ""); + let ret = unstyle(ret); + + assert_eq!(ret, " Ctrl + a b c ".to_string()); + } +} diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index e2b2be919f..38728d254c 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -417,17 +417,28 @@ pub fn style_key_with_modifier(keyvec: &[Key], palette: &Palette) -> Vec Vec<(Key, Vec)> { + vec![ + (Key::Char('a'), vec![Action::Quit]), + (Key::Ctrl('b'), vec![Action::ScrollUp]), + (Key::Ctrl('d'), vec![Action::ScrollDown]), + ( + Key::Alt(CharOrArrow::Char('c')), + vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)], + ), + ( + Key::Char('1'), + vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], + ), + ] + } #[test] fn common_modifier_with_ctrl_keys() { @@ -523,22 +534,6 @@ mod tests { assert_eq!(ret, Vec::new()); } - fn big_keymap() -> Vec<(Key, Vec)> { - vec![ - (Key::Char('a'), vec![Action::Quit]), - (Key::Ctrl('b'), vec![Action::ScrollUp]), - (Key::Ctrl('d'), vec![Action::ScrollDown]), - ( - Key::Alt(CharOrArrow::Char('c')), - vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)], - ), - ( - Key::Char('1'), - vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], - ), - ] - } - #[test] fn action_key_long_pattern_match_exact() { let keymap = big_keymap(); @@ -737,16 +732,15 @@ mod tests { let ret = style_key_with_modifier(&keyvec, &palette); let ret = unstyle(&ANSIStrings(&ret)); - assert_eq!(ret, "".to_string()) + assert_eq!( + ret, + "".to_string() + ) } #[test] fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() { - let keyvec = vec![ - Key::Ctrl('\n'), - Key::Ctrl(' '), - Key::Ctrl('\t'), - ]; + let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')]; let palette = get_palette(); let ret = style_key_with_modifier(&keyvec, &palette); diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index fc01d30294..26afcd94f2 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -521,3 +521,244 @@ pub fn locked_floating_panes_are_visible(palette: &Palette) -> LinePart { len, } } + +#[cfg(test)] +/// Unit tests. +/// +/// Note that we cheat a little here, because the number of things one may want to test is endless, +/// and creating a Mockup of [`ModeInfo`] by hand for all these testcases is nothing less than +/// torture. Hence, we test the most atomic unit thoroughly ([`full_length_shortcut`] and then test +/// the public API ([`keybinds`]) to ensure correct operation. +mod tests { + use super::*; + + // Strip style information from `LinePart` and return a raw String instead + fn unstyle(line_part: LinePart) -> String { + let string = line_part.to_string(); + + let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); + let string = re.replace_all(&string, "".to_string()); + + string.to_string() + } + + fn get_palette() -> Palette { + Palette::default() + } + + #[test] + fn full_length_shortcut_with_key() { + let keyvec = vec![Key::Char('a')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_key_first_element() { + let keyvec = vec![Key::Char('a')]; + let palette = get_palette(); + + let ret = full_length_shortcut(true, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " Foobar"); + } + + #[test] + // When there is no binding, we print no shortcut either + fn full_length_shortcut_without_key() { + let keyvec = vec![]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, ""); + } + + #[test] + fn full_length_shortcut_with_key_unprintable_1() { + let keyvec = vec![Key::Char('\n')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_key_unprintable_2() { + let keyvec = vec![Key::Backspace]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_ctrl_key() { + let keyvec = vec![Key::Ctrl('a')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Ctrl + Foobar"); + } + + #[test] + fn full_length_shortcut_with_alt_key() { + let keyvec = vec![Key::Alt(CharOrArrow::Char('a'))]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Alt + Foobar"); + } + + #[test] + fn full_length_shortcut_with_homogenous_key_group() { + let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_heterogenous_key_group() { + let keyvec = vec![Key::Char('a'), Key::Ctrl('b'), Key::Char('\n')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Foobar"); + } + + #[test] + fn full_length_shortcut_with_key_group_shared_ctrl_modifier() { + let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')]; + let palette = get_palette(); + + let ret = full_length_shortcut(false, keyvec, "Foobar", palette); + let ret = unstyle(ret); + + assert_eq!(ret, " / Ctrl + Foobar"); + } + //pub fn keybinds(help: &ModeInfo, tip_name: &str, max_width: usize) -> LinePart { + + #[test] + // Note how it leaves out elements that don't exist! + fn keybinds_wide() { + let mode_info = ModeInfo { + mode: InputMode::Pane, + keybinds: vec![( + InputMode::Pane, + vec![ + (Key::Left, vec![Action::MoveFocus(actions::Direction::Left)]), + (Key::Down, vec![Action::MoveFocus(actions::Direction::Down)]), + (Key::Up, vec![Action::MoveFocus(actions::Direction::Up)]), + ( + Key::Right, + vec![Action::MoveFocus(actions::Direction::Right)], + ), + (Key::Char('n'), vec![Action::NewPane(None), TO_NORMAL]), + (Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]), + ( + Key::Char('f'), + vec![Action::ToggleFocusFullscreen, TO_NORMAL], + ), + ], + )], + ..ModeInfo::default() + }; + + let ret = keybinds(&mode_info, "quicknav", 500); + let ret = unstyle(ret); + + assert_eq!( + ret, + " <←↓↑→> Move focus / New / Close / Fullscreen" + ); + } + + #[test] + // Note how "Move focus" becomes "Move" + fn keybinds_tight_width() { + let mode_info = ModeInfo { + mode: InputMode::Pane, + keybinds: vec![( + InputMode::Pane, + vec![ + (Key::Left, vec![Action::MoveFocus(actions::Direction::Left)]), + (Key::Down, vec![Action::MoveFocus(actions::Direction::Down)]), + (Key::Up, vec![Action::MoveFocus(actions::Direction::Up)]), + ( + Key::Right, + vec![Action::MoveFocus(actions::Direction::Right)], + ), + (Key::Char('n'), vec![Action::NewPane(None), TO_NORMAL]), + (Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]), + ( + Key::Char('f'), + vec![Action::ToggleFocusFullscreen, TO_NORMAL], + ), + ], + )], + ..ModeInfo::default() + }; + + let ret = keybinds(&mode_info, "quicknav", 35); + let ret = unstyle(ret); + + assert_eq!(ret, " <←↓↑→> Move / New ... "); + } + + #[test] + fn keybinds_wide_weird_keys() { + let mode_info = ModeInfo { + mode: InputMode::Pane, + keybinds: vec![( + InputMode::Pane, + vec![ + ( + Key::Ctrl('a'), + vec![Action::MoveFocus(actions::Direction::Left)], + ), + ( + Key::Ctrl('\n'), + vec![Action::MoveFocus(actions::Direction::Down)], + ), + ( + Key::Ctrl('1'), + vec![Action::MoveFocus(actions::Direction::Up)], + ), + ( + Key::Ctrl(' '), + vec![Action::MoveFocus(actions::Direction::Right)], + ), + (Key::Backspace, vec![Action::NewPane(None), TO_NORMAL]), + (Key::Esc, vec![Action::CloseFocus, TO_NORMAL]), + (Key::End, vec![Action::ToggleFocusFullscreen, TO_NORMAL]), + ], + )], + ..ModeInfo::default() + }; + + let ret = keybinds(&mode_info, "quicknav", 500); + let ret = unstyle(ret); + + assert_eq!(ret, " Ctrl + Move focus / New / Close / Fullscreen"); + } +}