Merge branch 'develop' into fix-key-event-handling

This commit is contained in:
Alex Clarke
2025-04-07 11:58:06 -06:00
committed by GitHub
152 changed files with 3309 additions and 666 deletions
Generated
+10
View File
@@ -1391,6 +1391,7 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"urlencoding", "urlencoding",
"validate_theme_derive",
"veil", "veil",
] ]
@@ -2780,6 +2781,15 @@ dependencies = [
"getrandom 0.3.1", "getrandom 0.3.1",
] ]
[[package]]
name = "validate_theme_derive"
version = "0.1.0"
dependencies = [
"log",
"quote",
"syn 2.0.99",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
+2 -1
View File
@@ -14,7 +14,7 @@ rust-version = "1.85.0"
exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"] exclude = [".github", "CONTRIBUTING.md", "*.log", "tags"]
[workspace] [workspace]
members = ["proc_macros/enum_display_style_derive"] members = ["proc_macros/enum_display_style_derive", "proc_macros/validate_theme_derive"]
[dependencies] [dependencies]
anyhow = "1.0.68" anyhow = "1.0.68"
@@ -63,6 +63,7 @@ deunicode = "1.6.0"
paste = "1.0.15" paste = "1.0.15"
openssl = { version = "0.10.70", features = ["vendored"] } openssl = { version = "0.10.70", features = ["vendored"] }
veil = "0.2.0" veil = "0.2.0"
validate_theme_derive = { path = "proc_macros/validate_theme_derive" }
enum_display_style_derive = { path = "proc_macros/enum_display_style_derive" } enum_display_style_derive = { path = "proc_macros/enum_display_style_derive" }
[dev-dependencies] [dev-dependencies]
+15 -1
View File
@@ -206,6 +206,16 @@ Key:
- [ ] Support for Tautulli - [ ] Support for Tautulli
### Themes
Managarr ships with a few themes out of the box. Here's a few examples:
![default](themes/default/manual_episode_search.png)
![dracula](themes/dracula/manual_episode_search.png)
![watermelon-dark](themes/watermelon-dark/manual_episode_search.png)
You can also create your own custom themes as well. To learn more about what themes are built-in to Managarr and how
to create your own custom themes, check out the [Themes README](themes/README.md).
### The Managarr CLI ### The Managarr CLI
Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs. Managarr can be used in one of two ways: As a TUI, or as a CLI for managing your Servarrs.
@@ -218,7 +228,7 @@ To see all available commands, simply run `managarr --help`:
```shell ```shell
$ managarr --help $ managarr --help
managarr 0.5.0 managarr 0.5.1
Alex Clarke <alex.j.tusa@gmail.com> Alex Clarke <alex.j.tusa@gmail.com>
A TUI and CLI to manage your Servarrs A TUI and CLI to manage your Servarrs
@@ -235,6 +245,8 @@ Commands:
Options: Options:
--disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=] --disable-spinner Disable the spinner (can sometimes make parsing output challenging) [env: MANAGARR_DISABLE_SPINNER=]
--config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=] --config-file <CONFIG_FILE> The Managarr configuration file to use [env: MANAGARR_CONFIG_FILE=]
--themes-file <THEMES_FILE> The Managarr themes file to use [env: MANAGARR_THEMES_FILE=]
--theme <THEME> The name of the Managarr theme to use [env: MANAGARR_THEME=]
--servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use. --servarr-name <SERVARR_NAME> For multi-instance configurations, you need to specify the name of the instance configuration that you want to use.
This is useful when you have multiple instances of the same Servarr defined in your config file. This is useful when you have multiple instances of the same Servarr defined in your config file.
By default, if left empty, the first configured Servarr instance listed in the config file will be used. By default, if left empty, the first configured Servarr instance listed in the config file will be used.
@@ -315,6 +327,7 @@ managarr --config-file /path/to/config.yml
### Example Configuration: ### Example Configuration:
```yaml ```yaml
theme: default
radarr: radarr:
- host: 192.168.0.78 - host: 192.168.0.78
port: 7878 port: 7878
@@ -357,6 +370,7 @@ tautulli:
### Example Multi-Instance Configuration: ### Example Multi-Instance Configuration:
```yaml ```yaml
theme: default
radarr: radarr:
- host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1' - host: 192.168.0.78 # No name specified, so this instance's name will default to 'Radarr 1'
port: 7878 port: 7878
@@ -4,7 +4,8 @@ use crate::macro_models::DisplayStyleArgs;
use darling::FromVariant; use darling::FromVariant;
use quote::quote; use quote::quote;
use syn::{Data, DeriveInput, parse_macro_input}; use syn::{Data, DeriveInput, parse_macro_input};
/// Derive macro for the EnumDisplayStyle trait.
/// Derive macro for generating a `to_display_str` method for an enum.
/// ///
/// # Example /// # Example
/// ///
@@ -21,7 +22,6 @@ use syn::{Data, DeriveInput, parse_macro_input};
/// ///
/// assert_eq!(Weekend::Saturday.to_display_str(), "Saturday"); /// assert_eq!(Weekend::Saturday.to_display_str(), "Saturday");
/// assert_eq!(Weekend::Sunday.to_display_str(), "Sunday"); /// assert_eq!(Weekend::Sunday.to_display_str(), "Sunday");
///
/// ``` /// ```
/// ///
/// Using custom values for the display style: /// Using custom values for the display style:
@@ -0,0 +1,14 @@
[package]
name = "validate_theme_derive"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
quote = "1.0.39"
syn = "2.0.99"
[dev-dependencies]
log = "0.4.17"
@@ -0,0 +1,106 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, parse_macro_input};
/// Derive macro for generating a `validate` method for a Theme struct.
/// The `validate` method ensures that all values with the `validate` attribute are not `None`.
/// Otherwise, an error message it output to both the log file and stdout and the program exits.
///
/// # Example
///
/// Valid themes pass through the program transitively without any messages being output.
///
/// ```
/// use validate_theme_derive::ValidateTheme;
///
/// #[derive(ValidateTheme, Default)]
/// struct Theme {
/// pub name: String,
/// #[validate]
/// pub good: Option<Style>,
/// #[validate]
/// pub bad: Option<Style>,
/// pub ugly: Option<Style>,
/// }
///
/// struct Style {
/// color: String,
/// }
///
/// let theme = Theme {
/// good: Some(Style { color: "Green".to_owned() }),
/// bad: Some(Style { color: "Red".to_owned() }),
/// ..Theme::default()
/// };
///
/// // Since only `good` and `bad` have the `validate` attribute, the `validate` method will only check those fields.
/// theme.validate();
/// // Since both `good` and `bad` have values, the program will not exit and no message is output.
/// ```
///
/// Invalid themes will output an error message to both the log file and stdout and the program will exit.
///
/// ```should_panic
/// use validate_theme_derive::ValidateTheme;
///
/// #[derive(ValidateTheme, Default)]
/// struct Theme {
/// pub name: String,
/// #[validate]
/// pub good: Option<Style>,
/// #[validate]
/// pub bad: Option<Style>,
/// pub ugly: Option<Style>,
/// }
///
/// struct Style {
/// color: String,
/// }
///
/// let theme = Theme {
/// bad: Some(Style { color: "Red".to_owned() }),
/// ..Theme::default()
/// };
///
/// // Since `good` has the `validate` attribute and since `good` is `None`, the `validate` method will output an error message and exit the program.
/// theme.validate();
/// ```
#[proc_macro_derive(ValidateTheme, attributes(validate))]
pub fn derive_validate_theme(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let mut validation_checks = Vec::new();
if let Data::Struct(data_struct) = &input.data {
if let Fields::Named(fields) = &data_struct.fields {
for field in &fields.named {
let field_name = &field.ident;
let has_validate_attr = field
.attrs
.iter()
.any(|attr| attr.path().is_ident("validate"));
if has_validate_attr {
validation_checks.push(quote! {
if self.#field_name.is_none() {
log::error!("{} is missing a color value.", stringify!(#field_name));
eprintln!("{} is missing a color value.", stringify!(#field_name));
std::process::exit(1);
}
})
}
}
}
}
quote! {
impl #struct_name {
pub fn validate(&self) {
#(#validation_checks)*
}
}
}
.into()
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 220 KiB

+3 -2
View File
@@ -31,6 +31,7 @@ mod tests {
}; };
let sonarr_config_2 = ServarrConfig::default(); let sonarr_config_2 = ServarrConfig::default();
let config = AppConfig { let config = AppConfig {
theme: None,
radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]), radarr: Some(vec![radarr_config_1.clone(), radarr_config_2.clone()]),
sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]), sonarr: Some(vec![sonarr_config_1.clone(), sonarr_config_2.clone()]),
}; };
@@ -97,7 +98,7 @@ mod tests {
assert!(!app.is_loading); assert!(!app.is_loading);
assert!(!app.is_routing); assert!(!app.is_routing);
assert!(!app.should_refresh); assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app.cli_mode); assert!(!app.cli_mode);
} }
@@ -117,7 +118,7 @@ mod tests {
assert!(!app.is_loading); assert!(!app.is_loading);
assert!(!app.is_routing); assert!(!app.is_routing);
assert!(!app.should_refresh); assert!(!app.should_refresh);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app.cli_mode); assert!(!app.cli_mode);
} }
+61 -1
View File
@@ -44,128 +44,188 @@ generate_keybindings! {
#[derive(Clone, Copy, Eq, PartialEq, Debug)] #[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub struct KeyBinding { pub struct KeyBinding {
pub key: Key, pub key: Key,
pub alt: Option<Key>,
pub desc: &'static str, pub desc: &'static str,
} }
pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings { pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
add: KeyBinding { add: KeyBinding {
key: Key::Char('a'), key: Key::Char('a'),
alt: None,
desc: "add", desc: "add",
}, },
up: KeyBinding { up: KeyBinding {
key: Key::Up, key: Key::Up,
alt: Some(Key::Char('k')),
desc: "up", desc: "up",
}, },
down: KeyBinding { down: KeyBinding {
key: Key::Down, key: Key::Down,
alt: Some(Key::Char('j')),
desc: "down", desc: "down",
}, },
left: KeyBinding { left: KeyBinding {
key: Key::Left, key: Key::Left,
alt: Some(Key::Char('h')),
desc: "left", desc: "left",
}, },
right: KeyBinding { right: KeyBinding {
key: Key::Right, key: Key::Right,
alt: Some(Key::Char('l')),
desc: "right", desc: "right",
}, },
backspace: KeyBinding { backspace: KeyBinding {
key: Key::Backspace, key: Key::Backspace,
alt: Some(Key::Ctrl('h')),
desc: "backspace", desc: "backspace",
}, },
next_servarr: KeyBinding { next_servarr: KeyBinding {
key: Key::Tab, key: Key::Tab,
alt: None,
desc: "next servarr", desc: "next servarr",
}, },
previous_servarr: KeyBinding { previous_servarr: KeyBinding {
key: Key::BackTab, key: Key::BackTab,
alt: None,
desc: "previous servarr", desc: "previous servarr",
}, },
clear: KeyBinding { clear: KeyBinding {
key: Key::Char('c'), key: Key::Char('c'),
alt: None,
desc: "clear", desc: "clear",
}, },
auto_search: KeyBinding { auto_search: KeyBinding {
key: Key::Char('S'), key: Key::Char('S'),
alt: None,
desc: "auto search", desc: "auto search",
}, },
search: KeyBinding { search: KeyBinding {
key: Key::Char('s'), key: Key::Char('s'),
alt: None,
desc: "search", desc: "search",
}, },
settings: KeyBinding { settings: KeyBinding {
key: Key::Char('S'), key: Key::Char('S'),
alt: None,
desc: "settings", desc: "settings",
}, },
filter: KeyBinding { filter: KeyBinding {
key: Key::Char('f'), key: Key::Char('f'),
alt: None,
desc: "filter", desc: "filter",
}, },
sort: KeyBinding { sort: KeyBinding {
key: Key::Char('o'), key: Key::Char('o'),
alt: None,
desc: "sort", desc: "sort",
}, },
edit: KeyBinding { edit: KeyBinding {
key: Key::Char('e'), key: Key::Char('e'),
alt: None,
desc: "edit", desc: "edit",
}, },
events: KeyBinding { events: KeyBinding {
key: Key::Char('e'), key: Key::Char('e'),
alt: None,
desc: "events", desc: "events",
}, },
logs: KeyBinding { logs: KeyBinding {
key: Key::Char('l'), key: Key::Char('L'),
alt: None,
desc: "logs", desc: "logs",
}, },
tasks: KeyBinding { tasks: KeyBinding {
key: Key::Char('t'), key: Key::Char('t'),
alt: None,
desc: "tasks", desc: "tasks",
}, },
test: KeyBinding { test: KeyBinding {
key: Key::Char('t'), key: Key::Char('t'),
alt: None,
desc: "test", desc: "test",
}, },
test_all: KeyBinding { test_all: KeyBinding {
key: Key::Char('T'), key: Key::Char('T'),
alt: None,
desc: "test all", desc: "test all",
}, },
toggle_monitoring: KeyBinding { toggle_monitoring: KeyBinding {
key: Key::Char('m'), key: Key::Char('m'),
alt: None,
desc: "toggle monitoring", desc: "toggle monitoring",
}, },
refresh: KeyBinding { refresh: KeyBinding {
key: Key::Ctrl('r'), key: Key::Ctrl('r'),
alt: None,
desc: "refresh", desc: "refresh",
}, },
update: KeyBinding { update: KeyBinding {
key: Key::Char('u'), key: Key::Char('u'),
alt: None,
desc: "update", desc: "update",
}, },
home: KeyBinding { home: KeyBinding {
key: Key::Home, key: Key::Home,
alt: None,
desc: "home", desc: "home",
}, },
end: KeyBinding { end: KeyBinding {
key: Key::End, key: Key::End,
alt: None,
desc: "end", desc: "end",
}, },
delete: KeyBinding { delete: KeyBinding {
key: Key::Delete, key: Key::Delete,
alt: None,
desc: "delete", desc: "delete",
}, },
submit: KeyBinding { submit: KeyBinding {
key: Key::Enter, key: Key::Enter,
alt: None,
desc: "submit", desc: "submit",
}, },
confirm: KeyBinding { confirm: KeyBinding {
key: Key::Ctrl('s'), key: Key::Ctrl('s'),
alt: None,
desc: "submit", desc: "submit",
}, },
quit: KeyBinding { quit: KeyBinding {
key: Key::Char('q'), key: Key::Char('q'),
alt: None,
desc: "quit", desc: "quit",
}, },
esc: KeyBinding { esc: KeyBinding {
key: Key::Esc, key: Key::Esc,
alt: None,
desc: "close", desc: "close",
}, },
}; };
#[macro_export]
macro_rules! matches_key {
($binding:ident, $key:expr) => {
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|| ($crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.is_some()
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.unwrap()
== $key)
};
($binding:ident, $key:expr, $ignore_special_keys:expr) => {
$crate::app::key_binding::DEFAULT_KEYBINDINGS.$binding.key == $key
|| !$ignore_special_keys
&& ($crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.is_some()
&& $crate::app::key_binding::DEFAULT_KEYBINDINGS
.$binding
.alt
.unwrap()
== $key)
};
}
+71 -30
View File
@@ -5,44 +5,85 @@ mod test {
use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS}; use crate::app::key_binding::{KeyBinding, DEFAULT_KEYBINDINGS};
use crate::event::Key; use crate::event::Key;
use crate::matches_key;
#[rstest] #[rstest]
#[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), "add")] #[case(DEFAULT_KEYBINDINGS.add, Key::Char('a'), None, "add")]
#[case(DEFAULT_KEYBINDINGS.up, Key::Up, "up")] #[case(DEFAULT_KEYBINDINGS.up, Key::Up, Some(Key::Char('k')), "up")]
#[case(DEFAULT_KEYBINDINGS.down, Key::Down, "down")] #[case(DEFAULT_KEYBINDINGS.down, Key::Down, Some(Key::Char('j')), "down")]
#[case(DEFAULT_KEYBINDINGS.left, Key::Left, "left")] #[case(DEFAULT_KEYBINDINGS.left, Key::Left, Some(Key::Char('h')), "left")]
#[case(DEFAULT_KEYBINDINGS.right, Key::Right, "right")] #[case(DEFAULT_KEYBINDINGS.right, Key::Right, Some(Key::Char('l')), "right")]
#[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, "backspace")] #[case(DEFAULT_KEYBINDINGS.backspace, Key::Backspace, Some(Key::Ctrl('h')), "backspace")]
#[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, "next servarr")] #[case(DEFAULT_KEYBINDINGS.next_servarr, Key::Tab, None, "next servarr")]
#[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, "previous servarr")] #[case(DEFAULT_KEYBINDINGS.previous_servarr, Key::BackTab, None, "previous servarr")]
#[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), "clear")] #[case(DEFAULT_KEYBINDINGS.clear, Key::Char('c'), None, "clear")]
#[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), "auto search")] #[case(DEFAULT_KEYBINDINGS.auto_search, Key::Char('S'), None, "auto search")]
#[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), "search")] #[case(DEFAULT_KEYBINDINGS.search, Key::Char('s'), None, "search")]
#[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), "settings")] #[case(DEFAULT_KEYBINDINGS.settings, Key::Char('S'), None, "settings")]
#[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), "filter")] #[case(DEFAULT_KEYBINDINGS.filter, Key::Char('f'), None, "filter")]
#[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), "sort")] #[case(DEFAULT_KEYBINDINGS.sort, Key::Char('o'), None, "sort")]
#[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), "edit")] #[case(DEFAULT_KEYBINDINGS.edit, Key::Char('e'), None, "edit")]
#[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), "events")] #[case(DEFAULT_KEYBINDINGS.events, Key::Char('e'), None, "events")]
#[case(DEFAULT_KEYBINDINGS.logs, Key::Char('l'), "logs")] #[case(DEFAULT_KEYBINDINGS.logs, Key::Char('L'), None, "logs")]
#[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), "tasks")] #[case(DEFAULT_KEYBINDINGS.tasks, Key::Char('t'), None, "tasks")]
#[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), "test")] #[case(DEFAULT_KEYBINDINGS.test, Key::Char('t'), None, "test")]
#[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), "test all")] #[case(DEFAULT_KEYBINDINGS.test_all, Key::Char('T'), None, "test all")]
#[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), "toggle monitoring")] #[case(DEFAULT_KEYBINDINGS.toggle_monitoring, Key::Char('m'), None, "toggle monitoring")]
#[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), "refresh")] #[case(DEFAULT_KEYBINDINGS.refresh, Key::Ctrl('r'), None, "refresh")]
#[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), "update")] #[case(DEFAULT_KEYBINDINGS.update, Key::Char('u'), None, "update")]
#[case(DEFAULT_KEYBINDINGS.home, Key::Home, "home")] #[case(DEFAULT_KEYBINDINGS.home, Key::Home, None, "home")]
#[case(DEFAULT_KEYBINDINGS.end, Key::End, "end")] #[case(DEFAULT_KEYBINDINGS.end, Key::End, None, "end")]
#[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, "delete")] #[case(DEFAULT_KEYBINDINGS.delete, Key::Delete, None, "delete")]
#[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, "submit")] #[case(DEFAULT_KEYBINDINGS.submit, Key::Enter, None, "submit")]
#[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), "submit")] #[case(DEFAULT_KEYBINDINGS.confirm, Key::Ctrl('s'), None, "submit")]
#[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), "quit")] #[case(DEFAULT_KEYBINDINGS.quit, Key::Char('q'), None, "quit")]
#[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, "close")] #[case(DEFAULT_KEYBINDINGS.esc, Key::Esc, None, "close")]
fn test_default_key_bindings_and_descriptions( fn test_default_key_bindings_and_descriptions(
#[case] key_binding: KeyBinding, #[case] key_binding: KeyBinding,
#[case] expected_key: Key, #[case] expected_key: Key,
#[case] expected_alt_key: Option<Key>,
#[case] expected_desc: &str, #[case] expected_desc: &str,
) { ) {
assert_eq!(key_binding.key, expected_key); assert_eq!(key_binding.key, expected_key);
assert_eq!(key_binding.alt, expected_alt_key);
assert_str_eq!(key_binding.desc, expected_desc); assert_str_eq!(key_binding.desc, expected_desc);
} }
#[test]
fn test_matches_key_macro() {
let key = Key::Char('t');
assert!(matches_key!(test, key));
assert!(!matches_key!(test, Key::Char('T')));
}
#[test]
fn test_matches_key_macro_with_alt_keybinding() {
let alt_key = Key::Char('k');
let key = Key::Up;
assert!(matches_key!(up, key));
assert!(matches_key!(up, alt_key));
assert!(!matches_key!(up, Key::Char('t')));
}
#[test]
fn test_matches_key_macro_with_alt_keybinding_uses_alt_key_when_ignore_special_keys_is_false() {
let alt_key = Key::Char('k');
let key = Key::Up;
assert!(matches_key!(up, key, false));
assert!(matches_key!(up, alt_key, false));
assert!(!matches_key!(up, Key::Char('t'), false));
}
#[test]
fn test_matches_key_macro_with_alt_keybinding_ignores_alt_key_when_ignore_special_keys_is_true() {
let alt_key = Key::Char('k');
let key = Key::Up;
assert!(matches_key!(up, key, true));
assert!(!matches_key!(up, alt_key, true));
assert!(!matches_key!(up, Key::Char('t'), true));
}
} }
+3 -2
View File
@@ -39,7 +39,7 @@ pub struct App<'a> {
pub is_routing: bool, pub is_routing: bool,
pub is_loading: bool, pub is_loading: bool,
pub should_refresh: bool, pub should_refresh: bool,
pub should_ignore_quit_key: bool, pub ignore_special_keys_for_textbox_input: bool,
pub cli_mode: bool, pub cli_mode: bool,
pub data: Data<'a>, pub data: Data<'a>,
} }
@@ -224,7 +224,7 @@ impl Default for App<'_> {
is_loading: false, is_loading: false,
is_routing: false, is_routing: false,
should_refresh: false, should_refresh: false,
should_ignore_quit_key: false, ignore_special_keys_for_textbox_input: false,
cli_mode: false, cli_mode: false,
data: Data::default(), data: Data::default(),
} }
@@ -270,6 +270,7 @@ pub struct Data<'a> {
#[derive(Debug, Deserialize, Serialize, Default, Clone)] #[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct AppConfig { pub struct AppConfig {
pub theme: Option<String>,
pub radarr: Option<Vec<ServarrConfig>>, pub radarr: Option<Vec<ServarrConfig>>,
pub sonarr: Option<Vec<ServarrConfig>>, pub sonarr: Option<Vec<ServarrConfig>>,
} }
+18 -17
View File
@@ -1,9 +1,9 @@
use radarr_handlers::RadarrHandler; use radarr_handlers::RadarrHandler;
use sonarr_handlers::SonarrHandler; use sonarr_handlers::SonarrHandler;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::matches_key;
use crate::models::{HorizontallyScrollableText, Route}; use crate::models::{HorizontallyScrollableText, Route};
mod radarr_handlers; mod radarr_handlers;
@@ -22,40 +22,42 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
fn handle_key_event(&mut self) { fn handle_key_event(&mut self) {
let key = self.get_key(); let key = self.get_key();
match key { match key {
_ if key == DEFAULT_KEYBINDINGS.up.key => { _ if matches_key!(up, key, self.ignore_special_keys()) => {
if self.is_ready() { if self.is_ready() {
self.handle_scroll_up(); self.handle_scroll_up();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.down.key => { _ if matches_key!(down, key, self.ignore_special_keys()) => {
if self.is_ready() { if self.is_ready() {
self.handle_scroll_down(); self.handle_scroll_down();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.home.key => { _ if matches_key!(home, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_home(); self.handle_home();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.end.key => { _ if matches_key!(end, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_end(); self.handle_end();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.delete.key => { _ if matches_key!(delete, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_delete(); self.handle_delete();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(left, key, self.ignore_special_keys())
|| matches_key!(right, key, self.ignore_special_keys()) =>
{
self.handle_left_right_action() self.handle_left_right_action()
} }
_ if key == DEFAULT_KEYBINDINGS.submit.key => { _ if matches_key!(submit, key) => {
if self.is_ready() { if self.is_ready() {
self.handle_submit(); self.handle_submit();
} }
} }
_ if key == DEFAULT_KEYBINDINGS.esc.key => self.handle_esc(), _ if matches_key!(esc, key) => self.handle_esc(),
_ => { _ => {
if self.is_ready() { if self.is_ready() {
self.handle_char_key_event(); self.handle_char_key_event();
@@ -71,6 +73,7 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
fn accepts(active_block: T) -> bool; fn accepts(active_block: T) -> bool;
fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self; fn new(key: Key, app: &'a mut App<'b>, active_block: T, context: Option<T>) -> Self;
fn get_key(&self) -> Key; fn get_key(&self) -> Key;
fn ignore_special_keys(&self) -> bool;
fn is_ready(&self) -> bool; fn is_ready(&self) -> bool;
fn handle_scroll_up(&mut self); fn handle_scroll_up(&mut self);
fn handle_scroll_down(&mut self); fn handle_scroll_down(&mut self);
@@ -84,12 +87,12 @@ pub trait KeyEventHandler<'a, 'b, T: Into<Route> + Copy> {
} }
pub fn handle_events(key: Key, app: &mut App<'_>) { pub fn handle_events(key: Key, app: &mut App<'_>) {
if key == DEFAULT_KEYBINDINGS.next_servarr.key { if matches_key!(next_servarr, key) {
app.reset(); app.reset();
app.server_tabs.next(); app.server_tabs.next();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
app.cancellation_token.cancel(); app.cancellation_token.cancel();
} else if key == DEFAULT_KEYBINDINGS.previous_servarr.key { } else if matches_key!(previous_servarr, key) {
app.reset(); app.reset();
app.server_tabs.previous(); app.server_tabs.previous();
app.pop_and_push_navigation_stack(app.server_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.server_tabs.get_active_route());
@@ -115,8 +118,7 @@ fn handle_clear_errors(app: &mut App<'_>) {
fn handle_prompt_toggle(app: &mut App<'_>, key: Key) { fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
match key { match key {
_ if key == DEFAULT_KEYBINDINGS.left.key || key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(left, key) || matches_key!(right, key) => match app.get_current_route() {
match app.get_current_route() {
Route::Radarr(_, _) => { Route::Radarr(_, _) => {
app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm app.data.radarr_data.prompt_confirm = !app.data.radarr_data.prompt_confirm
} }
@@ -124,8 +126,7 @@ fn handle_prompt_toggle(app: &mut App<'_>, key: Key) {
app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm app.data.sonarr_data.prompt_confirm = !app.data.sonarr_data.prompt_confirm
} }
_ => (), _ => (),
} },
}
_ => (), _ => (),
} }
} }
@@ -149,7 +150,7 @@ macro_rules! handle_text_box_left_right_keys {
macro_rules! handle_text_box_keys { macro_rules! handle_text_box_keys {
($self:expr, $key:expr, $input:expr) => { ($self:expr, $key:expr, $input:expr) => {
match $self.key { match $self.key {
_ if $key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.backspace.key => { _ if $crate::matches_key!(backspace, $key) => {
$input.pop(); $input.pop();
} }
Key::Char(character) => { Key::Char(character) => {
@@ -165,7 +166,7 @@ macro_rules! handle_prompt_left_right_keys {
($self:expr, $confirm_prompt:expr, $data:ident) => { ($self:expr, $confirm_prompt:expr, $data:ident) => {
if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt { if $self.app.data.$data.selected_block.get_active_block() == $confirm_prompt {
handle_prompt_toggle($self.app, $self.key); handle_prompt_toggle($self.app, $self.key);
} else if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key { } else if $crate::matches_key!(left, $self.key) {
$self.app.data.$data.selected_block.left(); $self.app.data.$data.selected_block.left();
} else { } else {
$self.app.data.$data.selected_block.right(); $self.app.data.$data.selected_block.right();
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime; use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -541,6 +542,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_blocklist_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = BlocklistHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_blocklist_item_id() { fn test_extract_blocklist_item_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
@@ -9,6 +7,7 @@ use crate::models::radarr_models::BlocklistItem;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, BLOCKLIST_BLOCKS};
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "blocklist_handler_tests.rs"] #[path = "blocklist_handler_tests.rs"]
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
BLOCKLIST_BLOCKS.contains(&active_block) BLOCKLIST_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Blocklist => match self.key { ActiveRadarrBlock::Blocklist => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.clear.key => { _ if matches_key!(clear, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::BlocklistClearAllItemsPrompt.into());
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteBlocklistItemPrompt => { ActiveRadarrBlock::DeleteBlocklistItemPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem( self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteBlocklistItem(
self.extract_blocklist_item_id(), self.extract_blocklist_item_id(),
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for BlocklistHandler<'a,
} }
} }
ActiveRadarrBlock::BlocklistClearAllItemsPrompt => { ActiveRadarrBlock::BlocklistClearAllItemsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::ClearBlocklist);
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::radarr_models::CollectionMovie; use crate::models::radarr_models::CollectionMovie;
@@ -11,6 +9,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
}; };
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "collection_details_handler_tests.rs"] #[path = "collection_details_handler_tests.rs"]
@@ -46,6 +45,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
COLLECTION_DETAILS_BLOCKS.contains(&active_block) COLLECTION_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -130,7 +133,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHan
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails if self.active_radarr_block == ActiveRadarrBlock::CollectionDetails
&& self.key == DEFAULT_KEYBINDINGS.edit.key && matches_key!(edit, self.key)
{ {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -278,6 +279,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_collection_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = CollectionDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_collection_details_handler_not_ready_when_loading() { fn test_collection_details_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -589,6 +589,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_collections_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = CollectionsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_collections_handler_not_ready_when_loading() { fn test_collections_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,7 @@ use crate::models::servarr_data::radarr::modals::EditCollectionModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_COLLECTION_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "edit_collection_handler_tests.rs"] #[path = "edit_collection_handler_tests.rs"]
@@ -69,6 +68,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
EDIT_COLLECTION_BLOCKS.contains(&active_block) EDIT_COLLECTION_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -262,7 +265,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
) )
.into(), .into(),
); );
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveRadarrBlock::EditCollectionToggleMonitored => { ActiveRadarrBlock::EditCollectionToggleMonitored => {
self self
@@ -311,7 +314,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
| ActiveRadarrBlock::EditCollectionSelectQualityProfile => self.app.pop_navigation_stack(), | ActiveRadarrBlock::EditCollectionSelectQualityProfile => self.app.pop_navigation_stack(),
ActiveRadarrBlock::EditCollectionRootFolderPathInput => { ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -321,7 +324,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::EditCollectionRootFolderPathInput => { ActiveRadarrBlock::EditCollectionRootFolderPathInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveRadarrBlock::EditCollectionPrompt => { ActiveRadarrBlock::EditCollectionPrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -354,7 +357,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandle
ActiveRadarrBlock::EditCollectionPrompt => { ActiveRadarrBlock::EditCollectionPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditCollectionConfirmPrompt == ActiveRadarrBlock::EditCollectionConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection( self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection(
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -457,7 +458,7 @@ mod tests {
#[test] #[test]
fn test_edit_collection_root_folder_path_input_submit() { fn test_edit_collection_root_folder_path_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal { app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal {
path: "Test Path".into(), path: "Test Path".into(),
..EditCollectionModal::default() ..EditCollectionModal::default()
@@ -473,7 +474,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -759,7 +760,7 @@ mod tests {
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
if selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { if selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
} }
@@ -791,7 +792,7 @@ mod tests {
); );
if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput { if active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput {
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
} }
} }
@@ -811,7 +812,7 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into());
@@ -823,7 +824,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::EditCollectionPrompt.into() ActiveRadarrBlock::EditCollectionPrompt.into()
@@ -926,7 +927,7 @@ mod tests {
app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default()); app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal::default());
EditCollectionHandler::new( EditCollectionHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditCollectionRootFolderPathInput, ActiveRadarrBlock::EditCollectionRootFolderPathInput,
None, None,
@@ -942,7 +943,7 @@ mod tests {
.unwrap() .unwrap()
.path .path
.text, .text,
"h" "a"
); );
} }
@@ -1019,6 +1020,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_edit_collection_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditCollectionHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_collection_params() { fn test_build_edit_collection_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler; use crate::handlers::radarr_handlers::collections::collection_details_handler::CollectionDetailsHandler;
use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler; use crate::handlers::radarr_handlers::collections::edit_collection_handler::EditCollectionHandler;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
@@ -14,6 +12,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod collection_details_handler; mod collection_details_handler;
mod edit_collection_handler; mod edit_collection_handler;
@@ -73,6 +72,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
|| COLLECTIONS_BLOCKS.contains(&active_block) || COLLECTIONS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -145,7 +148,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Collections => match self.key { ActiveRadarrBlock::Collections => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into());
@@ -154,18 +157,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<'
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_COLLECTION_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAllCollectionsPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::UpdateAllCollectionsPrompt => { ActiveRadarrBlock::UpdateAllCollectionsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateCollections);
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -387,6 +388,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_downloads_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_download_id() { fn test_extract_download_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "downloads_handler_tests.rs"] #[path = "downloads_handler_tests.rs"]
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
DOWNLOADS_BLOCKS.contains(&active_block) DOWNLOADS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Downloads => match self.key { ActiveRadarrBlock::Downloads => match self.key {
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateDownloadsPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteDownloadPrompt => { ActiveRadarrBlock::DeleteDownloadPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteDownload(self.extract_download_id())); Some(RadarrEvent::DeleteDownload(self.extract_download_id()));
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DownloadsHandler<'a,
} }
} }
ActiveRadarrBlock::UpdateDownloadsPrompt => { ActiveRadarrBlock::UpdateDownloadsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateDownloads);
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -6,7 +5,9 @@ use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"] #[path = "edit_indexer_handler_tests.rs"]
@@ -65,6 +66,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
EDIT_INDEXER_BLOCKS.contains(&active_block) EDIT_INDEXER_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -356,7 +361,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerSeedRatioInput | ActiveRadarrBlock::EditIndexerSeedRatioInput
| ActiveRadarrBlock::EditIndexerTagsInput => { | ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.push_navigation_stack(selected_block.into()); self.app.push_navigation_stack(selected_block.into());
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveRadarrBlock::EditIndexerPriorityInput => self ActiveRadarrBlock::EditIndexerPriorityInput => self
.app .app
@@ -402,7 +407,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerSeedRatioInput | ActiveRadarrBlock::EditIndexerSeedRatioInput
| ActiveRadarrBlock::EditIndexerTagsInput => { | ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveRadarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(), ActiveRadarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
_ => (), _ => (),
@@ -423,7 +428,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
| ActiveRadarrBlock::EditIndexerPriorityInput | ActiveRadarrBlock::EditIndexerPriorityInput
| ActiveRadarrBlock::EditIndexerTagsInput => { | ActiveRadarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => self.app.pop_navigation_stack(), _ => self.app.pop_navigation_stack(),
} }
@@ -504,7 +509,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'
ActiveRadarrBlock::EditIndexerPrompt => { ActiveRadarrBlock::EditIndexerPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditIndexerConfirmPrompt == ActiveRadarrBlock::EditIndexerConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -10,6 +10,7 @@ mod tests {
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -1002,7 +1003,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), block.into()); assert_eq!(app.get_current_route(), block.into());
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -1027,7 +1028,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::EditIndexerPriorityInput.into() ActiveRadarrBlock::EditIndexerPriorityInput.into()
); );
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -1193,7 +1194,7 @@ mod tests {
fn test_edit_indexer_name_input_submit() { fn test_edit_indexer_name_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
name: "Test".into(), name: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1209,7 +1210,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -1229,7 +1230,7 @@ mod tests {
fn test_edit_indexer_url_input_submit() { fn test_edit_indexer_url_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
url: "Test".into(), url: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1245,7 +1246,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -1265,7 +1266,7 @@ mod tests {
fn test_edit_indexer_api_key_input_submit() { fn test_edit_indexer_api_key_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
api_key: "Test".into(), api_key: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1281,7 +1282,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -1301,7 +1302,7 @@ mod tests {
fn test_edit_indexer_seed_ratio_input_submit() { fn test_edit_indexer_seed_ratio_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
seed_ratio: "Test".into(), seed_ratio: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1317,7 +1318,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -1337,7 +1338,7 @@ mod tests {
fn test_edit_indexer_tags_input_submit() { fn test_edit_indexer_tags_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal {
tags: "Test".into(), tags: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1353,7 +1354,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -1417,12 +1418,12 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into());
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
EditIndexerHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle(); EditIndexerHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.radarr_data.edit_indexer_modal, app.data.radarr_data.edit_indexer_modal,
Some(EditIndexerModal::default()) Some(EditIndexerModal::default())
@@ -1597,7 +1598,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerNameInput, ActiveRadarrBlock::EditIndexerNameInput,
None, None,
@@ -1613,7 +1614,7 @@ mod tests {
.unwrap() .unwrap()
.name .name
.text, .text,
"h" "a"
); );
} }
@@ -1624,7 +1625,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerUrlInput, ActiveRadarrBlock::EditIndexerUrlInput,
None, None,
@@ -1640,7 +1641,7 @@ mod tests {
.unwrap() .unwrap()
.url .url
.text, .text,
"h" "a"
); );
} }
@@ -1651,7 +1652,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerApiKeyInput, ActiveRadarrBlock::EditIndexerApiKeyInput,
None, None,
@@ -1667,7 +1668,7 @@ mod tests {
.unwrap() .unwrap()
.api_key .api_key
.text, .text,
"h" "a"
); );
} }
@@ -1678,7 +1679,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerSeedRatioInput, ActiveRadarrBlock::EditIndexerSeedRatioInput,
None, None,
@@ -1694,7 +1695,7 @@ mod tests {
.unwrap() .unwrap()
.seed_ratio .seed_ratio
.text, .text,
"h" "a"
); );
} }
@@ -1705,7 +1706,7 @@ mod tests {
app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.radarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditIndexerTagsInput, ActiveRadarrBlock::EditIndexerTagsInput,
None, None,
@@ -1721,7 +1722,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .text,
"h" "a"
); );
} }
@@ -1793,6 +1794,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_edit_indexer_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditIndexerHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_indexer_params() { fn test_build_edit_indexer_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"] #[path = "edit_indexer_settings_handler_tests.rs"]
@@ -37,6 +38,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
INDEXER_SETTINGS_BLOCKS.contains(&active_block) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -207,7 +212,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
self.app.push_navigation_stack( self.app.push_navigation_stack(
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(), ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(),
); );
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags => { ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags => {
let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap();
@@ -224,7 +229,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
} }
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput ActiveRadarrBlock::IndexerSettingsMinimumAgeInput
| ActiveRadarrBlock::IndexerSettingsRetentionInput | ActiveRadarrBlock::IndexerSettingsRetentionInput
@@ -244,7 +249,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
} }
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => self.app.pop_navigation_stack(), _ => self.app.pop_navigation_stack(),
} }
@@ -269,7 +274,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
ActiveRadarrBlock::AllIndexerSettingsPrompt => { ActiveRadarrBlock::AllIndexerSettingsPrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::IndexerSettingsConfirmPrompt == ActiveRadarrBlock::IndexerSettingsConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some( self.app.data.radarr_data.prompt_confirm_action = Some(
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -602,7 +603,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into() ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -716,7 +717,7 @@ mod tests {
#[test] #[test]
fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_submit() { fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.indexer_settings = Some(IndexerSettings { app.data.radarr_data.indexer_settings = Some(IndexerSettings {
whitelisted_hardcoded_subs: "Test tags".into(), whitelisted_hardcoded_subs: "Test tags".into(),
..IndexerSettings::default() ..IndexerSettings::default()
@@ -734,7 +735,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -814,7 +815,7 @@ mod tests {
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(), ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(),
); );
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
IndexerSettingsHandler::new( IndexerSettingsHandler::new(
ESC_KEY, ESC_KEY,
@@ -825,7 +826,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.radarr_data.indexer_settings, app.data.radarr_data.indexer_settings,
Some(IndexerSettings::default()) Some(IndexerSettings::default())
@@ -907,7 +908,7 @@ mod tests {
app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.indexer_settings = Some(IndexerSettings::default());
IndexerSettingsHandler::new( IndexerSettingsHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
None, None,
@@ -923,7 +924,7 @@ mod tests {
.unwrap() .unwrap()
.whitelisted_hardcoded_subs .whitelisted_hardcoded_subs
.text, .text,
"h" "a"
); );
} }
@@ -970,6 +971,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_indexer_settings_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_indexer_settings_body() { fn test_build_edit_indexer_settings_body() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -633,6 +633,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_indexer_id() { fn test_extract_indexer_id() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -7
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
@@ -15,6 +13,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::servarr_models::Indexer; use crate::models::servarr_models::Indexer;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod edit_indexer_handler; mod edit_indexer_handler;
mod edit_indexer_settings_handler; mod edit_indexer_settings_handler;
@@ -70,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|| INDEXERS_BLOCKS.contains(&active_block) || INDEXERS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -169,20 +172,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Indexers => match self.key { ActiveRadarrBlock::Indexers => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.test.key => { _ if matches_key!(test, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestIndexer.into()); .push_navigation_stack(ActiveRadarrBlock::TestIndexer.into());
} }
_ if key == DEFAULT_KEYBINDINGS.test_all.key => { _ if matches_key!(test_all, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into()); .push_navigation_stack(ActiveRadarrBlock::TestAllIndexers.into());
} }
_ if key == DEFAULT_KEYBINDINGS.settings.key => { _ if matches_key!(settings, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AllIndexerSettingsPrompt.into());
@@ -192,7 +195,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
_ => (), _ => (),
}, },
ActiveRadarrBlock::DeleteIndexerPrompt => { ActiveRadarrBlock::DeleteIndexerPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id())); Some(RadarrEvent::DeleteIndexer(self.extract_indexer_id()));
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for TestAllIndexersHandl
active_block == ActiveRadarrBlock::TestAllIndexers active_block == ActiveRadarrBlock::TestAllIndexers
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -7,6 +7,7 @@ mod tests {
use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_esc { mod test_handle_esc {
@@ -48,6 +49,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_test_all_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_test_all_indexers_handler_is_not_ready_when_loading() { fn test_test_all_indexers_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{ use crate::models::radarr_models::{
@@ -11,7 +10,9 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, App, Key,
};
#[cfg(test)] #[cfg(test)]
#[path = "add_movie_handler_tests.rs"] #[path = "add_movie_handler_tests.rs"]
@@ -132,6 +133,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ADD_MOVIE_BLOCKS.contains(&active_block) ADD_MOVIE_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -404,7 +409,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into()); .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchResults.into());
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ if self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchResults _ if self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchResults
&& self.app.data.radarr_data.add_searched_movies.is_some() => && self.app.data.radarr_data.add_searched_movies.is_some() =>
@@ -468,7 +473,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
) )
.into(), .into(),
); );
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
_ => (), _ => (),
} }
@@ -479,7 +484,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
| ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.pop_navigation_stack(), | ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.pop_navigation_stack(),
ActiveRadarrBlock::AddMovieTagsInput => { ActiveRadarrBlock::AddMovieTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -490,12 +495,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ActiveRadarrBlock::AddMovieSearchInput => { ActiveRadarrBlock::AddMovieSearchInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.add_movie_search = None; self.app.data.radarr_data.add_movie_search = None;
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => { ActiveRadarrBlock::AddMovieSearchResults | ActiveRadarrBlock::AddMovieEmptySearchResults => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.add_searched_movies = None; self.app.data.radarr_data.add_searched_movies = None;
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveRadarrBlock::AddMoviePrompt => { ActiveRadarrBlock::AddMoviePrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -509,7 +514,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
| ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.pop_navigation_stack(), | ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.pop_navigation_stack(),
ActiveRadarrBlock::AddMovieTagsInput => { ActiveRadarrBlock::AddMovieTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -542,7 +547,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a,
ActiveRadarrBlock::AddMoviePrompt => { ActiveRadarrBlock::AddMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::AddMovieConfirmPrompt == ActiveRadarrBlock::AddMovieConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -782,7 +782,7 @@ mod tests {
#[test] #[test]
fn test_add_movie_search_input_submit() { fn test_add_movie_search_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.add_movie_search = Some("test".into()); app.data.radarr_data.add_movie_search = Some("test".into());
AddMovieHandler::new( AddMovieHandler::new(
@@ -793,7 +793,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::AddMovieSearchResults.into() ActiveRadarrBlock::AddMovieSearchResults.into()
@@ -805,7 +805,7 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
AddMovieHandler::new( AddMovieHandler::new(
SUBMIT_KEY, SUBMIT_KEY,
@@ -815,7 +815,7 @@ mod tests {
) )
.handle(); .handle();
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::AddMovieSearchInput.into() ActiveRadarrBlock::AddMovieSearchInput.into()
@@ -1093,7 +1093,7 @@ mod tests {
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
if selected_block == ActiveRadarrBlock::AddMovieTagsInput { if selected_block == ActiveRadarrBlock::AddMovieTagsInput {
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
} }
@@ -1126,7 +1126,7 @@ mod tests {
); );
if active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput { if active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput {
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
} }
} }
@@ -1149,7 +1149,7 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.is_loading = is_ready; app.is_loading = is_ready;
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
AddMovieHandler::new( AddMovieHandler::new(
@@ -1160,7 +1160,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert_eq!(app.data.radarr_data.add_movie_search, None); assert_eq!(app.data.radarr_data.add_movie_search, None);
} }
@@ -1169,7 +1169,7 @@ mod tests {
fn test_add_movie_input_esc() { fn test_add_movie_input_esc() {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into());
@@ -1181,7 +1181,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::AddMoviePrompt.into() ActiveRadarrBlock::AddMoviePrompt.into()
@@ -1213,7 +1213,7 @@ mod tests {
ActiveRadarrBlock::AddMovieSearchInput.into() ActiveRadarrBlock::AddMovieSearchInput.into()
); );
assert!(app.data.radarr_data.add_searched_movies.is_none()); assert!(app.data.radarr_data.add_searched_movies.is_none());
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -1259,7 +1259,7 @@ mod tests {
fn test_add_movie_tags_input_esc() { fn test_add_movie_tags_input_esc() {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into());
app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMovieTagsInput.into());
@@ -1271,7 +1271,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::AddMoviePrompt.into() ActiveRadarrBlock::AddMoviePrompt.into()
@@ -1395,7 +1395,7 @@ mod tests {
app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
AddMovieHandler::new( AddMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::AddMovieSearchInput, ActiveRadarrBlock::AddMovieSearchInput,
None, None,
@@ -1404,7 +1404,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.add_movie_search.as_ref().unwrap().text, app.data.radarr_data.add_movie_search.as_ref().unwrap().text,
"h" "a"
); );
} }
@@ -1414,7 +1414,7 @@ mod tests {
app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default()); app.data.radarr_data.add_movie_modal = Some(AddMovieModal::default());
AddMovieHandler::new( AddMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::AddMovieTagsInput, ActiveRadarrBlock::AddMovieTagsInput,
None, None,
@@ -1430,7 +1430,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .text,
"h" "a"
); );
} }
@@ -1523,6 +1523,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_add_movie_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = AddMovieHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_add_movie_search_no_panic_on_none_search_result() { fn test_add_movie_search_no_panic_on_none_search_result() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::radarr_models::DeleteMovieParams; use crate::models::radarr_models::DeleteMovieParams;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DELETE_MOVIE_BLOCKS};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
@@ -37,6 +37,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
DELETE_MOVIE_BLOCKS.contains(&active_block) DELETE_MOVIE_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -122,7 +126,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'
if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt if self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt
&& self.app.data.radarr_data.selected_block.get_active_block() && self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::DeleteMovieConfirmPrompt == ActiveRadarrBlock::DeleteMovieConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -313,6 +314,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_delete_movie_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DeleteMovieHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_delete_movie_params() { fn test_build_delete_movie_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,7 @@ use crate::models::servarr_data::radarr::modals::EditMovieModal;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_MOVIE_BLOCKS};
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "edit_movie_handler_tests.rs"] #[path = "edit_movie_handler_tests.rs"]
@@ -68,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
EDIT_MOVIE_BLOCKS.contains(&active_block) EDIT_MOVIE_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -290,7 +293,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
) )
.into(), .into(),
); );
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveRadarrBlock::EditMovieToggleMonitored => { ActiveRadarrBlock::EditMovieToggleMonitored => {
self self
@@ -319,7 +322,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
| ActiveRadarrBlock::EditMovieSelectQualityProfile => self.app.pop_navigation_stack(), | ActiveRadarrBlock::EditMovieSelectQualityProfile => self.app.pop_navigation_stack(),
ActiveRadarrBlock::EditMoviePathInput | ActiveRadarrBlock::EditMovieTagsInput => { ActiveRadarrBlock::EditMoviePathInput | ActiveRadarrBlock::EditMovieTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -329,7 +332,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::EditMovieTagsInput | ActiveRadarrBlock::EditMoviePathInput => { ActiveRadarrBlock::EditMovieTagsInput | ActiveRadarrBlock::EditMoviePathInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveRadarrBlock::EditMoviePrompt => { ActiveRadarrBlock::EditMoviePrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -376,7 +379,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a,
ActiveRadarrBlock::EditMoviePrompt => { ActiveRadarrBlock::EditMoviePrompt => {
if self.app.data.radarr_data.selected_block.get_active_block() if self.app.data.radarr_data.selected_block.get_active_block()
== ActiveRadarrBlock::EditMovieConfirmPrompt == ActiveRadarrBlock::EditMovieConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::assert_str_eq; use pretty_assertions::assert_str_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -548,7 +549,7 @@ mod tests {
#[test] #[test]
fn test_edit_movie_path_input_submit() { fn test_edit_movie_path_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal { app.data.radarr_data.edit_movie_modal = Some(EditMovieModal {
path: "Test Path".into(), path: "Test Path".into(),
..EditMovieModal::default() ..EditMovieModal::default()
@@ -564,7 +565,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -583,7 +584,7 @@ mod tests {
#[test] #[test]
fn test_edit_movie_tags_input_submit() { fn test_edit_movie_tags_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal { app.data.radarr_data.edit_movie_modal = Some(EditMovieModal {
tags: "Test Tags".into(), tags: "Test Tags".into(),
..EditMovieModal::default() ..EditMovieModal::default()
@@ -599,7 +600,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.radarr_data .radarr_data
@@ -813,7 +814,7 @@ mod tests {
if selected_block == ActiveRadarrBlock::EditMoviePathInput if selected_block == ActiveRadarrBlock::EditMoviePathInput
|| selected_block == ActiveRadarrBlock::EditMovieTagsInput || selected_block == ActiveRadarrBlock::EditMovieTagsInput
{ {
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
} }
@@ -851,7 +852,7 @@ mod tests {
.into() .into()
); );
assert_eq!(app.data.radarr_data.prompt_confirm_action, None); assert_eq!(app.data.radarr_data.prompt_confirm_action, None);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
#[rstest] #[rstest]
@@ -885,7 +886,7 @@ mod tests {
if active_radarr_block == ActiveRadarrBlock::EditMoviePathInput if active_radarr_block == ActiveRadarrBlock::EditMoviePathInput
|| active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput || active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput
{ {
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
} }
} }
@@ -911,13 +912,13 @@ mod tests {
) { ) {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into());
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
EditMovieHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle(); EditMovieHandler::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::EditMoviePrompt.into() ActiveRadarrBlock::EditMoviePrompt.into()
@@ -1033,7 +1034,7 @@ mod tests {
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default()); app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
EditMovieHandler::new( EditMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditMoviePathInput, ActiveRadarrBlock::EditMoviePathInput,
None, None,
@@ -1049,7 +1050,7 @@ mod tests {
.unwrap() .unwrap()
.path .path
.text, .text,
"h" "a"
); );
} }
@@ -1059,7 +1060,7 @@ mod tests {
app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default()); app.data.radarr_data.edit_movie_modal = Some(EditMovieModal::default());
EditMovieHandler::new( EditMovieHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::EditMovieTagsInput, ActiveRadarrBlock::EditMovieTagsInput,
None, None,
@@ -1075,7 +1076,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .text,
"h" "a"
); );
} }
@@ -1148,6 +1149,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_edit_movie_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditMovieHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_movie_params() { fn test_build_edit_movie_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -331,7 +331,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::AddMovieSearchInput.into() ActiveRadarrBlock::AddMovieSearchInput.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.add_movie_search.is_some()); assert!(app.data.radarr_data.add_movie_search.is_some());
} }
@@ -355,7 +355,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.add_movie_search.is_none()); assert!(app.data.radarr_data.add_movie_search.is_none());
} }
@@ -777,6 +777,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_library_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = LibraryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_library_handler_not_ready_when_loading() { fn test_library_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+11 -8
View File
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
@@ -8,7 +7,6 @@ use crate::handlers::radarr_handlers::library::edit_movie_handler::EditMovieHand
use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler; use crate::handlers::radarr_handlers::library::movie_details_handler::MovieDetailsHandler;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::models::radarr_models::Movie; use crate::models::radarr_models::Movie;
use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::servarr_data::radarr::radarr_data::{
@@ -17,6 +15,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::models::{BlockSelectionState, HorizontallyScrollableText}; use crate::models::{BlockSelectionState, HorizontallyScrollableText};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
mod add_movie_handler; mod add_movie_handler;
mod delete_movie_handler; mod delete_movie_handler;
@@ -81,6 +80,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
|| LIBRARY_BLOCKS.contains(&active_block) || LIBRARY_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -161,7 +164,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::Movies => match self.key { ActiveRadarrBlock::Movies => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMoviePrompt,
@@ -173,25 +176,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, '
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into());
self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.add_movie_search = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAllMoviesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::UpdateAllMoviesPrompt => { ActiveRadarrBlock::UpdateAllMoviesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies); self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
@@ -1,9 +1,7 @@
use serde_json::Number; use serde_json::Number;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::radarr_models::{ use crate::models::radarr_models::{
@@ -16,6 +14,7 @@ use crate::models::servarr_models::Language;
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "movie_details_handler_tests.rs"] #[path = "movie_details_handler_tests.rs"]
@@ -136,6 +135,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
MOVIE_DETAILS_BLOCKS.contains(&active_block) MOVIE_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -245,13 +248,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
| ActiveRadarrBlock::Cast | ActiveRadarrBlock::Cast
| ActiveRadarrBlock::Crew | ActiveRadarrBlock::Crew
| ActiveRadarrBlock::ManualSearch => match self.key { | ActiveRadarrBlock::ManualSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, self.key) => {
self.app.data.radarr_data.movie_info_tabs.previous(); self.app.data.radarr_data.movie_info_tabs.previous();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self.app.data.radarr_data.movie_info_tabs.get_active_route(), self.app.data.radarr_data.movie_info_tabs.get_active_route(),
); );
} }
_ if self.key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, self.key) => {
self.app.data.radarr_data.movie_info_tabs.next(); self.app.data.radarr_data.movie_info_tabs.next();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self.app.data.radarr_data.movie_info_tabs.get_active_route(), self.app.data.radarr_data.movie_info_tabs.get_active_route(),
@@ -332,12 +335,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
| ActiveRadarrBlock::Cast | ActiveRadarrBlock::Cast
| ActiveRadarrBlock::Crew | ActiveRadarrBlock::Crew
| ActiveRadarrBlock::ManualSearch => match self.key { | ActiveRadarrBlock::ManualSearch => match self.key {
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AutomaticallySearchMoviePrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMoviePrompt,
@@ -349,35 +352,33 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<
self.app.data.radarr_data.selected_block = self.app.data.radarr_data.selected_block =
BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_MOVIE_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::UpdateAndScanPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::UpdateAndScanPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self self
.app .app
.pop_and_push_navigation_stack(self.active_radarr_block.into()); .pop_and_push_navigation_stack(self.active_radarr_block.into());
} }
_ => (), _ => (),
}, },
ActiveRadarrBlock::AutomaticallySearchMoviePrompt ActiveRadarrBlock::AutomaticallySearchMoviePrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id())); Some(RadarrEvent::TriggerAutomaticSearch(self.extract_movie_id()));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::UpdateAndScanPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { ActiveRadarrBlock::UpdateAndScanPrompt if matches_key!(confirm, key) => {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::UpdateAndScan(self.extract_movie_id())); Some(RadarrEvent::UpdateAndScan(self.extract_movie_id()));
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveRadarrBlock::ManualSearchConfirmPrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { ActiveRadarrBlock::ManualSearchConfirmPrompt if matches_key!(confirm, key) => {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease( self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DownloadRelease(
self.build_radarr_release_download_body(), self.build_radarr_release_download_body(),
@@ -1042,6 +1042,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_movie_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = MovieDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[rstest] #[rstest]
fn test_movie_details_handler_is_not_ready_when_loading( fn test_movie_details_handler_is_not_ready_when_loading(
#[values( #[values(
+9 -6
View File
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler; use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
use crate::handlers::radarr_handlers::collections::CollectionsHandler; use crate::handlers::radarr_handlers::collections::CollectionsHandler;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler; use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
@@ -8,7 +7,7 @@ use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
use crate::handlers::radarr_handlers::system::SystemHandler; use crate::handlers::radarr_handlers::system::SystemHandler;
use crate::handlers::KeyEventHandler; use crate::handlers::KeyEventHandler;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::{App, Key}; use crate::{matches_key, App, Key};
mod blocklist; mod blocklist;
mod collections; mod collections;
@@ -65,6 +64,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
true true
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -109,11 +112,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) { pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
let key_ref = key; let key_ref = key;
match key_ref { match key_ref {
_ if key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, key, app.ignore_special_keys_for_textbox_input) => {
app.data.radarr_data.main_tabs.previous(); app.data.radarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route());
} }
_ if key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, key, app.ignore_special_keys_for_textbox_input) => {
app.data.radarr_data.main_tabs.next(); app.data.radarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.data.radarr_data.main_tabs.get_active_route());
} }
@@ -139,7 +142,7 @@ macro_rules! search_table {
}; };
$app.data.radarr_data.is_searching = false; $app.data.radarr_data.is_searching = false;
$app.should_ignore_quit_key = false; $app.ignore_special_keys_for_textbox_input = false;
if search_index.is_some() { if search_index.is_some() {
$app.pop_navigation_stack(); $app.pop_navigation_stack();
@@ -166,7 +169,7 @@ macro_rules! search_table {
}; };
$app.data.radarr_data.is_searching = false; $app.data.radarr_data.is_searching = false;
$app.should_ignore_quit_key = false; $app.ignore_special_keys_for_textbox_input = false;
if search_index.is_some() { if search_index.is_some() {
$app.pop_navigation_stack(); $app.pop_navigation_stack();
@@ -275,7 +275,7 @@ pub(in crate::handlers::radarr_handlers) mod utils {
audio_stream_count: 1, audio_stream_count: 1,
video_bit_depth: 10, video_bit_depth: 10,
video_bitrate: 0, video_bitrate: 0,
video_codec: "x265".to_owned(), video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(), video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x804".to_owned(), resolution: "1920x804".to_owned(),
run_time: "2:00:00".to_owned(), run_time: "2:00:00".to_owned(),
@@ -46,6 +46,76 @@ mod tests {
assert_eq!(app.get_current_route(), right_block.into()); assert_eq!(app.get_current_route(), right_block.into());
} }
#[rstest]
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation(
#[case] index: usize,
#[case] left_block: ActiveRadarrBlock,
#[case] right_block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = false;
app.data.radarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
left_block.into()
);
assert_eq!(app.get_current_route(), left_block.into());
app.data.radarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
right_block.into()
);
assert_eq!(app.get_current_route(), right_block.into());
}
#[rstest]
#[case(0, ActiveRadarrBlock::Movies)]
#[case(1, ActiveRadarrBlock::Collections)]
#[case(2, ActiveRadarrBlock::Downloads)]
#[case(3, ActiveRadarrBlock::Blocklist)]
#[case(4, ActiveRadarrBlock::RootFolders)]
#[case(5, ActiveRadarrBlock::Indexers)]
#[case(6, ActiveRadarrBlock::System)]
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
#[case] index: usize,
#[case] block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(block.into());
app.data.radarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
}
#[rstest] #[rstest]
fn test_delegates_system_blocks_to_system_handler( fn test_delegates_system_blocks_to_system_handler(
#[values( #[values(
@@ -217,6 +287,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_radarr_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = RadarrHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::Movies,
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_radarr_handler_is_ready() { fn test_radarr_handler_is_ready() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
@@ -8,7 +7,9 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_F
use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; use crate::models::servarr_models::{AddRootFolderBody, RootFolder};
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use crate::network::radarr_network::RadarrEvent; use crate::network::radarr_network::RadarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)] #[cfg(test)]
#[path = "root_folders_handler_tests.rs"] #[path = "root_folders_handler_tests.rs"]
@@ -68,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
ROOT_FOLDERS_BLOCKS.contains(&active_block) ROOT_FOLDERS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -168,7 +173,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
self.build_add_root_folder_body(), self.build_add_root_folder_body(),
)); ));
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
_ => (), _ => (),
@@ -181,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.radarr_data.edit_root_folder = None; self.app.data.radarr_data.edit_root_folder = None;
self.app.data.radarr_data.prompt_confirm = false; self.app.data.radarr_data.prompt_confirm = false;
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveRadarrBlock::DeleteRootFolderPrompt => { ActiveRadarrBlock::DeleteRootFolderPrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -195,15 +200,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
let key = self.key; let key = self.key;
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::RootFolders => match self.key { ActiveRadarrBlock::RootFolders => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); .push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
self.app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
_ => (), _ => (),
}, },
@@ -215,7 +220,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<'
) )
} }
ActiveRadarrBlock::DeleteRootFolderPrompt => { ActiveRadarrBlock::DeleteRootFolderPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id())); Some(RadarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -262,7 +263,7 @@ mod tests {
.set_items(vec![RootFolder::default()]); .set_items(vec![RootFolder::default()]);
app.data.radarr_data.edit_root_folder = Some("Test".into()); app.data.radarr_data.edit_root_folder = Some("Test".into());
app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.prompt_confirm = true;
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
@@ -275,7 +276,7 @@ mod tests {
.handle(); .handle();
assert!(app.data.radarr_data.prompt_confirm); assert!(app.data.radarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.radarr_data.prompt_confirm_action, app.data.radarr_data.prompt_confirm_action,
Some(RadarrEvent::AddRootFolder(expected_add_root_folder_body)) Some(RadarrEvent::AddRootFolder(expected_add_root_folder_body))
@@ -291,7 +292,7 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
app.data.radarr_data.prompt_confirm = false; app.data.radarr_data.prompt_confirm = false;
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
@@ -304,7 +305,7 @@ mod tests {
.handle(); .handle();
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.prompt_confirm_action.is_none()); assert!(app.data.radarr_data.prompt_confirm_action.is_none());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -406,7 +407,7 @@ mod tests {
app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into());
app.data.radarr_data.edit_root_folder = Some("/nfs/test".into()); app.data.radarr_data.edit_root_folder = Some("/nfs/test".into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
RootFoldersHandler::new( RootFoldersHandler::new(
ESC_KEY, ESC_KEY,
@@ -423,7 +424,7 @@ mod tests {
assert!(app.data.radarr_data.edit_root_folder.is_none()); assert!(app.data.radarr_data.edit_root_folder.is_none());
assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.data.radarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
#[rstest] #[rstest]
@@ -472,7 +473,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::AddRootFolderPrompt.into() ActiveRadarrBlock::AddRootFolderPrompt.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.edit_root_folder.is_some()); assert!(app.data.radarr_data.edit_root_folder.is_some());
} }
@@ -499,7 +500,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::RootFolders.into() ActiveRadarrBlock::RootFolders.into()
); );
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.edit_root_folder.is_none()); assert!(app.data.radarr_data.edit_root_folder.is_none());
} }
@@ -589,7 +590,7 @@ mod tests {
app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
RootFoldersHandler::new( RootFoldersHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::AddRootFolderPrompt, ActiveRadarrBlock::AddRootFolderPrompt,
None, None,
@@ -598,7 +599,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.edit_root_folder.as_ref().unwrap().text, app.data.radarr_data.edit_root_folder.as_ref().unwrap().text,
"h" "a"
); );
} }
@@ -644,6 +645,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_root_folders_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = RootFoldersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_add_root_folder_body() { fn test_build_add_root_folder_body() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -6
View File
@@ -1,9 +1,9 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler}; use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::Scrollable; use crate::models::Scrollable;
@@ -35,6 +35,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
SystemDetailsHandler::accepts(active_block) || active_block == ActiveRadarrBlock::System SystemDetailsHandler::accepts(active_block) || active_block == ActiveRadarrBlock::System
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -86,15 +90,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
if self.active_radarr_block == ActiveRadarrBlock::System { if self.active_radarr_block == ActiveRadarrBlock::System {
let key = self.key; let key = self.key;
match self.key { match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.events.key => { _ if matches_key!(events, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into()); .push_navigation_stack(ActiveRadarrBlock::SystemQueuedEvents.into());
} }
_ if key == DEFAULT_KEYBINDINGS.logs.key => { _ if matches_key!(logs, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemLogs.into()); .push_navigation_stack(ActiveRadarrBlock::SystemLogs.into());
@@ -106,12 +110,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemHandler<'a, 'b
.set_items(self.app.data.radarr_data.logs.items.to_vec()); .set_items(self.app.data.radarr_data.logs.items.to_vec());
self.app.data.radarr_data.log_details.scroll_to_bottom(); self.app.data.radarr_data.log_details.scroll_to_bottom();
} }
_ if key == DEFAULT_KEYBINDINGS.tasks.key => { _ if matches_key!(tasks, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemTasks.into()); .push_navigation_stack(ActiveRadarrBlock::SystemTasks.into());
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into()); .push_navigation_stack(ActiveRadarrBlock::SystemUpdates.into());
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::radarr_models::RadarrTaskName; use crate::models::radarr_models::RadarrTaskName;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::stateful_list::StatefulList; use crate::models::stateful_list::StatefulList;
@@ -36,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
SYSTEM_DETAILS_BLOCKS.contains(&active_block) SYSTEM_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
match self.active_radarr_block { match self.active_radarr_block {
ActiveRadarrBlock::SystemLogs => match self.key { ActiveRadarrBlock::SystemLogs => match self.key {
_ if key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, key) => {
self self
.app .app
.data .data
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
.iter() .iter()
.for_each(|log| log.scroll_right()); .for_each(|log| log.scroll_right());
} }
_ if key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, key) => {
self self
.app .app
.data .data
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for SystemDetailsHandler
} }
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block) if SYSTEM_DETAILS_BLOCKS.contains(&self.active_radarr_block) && matches_key!(refresh, self.key)
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
{ {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
if self.active_radarr_block == ActiveRadarrBlock::SystemTaskStartConfirmPrompt if self.active_radarr_block == ActiveRadarrBlock::SystemTaskStartConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.radarr_data.prompt_confirm = true; self.app.data.radarr_data.prompt_confirm = true;
self.app.data.radarr_data.prompt_confirm_action = self.app.data.radarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -938,6 +939,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_system_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_task_name() { fn test_extract_task_name() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -450,6 +450,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_system_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_system_handler_is_not_ready_when_loading() { fn test_system_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime; use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -513,6 +514,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_blocklist_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = BlocklistHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_blocklist_item_id() { fn test_extract_blocklist_item_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
@@ -9,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, BLOCKL
use crate::models::sonarr_models::BlocklistItem; use crate::models::sonarr_models::BlocklistItem;
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "blocklist_handler_tests.rs"] #[path = "blocklist_handler_tests.rs"]
@@ -51,6 +50,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
BLOCKLIST_BLOCKS.contains(&active_block) BLOCKLIST_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -143,10 +146,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Blocklist => match self.key { ActiveSonarrBlock::Blocklist => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.clear.key => { _ if matches_key!(clear, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::BlocklistClearAllItemsPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::BlocklistClearAllItemsPrompt.into());
@@ -154,7 +157,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
_ => (), _ => (),
}, },
ActiveSonarrBlock::DeleteBlocklistItemPrompt => { ActiveSonarrBlock::DeleteBlocklistItemPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem( self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteBlocklistItem(
self.extract_blocklist_item_id(), self.extract_blocklist_item_id(),
@@ -164,7 +167,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for BlocklistHandler<'a,
} }
} }
ActiveSonarrBlock::BlocklistClearAllItemsPrompt => { ActiveSonarrBlock::BlocklistClearAllItemsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::ClearBlocklist); self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::ClearBlocklist);
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -389,6 +390,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_downloads_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DownloadsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_download_id() { fn test_extract_download_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DOWNLOADS_BLOCKS};
use crate::models::sonarr_models::DownloadRecord; use crate::models::sonarr_models::DownloadRecord;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "downloads_handler_tests.rs"] #[path = "downloads_handler_tests.rs"]
@@ -47,6 +46,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
DOWNLOADS_BLOCKS.contains(&active_block) DOWNLOADS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -130,18 +133,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Downloads => match self.key { ActiveSonarrBlock::Downloads => match self.key {
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateDownloadsPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateDownloadsPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::DeleteDownloadPrompt => { ActiveSonarrBlock::DeleteDownloadPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteDownload(self.extract_download_id())); Some(SonarrEvent::DeleteDownload(self.extract_download_id()));
@@ -150,7 +153,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DownloadsHandler<'a,
} }
} }
ActiveSonarrBlock::UpdateDownloadsPrompt => { ActiveSonarrBlock::UpdateDownloadsPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateDownloads); self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateDownloads);
@@ -4,6 +4,7 @@ mod tests {
use chrono::DateTime; use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -306,6 +307,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_history_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_history_handler_not_ready_when_loading() { fn test_history_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+6 -3
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_clear_errors, KeyEventHandler}; use crate::handlers::{handle_clear_errors, KeyEventHandler};
@@ -9,6 +7,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, HISTOR
use crate::models::servarr_models::Language; use crate::models::servarr_models::Language;
use crate::models::sonarr_models::SonarrHistoryItem; use crate::models::sonarr_models::SonarrHistoryItem;
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "history_handler_tests.rs"] #[path = "history_handler_tests.rs"]
@@ -52,6 +51,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
HISTORY_BLOCKS.contains(&active_block) HISTORY_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -110,7 +113,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for HistoryHandler<'a, '
let key = self.key; let key = self.key;
if self.active_sonarr_block == ActiveSonarrBlock::History { if self.active_sonarr_block == ActiveSonarrBlock::History {
match self.key { match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -6,7 +5,9 @@ use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{
handle_prompt_left_right_keys, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_handler_tests.rs"] #[path = "edit_indexer_handler_tests.rs"]
@@ -64,6 +65,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
EDIT_INDEXER_BLOCKS.contains(&active_block) EDIT_INDEXER_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -355,7 +360,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
| ActiveSonarrBlock::EditIndexerSeedRatioInput | ActiveSonarrBlock::EditIndexerSeedRatioInput
| ActiveSonarrBlock::EditIndexerTagsInput => { | ActiveSonarrBlock::EditIndexerTagsInput => {
self.app.push_navigation_stack(selected_block.into()); self.app.push_navigation_stack(selected_block.into());
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveSonarrBlock::EditIndexerPriorityInput => self ActiveSonarrBlock::EditIndexerPriorityInput => self
.app .app
@@ -401,7 +406,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
| ActiveSonarrBlock::EditIndexerSeedRatioInput | ActiveSonarrBlock::EditIndexerSeedRatioInput
| ActiveSonarrBlock::EditIndexerTagsInput => { | ActiveSonarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveSonarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(), ActiveSonarrBlock::EditIndexerPriorityInput => self.app.pop_navigation_stack(),
_ => (), _ => (),
@@ -422,7 +427,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
| ActiveSonarrBlock::EditIndexerPriorityInput | ActiveSonarrBlock::EditIndexerPriorityInput
| ActiveSonarrBlock::EditIndexerTagsInput => { | ActiveSonarrBlock::EditIndexerTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => self.app.pop_navigation_stack(), _ => self.app.pop_navigation_stack(),
} }
@@ -503,7 +508,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditIndexerHandler<'
ActiveSonarrBlock::EditIndexerPrompt => { ActiveSonarrBlock::EditIndexerPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block() if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::EditIndexerConfirmPrompt == ActiveSonarrBlock::EditIndexerConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -10,6 +10,7 @@ mod tests {
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::servarr_models::EditIndexerParams; use crate::models::servarr_models::EditIndexerParams;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_scroll_up_and_down { mod test_handle_scroll_up_and_down {
@@ -1002,7 +1003,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), block.into()); assert_eq!(app.get_current_route(), block.into());
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -1027,7 +1028,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::EditIndexerPriorityInput.into() ActiveSonarrBlock::EditIndexerPriorityInput.into()
); );
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -1193,7 +1194,7 @@ mod tests {
fn test_edit_indexer_name_input_submit() { fn test_edit_indexer_name_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
name: "Test".into(), name: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1209,7 +1210,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -1229,7 +1230,7 @@ mod tests {
fn test_edit_indexer_url_input_submit() { fn test_edit_indexer_url_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
url: "Test".into(), url: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1245,7 +1246,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -1265,7 +1266,7 @@ mod tests {
fn test_edit_indexer_api_key_input_submit() { fn test_edit_indexer_api_key_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
api_key: "Test".into(), api_key: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1281,7 +1282,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -1301,7 +1302,7 @@ mod tests {
fn test_edit_indexer_seed_ratio_input_submit() { fn test_edit_indexer_seed_ratio_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
seed_ratio: "Test".into(), seed_ratio: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1317,7 +1318,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -1337,7 +1338,7 @@ mod tests {
fn test_edit_indexer_tags_input_submit() { fn test_edit_indexer_tags_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal { app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal {
tags: "Test".into(), tags: "Test".into(),
..EditIndexerModal::default() ..EditIndexerModal::default()
@@ -1353,7 +1354,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -1417,12 +1418,12 @@ mod tests {
app.push_navigation_stack(ActiveSonarrBlock::Indexers.into()); app.push_navigation_stack(ActiveSonarrBlock::Indexers.into());
app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(active_sonarr_block.into());
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
EditIndexerHandler::new(ESC_KEY, &mut app, active_sonarr_block, None).handle(); EditIndexerHandler::new(ESC_KEY, &mut app, active_sonarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Indexers.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.sonarr_data.edit_indexer_modal, app.data.sonarr_data.edit_indexer_modal,
Some(EditIndexerModal::default()) Some(EditIndexerModal::default())
@@ -1597,7 +1598,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerNameInput, ActiveSonarrBlock::EditIndexerNameInput,
None, None,
@@ -1613,7 +1614,7 @@ mod tests {
.unwrap() .unwrap()
.name .name
.text, .text,
"h" "a"
); );
} }
@@ -1624,7 +1625,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerUrlInput, ActiveSonarrBlock::EditIndexerUrlInput,
None, None,
@@ -1640,7 +1641,7 @@ mod tests {
.unwrap() .unwrap()
.url .url
.text, .text,
"h" "a"
); );
} }
@@ -1651,7 +1652,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerApiKeyInput, ActiveSonarrBlock::EditIndexerApiKeyInput,
None, None,
@@ -1667,7 +1668,7 @@ mod tests {
.unwrap() .unwrap()
.api_key .api_key
.text, .text,
"h" "a"
); );
} }
@@ -1678,7 +1679,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerSeedRatioInput, ActiveSonarrBlock::EditIndexerSeedRatioInput,
None, None,
@@ -1694,7 +1695,7 @@ mod tests {
.unwrap() .unwrap()
.seed_ratio .seed_ratio
.text, .text,
"h" "a"
); );
} }
@@ -1705,7 +1706,7 @@ mod tests {
app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default()); app.data.sonarr_data.edit_indexer_modal = Some(EditIndexerModal::default());
EditIndexerHandler::new( EditIndexerHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditIndexerTagsInput, ActiveSonarrBlock::EditIndexerTagsInput,
None, None,
@@ -1721,7 +1722,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .text,
"h" "a"
); );
} }
@@ -1793,6 +1794,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_edit_indexer_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditIndexerHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_indexer_params() { fn test_build_edit_indexer_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_prompt_left_right_keys;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ use crate::models::servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS, ActiveSonarrBlock, INDEXER_SETTINGS_BLOCKS,
}; };
use crate::models::sonarr_models::IndexerSettings; use crate::models::sonarr_models::IndexerSettings;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_prompt_left_right_keys, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_settings_handler_tests.rs"] #[path = "edit_indexer_settings_handler_tests.rs"]
@@ -37,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
INDEXER_SETTINGS_BLOCKS.contains(&active_block) INDEXER_SETTINGS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -183,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexerSettingsHandl
if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt if self.active_sonarr_block == ActiveSonarrBlock::AllIndexerSettingsPrompt
&& self.app.data.sonarr_data.selected_block.get_active_block() && self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::IndexerSettingsConfirmPrompt == ActiveSonarrBlock::IndexerSettingsConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings( self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::EditAllIndexerSettings(
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -521,6 +522,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_indexer_settings_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexerSettingsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_indexer_settings_params() { fn test_build_edit_indexer_settings_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -643,6 +643,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = IndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_indexer_id() { fn test_extract_indexer_id() {
let mut app = App::test_default(); let mut app = App::test_default();
+10 -7
View File
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler; use crate::handlers::sonarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler; use crate::handlers::sonarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
@@ -15,6 +13,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
use crate::models::servarr_models::Indexer; use crate::models::servarr_models::Indexer;
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
mod edit_indexer_handler; mod edit_indexer_handler;
mod edit_indexer_settings_handler; mod edit_indexer_settings_handler;
@@ -70,6 +69,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
|| INDEXERS_BLOCKS.contains(&active_block) || INDEXERS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -168,20 +171,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Indexers => match self.key { ActiveSonarrBlock::Indexers => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.test.key => { _ if matches_key!(test, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::TestIndexer.into()); .push_navigation_stack(ActiveSonarrBlock::TestIndexer.into());
} }
_ if key == DEFAULT_KEYBINDINGS.test_all.key => { _ if matches_key!(test_all, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into()); .push_navigation_stack(ActiveSonarrBlock::TestAllIndexers.into());
} }
_ if key == DEFAULT_KEYBINDINGS.settings.key => { _ if matches_key!(settings, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AllIndexerSettingsPrompt.into());
@@ -191,7 +194,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for IndexersHandler<'a,
_ => (), _ => (),
}, },
ActiveSonarrBlock::DeleteIndexerPrompt => { ActiveSonarrBlock::DeleteIndexerPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id())); Some(SonarrEvent::DeleteIndexer(self.extract_indexer_id()));
@@ -48,6 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for TestAllIndexersHandl
active_block == ActiveSonarrBlock::TestAllIndexers active_block == ActiveSonarrBlock::TestAllIndexers
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -7,6 +7,7 @@ mod tests {
use crate::models::servarr_data::modals::IndexerTestResultModalItem; use crate::models::servarr_data::modals::IndexerTestResultModalItem;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
mod test_handle_esc { mod test_handle_esc {
@@ -48,6 +49,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_test_all_indexers_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = TestAllIndexersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_test_all_indexers_handler_is_not_ready_when_loading() { fn test_test_all_indexers_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::modals::AddSeriesModal; use crate::models::servarr_data::sonarr::modals::AddSeriesModal;
@@ -9,7 +8,9 @@ use crate::models::sonarr_models::{AddSeriesBody, AddSeriesOptions, AddSeriesSea
use crate::models::stateful_table::StatefulTable; use crate::models::stateful_table::StatefulTable;
use crate::models::{BlockSelectionState, Scrollable}; use crate::models::{BlockSelectionState, Scrollable};
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key, App, Key,
};
#[cfg(test)] #[cfg(test)]
#[path = "add_series_handler_tests.rs"] #[path = "add_series_handler_tests.rs"]
@@ -126,6 +127,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ADD_SERIES_BLOCKS.contains(&active_block) ADD_SERIES_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -440,7 +445,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into()); .push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchResults _ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchResults
&& self.app.data.sonarr_data.add_searched_series.is_some() => && self.app.data.sonarr_data.add_searched_series.is_some() =>
@@ -509,7 +514,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
.get_active_block() .get_active_block()
.into(), .into(),
); );
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder => { ActiveSonarrBlock::AddSeriesToggleUseSeasonFolder => {
self self
@@ -538,7 +543,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
| ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(), | ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(),
ActiveSonarrBlock::AddSeriesTagsInput => { ActiveSonarrBlock::AddSeriesTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -549,13 +554,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ActiveSonarrBlock::AddSeriesSearchInput => { ActiveSonarrBlock::AddSeriesSearchInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.sonarr_data.add_series_search = None; self.app.data.sonarr_data.add_series_search = None;
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveSonarrBlock::AddSeriesSearchResults ActiveSonarrBlock::AddSeriesSearchResults
| ActiveSonarrBlock::AddSeriesEmptySearchResults => { | ActiveSonarrBlock::AddSeriesEmptySearchResults => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.sonarr_data.add_searched_series = None; self.app.data.sonarr_data.add_searched_series = None;
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveSonarrBlock::AddSeriesPrompt => { ActiveSonarrBlock::AddSeriesPrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -570,7 +575,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
| ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(), | ActiveSonarrBlock::AddSeriesSelectRootFolder => self.app.pop_navigation_stack(),
ActiveSonarrBlock::AddSeriesTagsInput => { ActiveSonarrBlock::AddSeriesTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -609,7 +614,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
ActiveSonarrBlock::AddSeriesPrompt => { ActiveSonarrBlock::AddSeriesPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block() if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::AddSeriesConfirmPrompt == ActiveSonarrBlock::AddSeriesConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, key)
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -912,7 +913,7 @@ mod tests {
fn test_add_series_search_input_submit() { fn test_add_series_search_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.add_series_search = Some("test".into()); app.data.sonarr_data.add_series_search = Some("test".into());
AddSeriesHandler::new( AddSeriesHandler::new(
@@ -923,7 +924,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::AddSeriesSearchResults.into() ActiveSonarrBlock::AddSeriesSearchResults.into()
@@ -936,7 +937,7 @@ mod tests {
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
AddSeriesHandler::new( AddSeriesHandler::new(
SUBMIT_KEY, SUBMIT_KEY,
@@ -946,7 +947,7 @@ mod tests {
) )
.handle(); .handle();
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::AddSeriesSearchInput.into() ActiveSonarrBlock::AddSeriesSearchInput.into()
@@ -1230,7 +1231,7 @@ mod tests {
assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); assert_eq!(app.data.sonarr_data.prompt_confirm_action, None);
if selected_block == ActiveSonarrBlock::AddSeriesTagsInput { if selected_block == ActiveSonarrBlock::AddSeriesTagsInput {
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
} }
@@ -1259,7 +1260,7 @@ mod tests {
); );
if active_sonarr_block == ActiveSonarrBlock::AddSeriesTagsInput { if active_sonarr_block == ActiveSonarrBlock::AddSeriesTagsInput {
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
} }
@@ -1336,7 +1337,7 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.is_loading = is_ready; app.is_loading = is_ready;
app.data.sonarr_data = create_test_sonarr_data(); app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
@@ -1348,7 +1349,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert_eq!(app.data.sonarr_data.add_series_search, None); assert_eq!(app.data.sonarr_data.add_series_search, None);
} }
@@ -1357,7 +1358,7 @@ mod tests {
fn test_add_series_input_esc() { fn test_add_series_input_esc() {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.sonarr_data = create_test_sonarr_data(); app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into());
@@ -1370,7 +1371,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::AddSeriesPrompt.into() ActiveSonarrBlock::AddSeriesPrompt.into()
@@ -1403,7 +1404,7 @@ mod tests {
ActiveSonarrBlock::AddSeriesSearchInput.into() ActiveSonarrBlock::AddSeriesSearchInput.into()
); );
assert!(app.data.sonarr_data.add_searched_series.is_none()); assert!(app.data.sonarr_data.add_searched_series.is_none());
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
#[test] #[test]
@@ -1451,7 +1452,7 @@ mod tests {
fn test_add_series_tags_input_esc() { fn test_add_series_tags_input_esc() {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.sonarr_data = create_test_sonarr_data(); app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into()); app.push_navigation_stack(ActiveSonarrBlock::AddSeriesTagsInput.into());
@@ -1464,7 +1465,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::AddSeriesPrompt.into() ActiveSonarrBlock::AddSeriesPrompt.into()
@@ -1570,7 +1571,7 @@ mod tests {
app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
AddSeriesHandler::new( AddSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::AddSeriesSearchInput, ActiveSonarrBlock::AddSeriesSearchInput,
None, None,
@@ -1585,7 +1586,7 @@ mod tests {
.as_ref() .as_ref()
.unwrap() .unwrap()
.text, .text,
"h" "a"
); );
} }
@@ -1596,7 +1597,7 @@ mod tests {
app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default()); app.data.sonarr_data.add_series_modal = Some(AddSeriesModal::default());
AddSeriesHandler::new( AddSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::AddSeriesTagsInput, ActiveSonarrBlock::AddSeriesTagsInput,
None, None,
@@ -1612,7 +1613,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .text,
"h" "a"
); );
} }
@@ -1714,6 +1715,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_add_series_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = AddSeriesHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_add_series_search_no_panic_on_none_search_result() { fn test_add_series_search_no_panic_on_none_search_result() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,9 +1,10 @@
use crate::models::sonarr_models::DeleteSeriesParams; use crate::models::sonarr_models::DeleteSeriesParams;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{ use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App}, app::App,
event::Key, event::Key,
handlers::{handle_prompt_toggle, KeyEventHandler}, handlers::{handle_prompt_toggle, KeyEventHandler},
matches_key,
models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS}, models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, DELETE_SERIES_BLOCKS},
}; };
@@ -38,6 +39,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
DELETE_SERIES_BLOCKS.contains(&active_block) DELETE_SERIES_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -123,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for DeleteSeriesHandler<
if self.active_sonarr_block == ActiveSonarrBlock::DeleteSeriesPrompt if self.active_sonarr_block == ActiveSonarrBlock::DeleteSeriesPrompt
&& self.app.data.sonarr_data.selected_block.get_active_block() && self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::DeleteSeriesConfirmPrompt == ActiveSonarrBlock::DeleteSeriesConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -320,6 +321,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_delete_series_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = DeleteSeriesHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_delete_series_params() { fn test_build_delete_series_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -7,7 +6,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EDIT_S
use crate::models::sonarr_models::EditSeriesParams; use crate::models::sonarr_models::EditSeriesParams;
use crate::models::Scrollable; use crate::models::Scrollable;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "edit_series_handler_tests.rs"] #[path = "edit_series_handler_tests.rs"]
@@ -83,6 +82,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
EDIT_SERIES_BLOCKS.contains(&active_block) EDIT_SERIES_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -342,7 +345,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
) )
.into(), .into(),
); );
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
ActiveSonarrBlock::EditSeriesToggleMonitored => { ActiveSonarrBlock::EditSeriesToggleMonitored => {
self self
@@ -392,7 +395,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
| ActiveSonarrBlock::EditSeriesSelectLanguageProfile => self.app.pop_navigation_stack(), | ActiveSonarrBlock::EditSeriesSelectLanguageProfile => self.app.pop_navigation_stack(),
ActiveSonarrBlock::EditSeriesPathInput | ActiveSonarrBlock::EditSeriesTagsInput => { ActiveSonarrBlock::EditSeriesPathInput | ActiveSonarrBlock::EditSeriesTagsInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
_ => (), _ => (),
} }
@@ -402,7 +405,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::EditSeriesTagsInput | ActiveSonarrBlock::EditSeriesPathInput => { ActiveSonarrBlock::EditSeriesTagsInput | ActiveSonarrBlock::EditSeriesPathInput => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveSonarrBlock::EditSeriesPrompt => { ActiveSonarrBlock::EditSeriesPrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -450,7 +453,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EditSeriesHandler<'a
ActiveSonarrBlock::EditSeriesPrompt => { ActiveSonarrBlock::EditSeriesPrompt => {
if self.app.data.sonarr_data.selected_block.get_active_block() if self.app.data.sonarr_data.selected_block.get_active_block()
== ActiveSonarrBlock::EditSeriesConfirmPrompt == ActiveSonarrBlock::EditSeriesConfirmPrompt
&& key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, key)
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -2,6 +2,7 @@
mod tests { mod tests {
use bimap::BiMap; use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -677,7 +678,7 @@ mod tests {
#[test] #[test]
fn test_edit_series_path_input_submit() { fn test_edit_series_path_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal { app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal {
path: "Test Path".into(), path: "Test Path".into(),
..EditSeriesModal::default() ..EditSeriesModal::default()
@@ -694,7 +695,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -713,7 +714,7 @@ mod tests {
#[test] #[test]
fn test_edit_series_tags_input_submit() { fn test_edit_series_tags_input_submit() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal { app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal {
tags: "Test Tags".into(), tags: "Test Tags".into(),
..EditSeriesModal::default() ..EditSeriesModal::default()
@@ -730,7 +731,7 @@ mod tests {
) )
.handle(); .handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(!app assert!(!app
.data .data
.sonarr_data .sonarr_data
@@ -1010,7 +1011,7 @@ mod tests {
if selected_block == ActiveSonarrBlock::EditSeriesPathInput if selected_block == ActiveSonarrBlock::EditSeriesPathInput
|| selected_block == ActiveSonarrBlock::EditSeriesTagsInput || selected_block == ActiveSonarrBlock::EditSeriesTagsInput
{ {
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
} }
} }
@@ -1049,7 +1050,7 @@ mod tests {
.into() .into()
); );
assert_eq!(app.data.sonarr_data.prompt_confirm_action, None); assert_eq!(app.data.sonarr_data.prompt_confirm_action, None);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
#[rstest] #[rstest]
@@ -1085,7 +1086,7 @@ mod tests {
if active_sonarr_block == ActiveSonarrBlock::EditSeriesPathInput if active_sonarr_block == ActiveSonarrBlock::EditSeriesPathInput
|| active_sonarr_block == ActiveSonarrBlock::EditSeriesTagsInput || active_sonarr_block == ActiveSonarrBlock::EditSeriesTagsInput
{ {
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
} }
} }
@@ -1111,14 +1112,14 @@ mod tests {
) { ) {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.sonarr_data = create_test_sonarr_data(); app.data.sonarr_data = create_test_sonarr_data();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::Series.into()); app.push_navigation_stack(ActiveSonarrBlock::Series.into());
app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::EditSeriesPrompt.into());
app.push_navigation_stack(active_sonarr_block.into()); app.push_navigation_stack(active_sonarr_block.into());
EditSeriesHandler::new(ESC_KEY, &mut app, active_sonarr_block, None).handle(); EditSeriesHandler::new(ESC_KEY, &mut app, active_sonarr_block, None).handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::EditSeriesPrompt.into() ActiveSonarrBlock::EditSeriesPrompt.into()
@@ -1243,7 +1244,7 @@ mod tests {
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default()); app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
EditSeriesHandler::new( EditSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditSeriesPathInput, ActiveSonarrBlock::EditSeriesPathInput,
None, None,
@@ -1259,7 +1260,7 @@ mod tests {
.unwrap() .unwrap()
.path .path
.text, .text,
"h" "a"
); );
} }
@@ -1270,7 +1271,7 @@ mod tests {
app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default()); app.data.sonarr_data.edit_series_modal = Some(EditSeriesModal::default());
EditSeriesHandler::new( EditSeriesHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::EditSeriesTagsInput, ActiveSonarrBlock::EditSeriesTagsInput,
None, None,
@@ -1286,7 +1287,7 @@ mod tests {
.unwrap() .unwrap()
.tags .tags
.text, .text,
"h" "a"
); );
} }
@@ -1368,6 +1369,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_edit_series_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EditSeriesHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_build_edit_series_params() { fn test_build_edit_series_params() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,13 +1,12 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options; use crate::handlers::sonarr_handlers::library::season_details_handler::releases_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, EPISODE_DETAILS_BLOCKS};
use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody}; use crate::models::sonarr_models::{SonarrHistoryItem, SonarrRelease, SonarrReleaseDownloadBody};
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "episode_details_handler_tests.rs"] #[path = "episode_details_handler_tests.rs"]
@@ -88,6 +87,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
EPISODE_DETAILS_BLOCKS.contains(&active_block) EPISODE_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -142,7 +145,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
| ActiveSonarrBlock::EpisodeHistory | ActiveSonarrBlock::EpisodeHistory
| ActiveSonarrBlock::EpisodeFile | ActiveSonarrBlock::EpisodeFile
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key { | ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, self.key) => {
self self
.app .app
.data .data
@@ -170,7 +173,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
.get_active_route(), .get_active_route(),
); );
} }
_ if self.key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, self.key) => {
self self
.app .app
.data .data
@@ -306,21 +309,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
| ActiveSonarrBlock::EpisodeHistory | ActiveSonarrBlock::EpisodeHistory
| ActiveSonarrBlock::EpisodeFile | ActiveSonarrBlock::EpisodeFile
| ActiveSonarrBlock::ManualEpisodeSearch => match self.key { | ActiveSonarrBlock::ManualEpisodeSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, self.key) => {
self self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()); .pop_and_push_navigation_stack(self.active_sonarr_block.into());
} }
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, self.key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchEpisodePrompt.into());
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::AutomaticallySearchEpisodePrompt ActiveSonarrBlock::AutomaticallySearchEpisodePrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()), SonarrEvent::TriggerAutomaticEpisodeSearch(self.extract_episode_id()),
@@ -328,9 +329,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for EpisodeDetailsHandle
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
if self.app.data.sonarr_data.prompt_confirm { if self.app.data.sonarr_data.prompt_confirm {
let SonarrRelease { let SonarrRelease {
guid, indexer_id, .. guid, indexer_id, ..
@@ -625,6 +625,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_episode_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = EpisodeDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_episode_id() { fn test_extract_episode_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -316,7 +316,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::AddSeriesSearchInput.into() ActiveSonarrBlock::AddSeriesSearchInput.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.add_series_search.is_some()); assert!(app.data.sonarr_data.add_series_search.is_some());
} }
@@ -340,7 +340,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into()); assert_eq!(app.get_current_route(), ActiveSonarrBlock::Series.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.add_series_search.is_none()); assert!(app.data.sonarr_data.add_series_search.is_none());
} }
@@ -827,6 +827,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_library_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = LibraryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_library_handler_not_ready_when_loading() { fn test_library_handler_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+11 -7
View File
@@ -8,6 +8,7 @@ use crate::{
event::Key, event::Key,
handle_table_events, handle_table_events,
handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}, handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler},
matches_key,
models::{ models::{
servarr_data::sonarr::sonarr_data::{ servarr_data::sonarr::sonarr_data::{
ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS, ActiveSonarrBlock, DELETE_SERIES_SELECTION_BLOCKS, EDIT_SERIES_SELECTION_BLOCKS,
@@ -21,7 +22,6 @@ use crate::{
}; };
use super::handle_change_tab_left_right_keys; use super::handle_change_tab_left_right_keys;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeDetailsHandler; use crate::handlers::sonarr_handlers::library::episode_details_handler::EpisodeDetailsHandler;
use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler; use crate::handlers::sonarr_handlers::library::season_details_handler::SeasonDetailsHandler;
use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler; use crate::handlers::sonarr_handlers::library::series_details_handler::SeriesDetailsHandler;
@@ -102,6 +102,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
|| LIBRARY_BLOCKS.contains(&active_block) || LIBRARY_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -182,7 +186,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::Series => match self.key { ActiveSonarrBlock::Series => match self.key {
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
@@ -194,25 +198,25 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for LibraryHandler<'a, '
self.app.data.sonarr_data.selected_block = self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into()); .push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchInput.into());
self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default()); self.app.data.sonarr_data.add_series_search = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateAllSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::UpdateAllSeriesPrompt => { ActiveSonarrBlock::UpdateAllSeriesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries); self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::UpdateAllSeries);
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::history::history_sorting_options; use crate::handlers::sonarr_handlers::history::history_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -12,6 +10,7 @@ use crate::models::sonarr_models::{
}; };
use crate::models::stateful_table::SortOption; use crate::models::stateful_table::SortOption;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
use serde_json::Number; use serde_json::Number;
#[cfg(test)] #[cfg(test)]
@@ -140,6 +139,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
SEASON_DETAILS_BLOCKS.contains(&active_block) SEASON_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -193,7 +196,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
ActiveSonarrBlock::SeasonDetails ActiveSonarrBlock::SeasonDetails
| ActiveSonarrBlock::SeasonHistory | ActiveSonarrBlock::SeasonHistory
| ActiveSonarrBlock::ManualSeasonSearch => match self.key { | ActiveSonarrBlock::ManualSeasonSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, self.key) => {
self self
.app .app
.data .data
@@ -215,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
.get_active_route(), .get_active_route(),
); );
} }
_ if self.key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, self.key) => {
self self
.app .app
.data .data
@@ -378,7 +381,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SeasonDetails if self.key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => { ActiveSonarrBlock::SeasonDetails if matches_key!(toggle_monitoring, self.key) => {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()), SonarrEvent::ToggleEpisodeMonitoring(self.extract_episode_id()),
@@ -391,21 +394,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
ActiveSonarrBlock::SeasonDetails ActiveSonarrBlock::SeasonDetails
| ActiveSonarrBlock::SeasonHistory | ActiveSonarrBlock::SeasonHistory
| ActiveSonarrBlock::ManualSeasonSearch => match self.key { | ActiveSonarrBlock::ManualSeasonSearch => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, self.key) => {
self self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()); .pop_and_push_navigation_stack(self.active_sonarr_block.into());
} }
_ if self.key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, self.key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeasonPrompt.into());
} }
_ => (), _ => (),
}, },
ActiveSonarrBlock::AutomaticallySearchSeasonPrompt ActiveSonarrBlock::AutomaticallySearchSeasonPrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()), SonarrEvent::TriggerAutomaticSeasonSearch(self.extract_series_id_season_number_tuple()),
@@ -413,7 +414,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::DeleteEpisodeFilePrompt if key == DEFAULT_KEYBINDINGS.confirm.key => { ActiveSonarrBlock::DeleteEpisodeFilePrompt if matches_key!(confirm, key) => {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile( self.app.data.sonarr_data.prompt_confirm_action = Some(SonarrEvent::DeleteEpisodeFile(
self.extract_episode_file_id(), self.extract_episode_file_id(),
@@ -421,9 +422,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeasonDetailsHandler
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt if matches_key!(confirm, key) => {
if key == DEFAULT_KEYBINDINGS.confirm.key =>
{
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
let SonarrRelease { let SonarrRelease {
guid, indexer_id, .. guid, indexer_id, ..
@@ -789,6 +789,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_season_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SeasonDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_episode_file_id() { fn test_extract_episode_file_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -1,7 +1,5 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handle_table_events;
use crate::handlers::sonarr_handlers::history::history_sorting_options; use crate::handlers::sonarr_handlers::history::history_sorting_options;
use crate::handlers::table_handler::TableHandlingConfig; use crate::handlers::table_handler::TableHandlingConfig;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
@@ -11,6 +9,7 @@ use crate::models::servarr_data::sonarr::sonarr_data::{
use crate::models::sonarr_models::{Season, SonarrHistoryItem}; use crate::models::sonarr_models::{Season, SonarrHistoryItem};
use crate::models::BlockSelectionState; use crate::models::BlockSelectionState;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, matches_key};
#[cfg(test)] #[cfg(test)]
#[path = "series_details_handler_tests.rs"] #[path = "series_details_handler_tests.rs"]
@@ -91,6 +90,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
SERIES_DETAILS_BLOCKS.contains(&active_block) SERIES_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -130,7 +133,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
fn handle_left_right_action(&mut self) { fn handle_left_right_action(&mut self) {
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SeriesDetails | ActiveSonarrBlock::SeriesHistory => match self.key { ActiveSonarrBlock::SeriesDetails | ActiveSonarrBlock::SeriesHistory => match self.key {
_ if self.key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, self.key) => {
self.app.data.sonarr_data.series_info_tabs.previous(); self.app.data.sonarr_data.series_info_tabs.previous();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self self
@@ -141,7 +144,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
.get_active_route(), .get_active_route(),
); );
} }
_ if self.key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, self.key) => {
self.app.data.sonarr_data.series_info_tabs.next(); self.app.data.sonarr_data.series_info_tabs.next();
self.app.pop_and_push_navigation_stack( self.app.pop_and_push_navigation_stack(
self self
@@ -250,20 +253,20 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SeriesDetails => match self.key { ActiveSonarrBlock::SeriesDetails => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self _ if matches_key!(refresh, key) => self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()), .pop_and_push_navigation_stack(self.active_sonarr_block.into()),
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
@@ -275,7 +278,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
self.app.data.sonarr_data.selected_block = self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.toggle_monitoring.key => { _ if matches_key!(toggle_monitoring, key) => {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()), SonarrEvent::ToggleSeasonMonitoring(self.extract_series_id_season_number_tuple()),
@@ -288,15 +291,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
_ => (), _ => (),
}, },
ActiveSonarrBlock::SeriesHistory => match self.key { ActiveSonarrBlock::SeriesHistory => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => self _ if matches_key!(refresh, key) => self
.app .app
.pop_and_push_navigation_stack(self.active_sonarr_block.into()), .pop_and_push_navigation_stack(self.active_sonarr_block.into()),
_ if key == DEFAULT_KEYBINDINGS.auto_search.key => { _ if matches_key!(auto_search, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AutomaticallySearchSeriesPrompt.into());
} }
_ if key == DEFAULT_KEYBINDINGS.edit.key => { _ if matches_key!(edit, key) => {
self.app.push_navigation_stack( self.app.push_navigation_stack(
( (
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
@@ -308,7 +311,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
self.app.data.sonarr_data.selected_block = self.app.data.sonarr_data.selected_block =
BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS); BlockSelectionState::new(EDIT_SERIES_SELECTION_BLOCKS);
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::UpdateAndScanSeriesPrompt.into());
@@ -316,7 +319,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
_ => (), _ => (),
}, },
ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => { ActiveSonarrBlock::AutomaticallySearchSeriesPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = Some( self.app.data.sonarr_data.prompt_confirm_action = Some(
SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()), SonarrEvent::TriggerAutomaticSeriesSearch(self.extract_series_id()),
@@ -611,6 +611,25 @@ mod tests {
}); });
} }
#[rstest]
fn test_series_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SeriesDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_series_id_season_number_tuple() { fn test_extract_series_id_season_number_tuple() {
let mut app = App::test_default(); let mut app = App::test_default();
+7 -5
View File
@@ -7,9 +7,7 @@ use root_folders::RootFoldersHandler;
use system::SystemHandler; use system::SystemHandler;
use crate::{ use crate::{
app::{key_binding::DEFAULT_KEYBINDINGS, App}, app::App, event::Key, matches_key, models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
event::Key,
models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock,
}; };
use super::KeyEventHandler; use super::KeyEventHandler;
@@ -69,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
true true
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -113,11 +115,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SonarrHandler<'a, 'b
pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) { pub fn handle_change_tab_left_right_keys(app: &mut App<'_>, key: Key) {
let key_ref = key; let key_ref = key;
match key_ref { match key_ref {
_ if key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, key, app.ignore_special_keys_for_textbox_input) => {
app.data.sonarr_data.main_tabs.previous(); app.data.sonarr_data.main_tabs.previous();
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route());
} }
_ if key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, key, app.ignore_special_keys_for_textbox_input) => {
app.data.sonarr_data.main_tabs.next(); app.data.sonarr_data.main_tabs.next();
app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route()); app.pop_and_push_navigation_stack(app.data.sonarr_data.main_tabs.get_active_route());
} }
@@ -1,4 +1,3 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
@@ -8,7 +7,9 @@ use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, ROOT_F
use crate::models::servarr_models::{AddRootFolderBody, RootFolder}; use crate::models::servarr_models::{AddRootFolderBody, RootFolder};
use crate::models::HorizontallyScrollableText; use crate::models::HorizontallyScrollableText;
use crate::network::sonarr_network::SonarrEvent; use crate::network::sonarr_network::SonarrEvent;
use crate::{handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys}; use crate::{
handle_table_events, handle_text_box_keys, handle_text_box_left_right_keys, matches_key,
};
#[cfg(test)] #[cfg(test)]
#[path = "root_folders_handler_tests.rs"] #[path = "root_folders_handler_tests.rs"]
@@ -66,6 +67,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
ROOT_FOLDERS_BLOCKS.contains(&active_block) ROOT_FOLDERS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -166,7 +171,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
self.build_add_root_folder_body(), self.build_add_root_folder_body(),
)); ));
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
} }
_ => (), _ => (),
@@ -179,7 +184,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
self.app.data.sonarr_data.edit_root_folder = None; self.app.data.sonarr_data.edit_root_folder = None;
self.app.data.sonarr_data.prompt_confirm = false; self.app.data.sonarr_data.prompt_confirm = false;
self.app.should_ignore_quit_key = false; self.app.ignore_special_keys_for_textbox_input = false;
} }
ActiveSonarrBlock::DeleteRootFolderPrompt => { ActiveSonarrBlock::DeleteRootFolderPrompt => {
self.app.pop_navigation_stack(); self.app.pop_navigation_stack();
@@ -193,15 +198,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
let key = self.key; let key = self.key;
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::RootFolders => match self.key { ActiveSonarrBlock::RootFolders => match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.add.key => { _ if matches_key!(add, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into()); .push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
self.app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); self.app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
self.app.should_ignore_quit_key = true; self.app.ignore_special_keys_for_textbox_input = true;
} }
_ => (), _ => (),
}, },
@@ -213,7 +218,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for RootFoldersHandler<'
) )
} }
ActiveSonarrBlock::DeleteRootFolderPrompt => { ActiveSonarrBlock::DeleteRootFolderPrompt => {
if key == DEFAULT_KEYBINDINGS.confirm.key { if matches_key!(confirm, key) {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id())); Some(SonarrEvent::DeleteRootFolder(self.extract_root_folder_id()));
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -269,7 +270,7 @@ mod tests {
.set_items(vec![RootFolder::default()]); .set_items(vec![RootFolder::default()]);
app.data.sonarr_data.edit_root_folder = Some("Test".into()); app.data.sonarr_data.edit_root_folder = Some("Test".into());
app.data.sonarr_data.prompt_confirm = true; app.data.sonarr_data.prompt_confirm = true;
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
@@ -282,7 +283,7 @@ mod tests {
.handle(); .handle();
assert!(app.data.sonarr_data.prompt_confirm); assert!(app.data.sonarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.sonarr_data.prompt_confirm_action, app.data.sonarr_data.prompt_confirm_action,
Some(SonarrEvent::AddRootFolder(expected_add_root_folder_body)) Some(SonarrEvent::AddRootFolder(expected_add_root_folder_body))
@@ -299,7 +300,7 @@ mod tests {
let mut app = App::test_default(); let mut app = App::test_default();
app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
app.data.sonarr_data.prompt_confirm = false; app.data.sonarr_data.prompt_confirm = false;
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
@@ -312,7 +313,7 @@ mod tests {
.handle(); .handle();
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.prompt_confirm_action.is_none()); assert!(app.data.sonarr_data.prompt_confirm_action.is_none());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -414,7 +415,7 @@ mod tests {
app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveSonarrBlock::RootFolders.into());
app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into()); app.push_navigation_stack(ActiveSonarrBlock::AddRootFolderPrompt.into());
app.data.sonarr_data.edit_root_folder = Some("/nfs/test".into()); app.data.sonarr_data.edit_root_folder = Some("/nfs/test".into());
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
RootFoldersHandler::new( RootFoldersHandler::new(
ESC_KEY, ESC_KEY,
@@ -431,7 +432,7 @@ mod tests {
assert!(app.data.sonarr_data.edit_root_folder.is_none()); assert!(app.data.sonarr_data.edit_root_folder.is_none());
assert!(!app.data.sonarr_data.prompt_confirm); assert!(!app.data.sonarr_data.prompt_confirm);
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
} }
#[rstest] #[rstest]
@@ -481,7 +482,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::AddRootFolderPrompt.into() ActiveSonarrBlock::AddRootFolderPrompt.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.edit_root_folder.is_some()); assert!(app.data.sonarr_data.edit_root_folder.is_some());
} }
@@ -508,7 +509,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveSonarrBlock::RootFolders.into() ActiveSonarrBlock::RootFolders.into()
); );
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.sonarr_data.edit_root_folder.is_none()); assert!(app.data.sonarr_data.edit_root_folder.is_none());
} }
@@ -600,7 +601,7 @@ mod tests {
app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); app.data.sonarr_data.edit_root_folder = Some(HorizontallyScrollableText::default());
RootFoldersHandler::new( RootFoldersHandler::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveSonarrBlock::AddRootFolderPrompt, ActiveSonarrBlock::AddRootFolderPrompt,
None, None,
@@ -609,7 +610,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.sonarr_data.edit_root_folder.as_ref().unwrap().text, app.data.sonarr_data.edit_root_folder.as_ref().unwrap().text,
"h" "a"
); );
} }
@@ -655,6 +656,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_root_folders_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = RootFoldersHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_root_folder_id() { fn test_extract_root_folder_id() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -297,7 +297,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
audio_stream_count: 1, audio_stream_count: 1,
video_bit_depth: 10, video_bit_depth: 10,
video_bitrate: 0, video_bitrate: 0,
video_codec: "x265".to_owned(), video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(), video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x1080".to_owned(), resolution: "1920x1080".to_owned(),
run_time: "23:51".to_owned(), run_time: "23:51".to_owned(),
@@ -327,7 +327,7 @@ pub(in crate::handlers::sonarr_handlers) mod utils {
title: None, title: None,
season_number: 1, season_number: 1,
monitored: true, monitored: true,
statistics: season_statistics(), statistics: Some(season_statistics()),
} }
} }
@@ -9,6 +9,7 @@ mod tests {
use crate::test_handler_delegation; use crate::test_handler_delegation;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use rstest::rstest; use rstest::rstest;
use strum::IntoEnumIterator;
#[rstest] #[rstest]
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)] #[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
@@ -45,6 +46,77 @@ mod tests {
assert_eq!(app.get_current_route(), right_block.into()); assert_eq!(app.get_current_route(), right_block.into());
} }
#[rstest]
#[case(0, ActiveSonarrBlock::System, ActiveSonarrBlock::Downloads)]
#[case(1, ActiveSonarrBlock::Series, ActiveSonarrBlock::Blocklist)]
#[case(2, ActiveSonarrBlock::Downloads, ActiveSonarrBlock::History)]
#[case(3, ActiveSonarrBlock::Blocklist, ActiveSonarrBlock::RootFolders)]
#[case(4, ActiveSonarrBlock::History, ActiveSonarrBlock::Indexers)]
#[case(5, ActiveSonarrBlock::RootFolders, ActiveSonarrBlock::System)]
#[case(6, ActiveSonarrBlock::Indexers, ActiveSonarrBlock::Series)]
fn test_sonarr_handler_change_tab_left_right_keys_alt_navigation(
#[case] index: usize,
#[case] left_block: ActiveSonarrBlock,
#[case] right_block: ActiveSonarrBlock,
) {
let mut app = App::test_default();
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
left_block.into()
);
assert_eq!(app.get_current_route(), left_block.into());
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
right_block.into()
);
assert_eq!(app.get_current_route(), right_block.into());
}
#[rstest]
#[case(0, ActiveSonarrBlock::Series)]
#[case(1, ActiveSonarrBlock::Downloads)]
#[case(2, ActiveSonarrBlock::Blocklist)]
#[case(3, ActiveSonarrBlock::History)]
#[case(4, ActiveSonarrBlock::RootFolders)]
#[case(5, ActiveSonarrBlock::Indexers)]
#[case(6, ActiveSonarrBlock::System)]
fn test_sonarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
#[case] index: usize,
#[case] block: ActiveSonarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(block.into());
app.ignore_special_keys_for_textbox_input = true;
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.left.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
app.data.sonarr_data.main_tabs.set_index(index);
handle_change_tab_left_right_keys(&mut app, DEFAULT_KEYBINDINGS.right.alt.unwrap());
assert_eq!(
app.data.sonarr_data.main_tabs.get_active_route(),
block.into()
);
assert_eq!(app.get_current_route(), block.into());
}
#[rstest] #[rstest]
fn test_delegates_library_blocks_to_library_handler( fn test_delegates_library_blocks_to_library_handler(
#[values( #[values(
@@ -59,10 +131,10 @@ mod tests {
ActiveSonarrBlock::AddSeriesSelectRootFolder, ActiveSonarrBlock::AddSeriesSelectRootFolder,
ActiveSonarrBlock::AddSeriesSelectSeriesType, ActiveSonarrBlock::AddSeriesSelectSeriesType,
ActiveSonarrBlock::AddSeriesTagsInput, ActiveSonarrBlock::AddSeriesTagsInput,
// ActiveSonarrBlock::AutomaticallySearchEpisodePrompt, ActiveSonarrBlock::AutomaticallySearchEpisodePrompt,
// ActiveSonarrBlock::AutomaticallySearchSeasonPrompt, ActiveSonarrBlock::AutomaticallySearchSeasonPrompt,
// ActiveSonarrBlock::AutomaticallySearchSeriesPrompt, ActiveSonarrBlock::AutomaticallySearchSeriesPrompt,
// ActiveSonarrBlock::DeleteEpisodeFilePrompt, ActiveSonarrBlock::DeleteEpisodeFilePrompt,
ActiveSonarrBlock::DeleteSeriesPrompt, ActiveSonarrBlock::DeleteSeriesPrompt,
ActiveSonarrBlock::EditSeriesPrompt, ActiveSonarrBlock::EditSeriesPrompt,
ActiveSonarrBlock::EditSeriesPathInput, ActiveSonarrBlock::EditSeriesPathInput,
@@ -70,39 +142,36 @@ mod tests {
ActiveSonarrBlock::EditSeriesSelectQualityProfile, ActiveSonarrBlock::EditSeriesSelectQualityProfile,
ActiveSonarrBlock::EditSeriesSelectLanguageProfile, ActiveSonarrBlock::EditSeriesSelectLanguageProfile,
ActiveSonarrBlock::EditSeriesTagsInput, ActiveSonarrBlock::EditSeriesTagsInput,
// ActiveSonarrBlock::EpisodeDetails, ActiveSonarrBlock::EpisodeDetails,
// ActiveSonarrBlock::EpisodeFile, ActiveSonarrBlock::EpisodeFile,
// ActiveSonarrBlock::EpisodeHistory, ActiveSonarrBlock::EpisodeHistory,
// ActiveSonarrBlock::EpisodesSortPrompt,
// ActiveSonarrBlock::FilterEpisodes,
// ActiveSonarrBlock::FilterEpisodesError,
ActiveSonarrBlock::FilterSeries, ActiveSonarrBlock::FilterSeries,
ActiveSonarrBlock::FilterSeriesError, ActiveSonarrBlock::FilterSeriesError,
// ActiveSonarrBlock::FilterSeriesHistory, ActiveSonarrBlock::FilterSeriesHistory,
// ActiveSonarrBlock::FilterSeriesHistoryError, ActiveSonarrBlock::FilterSeriesHistoryError,
// ActiveSonarrBlock::ManualEpisodeSearch, ActiveSonarrBlock::ManualEpisodeSearch,
// ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt, ActiveSonarrBlock::ManualEpisodeSearchConfirmPrompt,
// ActiveSonarrBlock::ManualEpisodeSearchSortPrompt, ActiveSonarrBlock::ManualEpisodeSearchSortPrompt,
// ActiveSonarrBlock::ManualSeasonSearch, ActiveSonarrBlock::ManualSeasonSearch,
// ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt, ActiveSonarrBlock::ManualSeasonSearchConfirmPrompt,
// ActiveSonarrBlock::ManualSeasonSearchSortPrompt, ActiveSonarrBlock::ManualSeasonSearchSortPrompt,
// ActiveSonarrBlock::SearchEpisodes, ActiveSonarrBlock::SearchEpisodes,
// ActiveSonarrBlock::SearchEpisodesError, ActiveSonarrBlock::SearchEpisodesError,
// ActiveSonarrBlock::SearchSeason, ActiveSonarrBlock::SearchSeason,
// ActiveSonarrBlock::SearchSeasonError, ActiveSonarrBlock::SearchSeasonError,
ActiveSonarrBlock::SearchSeries, ActiveSonarrBlock::SearchSeries,
ActiveSonarrBlock::SearchSeriesError, ActiveSonarrBlock::SearchSeriesError,
// ActiveSonarrBlock::SearchSeriesHistory, ActiveSonarrBlock::SearchSeriesHistory,
// ActiveSonarrBlock::SearchSeriesHistoryError, ActiveSonarrBlock::SearchSeriesHistoryError,
// ActiveSonarrBlock::SeasonDetails, ActiveSonarrBlock::SeasonDetails,
ActiveSonarrBlock::Series, ActiveSonarrBlock::Series,
// ActiveSonarrBlock::SeriesDetails, ActiveSonarrBlock::SeriesDetails,
// ActiveSonarrBlock::SeriesHistory, ActiveSonarrBlock::SeriesHistory,
// ActiveSonarrBlock::SeriesHistorySortPrompt, ActiveSonarrBlock::SeriesHistorySortPrompt,
ActiveSonarrBlock::SeriesSortPrompt, ActiveSonarrBlock::SeriesSortPrompt,
ActiveSonarrBlock::UpdateAllSeriesPrompt, ActiveSonarrBlock::UpdateAllSeriesPrompt,
// ActiveSonarrBlock::UpdateAndScanSeriesPrompt ActiveSonarrBlock::UpdateAndScanSeriesPrompt,
// ActiveSonarrBlock::SeriesHistoryDetails, ActiveSonarrBlock::SeriesHistoryDetails
)] )]
active_sonarr_block: ActiveSonarrBlock, active_sonarr_block: ActiveSonarrBlock,
) { ) {
@@ -222,4 +291,45 @@ mod tests {
active_sonarr_block active_sonarr_block
); );
} }
#[test]
fn test_sonarr_handler_accepts() {
ActiveSonarrBlock::iter().for_each(|active_sonarr_block| {
assert!(SonarrHandler::accepts(active_sonarr_block));
})
}
#[rstest]
fn test_sonarr_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SonarrHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_sonarr_handler_is_ready() {
let mut app = App::test_default();
app.is_loading = true;
let handler = SonarrHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert!(handler.is_ready());
}
} }
+10 -6
View File
@@ -1,9 +1,9 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys; use crate::handlers::sonarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::sonarr_handlers::system::system_details_handler::SystemDetailsHandler;
use crate::handlers::{handle_clear_errors, KeyEventHandler}; use crate::handlers::{handle_clear_errors, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
use crate::models::Scrollable; use crate::models::Scrollable;
@@ -35,6 +35,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
SystemDetailsHandler::accepts(active_block) || active_block == ActiveSonarrBlock::System SystemDetailsHandler::accepts(active_block) || active_block == ActiveSonarrBlock::System
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -86,15 +90,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
if self.active_sonarr_block == ActiveSonarrBlock::System { if self.active_sonarr_block == ActiveSonarrBlock::System {
let key = self.key; let key = self.key;
match self.key { match self.key {
_ if key == DEFAULT_KEYBINDINGS.refresh.key => { _ if matches_key!(refresh, key) => {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
_ if key == DEFAULT_KEYBINDINGS.events.key => { _ if matches_key!(events, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into()); .push_navigation_stack(ActiveSonarrBlock::SystemQueuedEvents.into());
} }
_ if key == DEFAULT_KEYBINDINGS.logs.key => { _ if matches_key!(logs, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemLogs.into()); .push_navigation_stack(ActiveSonarrBlock::SystemLogs.into());
@@ -106,12 +110,12 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemHandler<'a, 'b
.set_items(self.app.data.sonarr_data.logs.items.to_vec()); .set_items(self.app.data.sonarr_data.logs.items.to_vec());
self.app.data.sonarr_data.log_details.scroll_to_bottom(); self.app.data.sonarr_data.log_details.scroll_to_bottom();
} }
_ if key == DEFAULT_KEYBINDINGS.tasks.key => { _ if matches_key!(tasks, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemTasks.into()); .push_navigation_stack(ActiveSonarrBlock::SystemTasks.into());
} }
_ if key == DEFAULT_KEYBINDINGS.update.key => { _ if matches_key!(update, key) => {
self self
.app .app
.push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into()); .push_navigation_stack(ActiveSonarrBlock::SystemUpdates.into());
@@ -1,7 +1,7 @@
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::App; use crate::app::App;
use crate::event::Key; use crate::event::Key;
use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
use crate::matches_key;
use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS}; use crate::models::servarr_data::sonarr::sonarr_data::{ActiveSonarrBlock, SYSTEM_DETAILS_BLOCKS};
use crate::models::sonarr_models::SonarrTaskName; use crate::models::sonarr_models::SonarrTaskName;
use crate::models::stateful_list::StatefulList; use crate::models::stateful_list::StatefulList;
@@ -36,6 +36,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
SYSTEM_DETAILS_BLOCKS.contains(&active_block) SYSTEM_DETAILS_BLOCKS.contains(&active_block)
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -114,7 +118,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
match self.active_sonarr_block { match self.active_sonarr_block {
ActiveSonarrBlock::SystemLogs => match self.key { ActiveSonarrBlock::SystemLogs => match self.key {
_ if key == DEFAULT_KEYBINDINGS.left.key => { _ if matches_key!(left, key) => {
self self
.app .app
.data .data
@@ -124,7 +128,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
.iter() .iter()
.for_each(|log| log.scroll_right()); .for_each(|log| log.scroll_right());
} }
_ if key == DEFAULT_KEYBINDINGS.right.key => { _ if matches_key!(right, key) => {
self self
.app .app
.data .data
@@ -178,14 +182,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SystemDetailsHandler
} }
fn handle_char_key_event(&mut self) { fn handle_char_key_event(&mut self) {
if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) if SYSTEM_DETAILS_BLOCKS.contains(&self.active_sonarr_block) && matches_key!(refresh, self.key)
&& self.key == DEFAULT_KEYBINDINGS.refresh.key
{ {
self.app.should_refresh = true; self.app.should_refresh = true;
} }
if self.active_sonarr_block == ActiveSonarrBlock::SystemTaskStartConfirmPrompt if self.active_sonarr_block == ActiveSonarrBlock::SystemTaskStartConfirmPrompt
&& self.key == DEFAULT_KEYBINDINGS.confirm.key && matches_key!(confirm, self.key)
{ {
self.app.data.sonarr_data.prompt_confirm = true; self.app.data.sonarr_data.prompt_confirm = true;
self.app.data.sonarr_data.prompt_confirm_action = self.app.data.sonarr_data.prompt_confirm_action =
@@ -1,6 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::{assert_eq, assert_str_eq}; use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::key_binding::DEFAULT_KEYBINDINGS;
@@ -960,6 +961,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_system_details_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemDetailsHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_extract_task_name() { fn test_extract_task_name() {
let mut app = App::test_default(); let mut app = App::test_default();
@@ -456,6 +456,25 @@ mod tests {
}) })
} }
#[rstest]
fn test_system_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = SystemHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveSonarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test] #[test]
fn test_system_handler_is_not_ready_when_loading() { fn test_system_handler_is_not_ready_when_loading() {
let mut app = App::test_default(); let mut app = App::test_default();
+17 -17
View File
@@ -42,17 +42,17 @@ macro_rules! handle_table_events {
fn [<handle_ $name _table_events>](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool { fn [<handle_ $name _table_events>](&mut $self, config: $crate::handlers::table_handler::TableHandlingConfig<$row>) -> bool {
if $self.is_ready() { if $self.is_ready() {
match $self.key { match $self.key {
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.up.key => $self.[<handle_ $name _table_scroll_up>](config), _ if $crate::matches_key!(up, $self.key, $self.ignore_special_keys()) => $self.[<handle_ $name _table_scroll_up>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.down.key => $self.[<handle_ $name _table_scroll_down>](config), _ if $crate::matches_key!(down, $self.key, $self.ignore_special_keys()) => $self.[<handle_ $name _table_scroll_down>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.home.key => $self.[<handle_ $name _table_home>](config), _ if $crate::matches_key!(home, $self.key) => $self.[<handle_ $name _table_home>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.end.key => $self.[<handle_ $name _table_end>](config), _ if $crate::matches_key!(end, $self.key) => $self.[<handle_ $name _table_end>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.left.key _ if $crate::matches_key!(left, $self.key, $self.ignore_special_keys())
|| $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.right.key => || $crate::matches_key!(right, $self.key, $self.ignore_special_keys()) =>
{ {
$self.[<handle_ $name _table_left_right>](config) $self.[<handle_ $name _table_left_right>](config)
} }
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.submit.key => $self.[<handle_ $name _table_submit>](config), _ if $crate::matches_key!(submit, $self.key) => $self.[<handle_ $name _table_submit>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.esc.key => $self.[<handle_ $name _table_esc>](config), _ if $crate::matches_key!(esc, $self.key) => $self.[<handle_ $name _table_esc>](config),
_ if config.searching_block.is_some() _ if config.searching_block.is_some()
&& $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() =>
{ {
@@ -63,11 +63,11 @@ macro_rules! handle_table_events {
{ {
$self.[<handle_ $name _table_filter_box_input>]() $self.[<handle_ $name _table_filter_box_input>]()
} }
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.filter.key _ if $crate::matches_key!(filter, $self.key)
&& config.filtering_block.is_some() => $self.[<handle_ $name _table_filter_key>](config), && config.filtering_block.is_some() => $self.[<handle_ $name _table_filter_key>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.search.key _ if $crate::matches_key!(search, $self.key)
&& config.searching_block.is_some() => $self.[<handle_ $name _table_search_key>](config), && config.searching_block.is_some() => $self.[<handle_ $name _table_search_key>](config),
_ if $self.key == $crate::app::key_binding::DEFAULT_KEYBINDINGS.sort.key _ if $crate::matches_key!(sort, $self.key)
&& config.sorting_block.is_some() => $self.[<handle_ $name _table_sort_key>](config), && config.sorting_block.is_some() => $self.[<handle_ $name _table_sort_key>](config),
_ => false, _ => false,
} }
@@ -244,7 +244,7 @@ macro_rules! handle_table_events {
&& $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() => && $self.app.get_current_route() == *config.searching_block.as_ref().unwrap() =>
{ {
$self.app.pop_navigation_stack(); $self.app.pop_navigation_stack();
$self.app.should_ignore_quit_key = false; $self.app.ignore_special_keys_for_textbox_input = false;
if $table.search.is_some() { if $table.search.is_some() {
let search_field_fn = config let search_field_fn = config
@@ -267,7 +267,7 @@ macro_rules! handle_table_events {
&& $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() => && $self.app.get_current_route() == *config.filtering_block.as_ref().unwrap() =>
{ {
$self.app.pop_navigation_stack(); $self.app.pop_navigation_stack();
$self.app.should_ignore_quit_key = false; $self.app.ignore_special_keys_for_textbox_input = false;
if $table.filter.is_some() { if $table.filter.is_some() {
let filter_field_fn = config let filter_field_fn = config
@@ -305,7 +305,7 @@ macro_rules! handle_table_events {
{ {
$self.app.pop_navigation_stack(); $self.app.pop_navigation_stack();
$table.reset_search(); $table.reset_search();
$self.app.should_ignore_quit_key = false; $self.app.ignore_special_keys_for_textbox_input = false;
true true
} }
_ if (config.filtering_block.is_some() _ if (config.filtering_block.is_some()
@@ -315,7 +315,7 @@ macro_rules! handle_table_events {
{ {
$self.app.pop_navigation_stack(); $self.app.pop_navigation_stack();
$table.reset_filter(); $table.reset_filter();
$self.app.should_ignore_quit_key = false; $self.app.ignore_special_keys_for_textbox_input = false;
true true
} }
_ if config.table_block == $self.app.get_current_route() _ if config.table_block == $self.app.get_current_route()
@@ -335,7 +335,7 @@ macro_rules! handle_table_events {
.push_navigation_stack(config.filtering_block.expect("Filtering block is undefined").into()); .push_navigation_stack(config.filtering_block.expect("Filtering block is undefined").into());
$table.reset_filter(); $table.reset_filter();
$table.filter = Some($crate::models::HorizontallyScrollableText::default()); $table.filter = Some($crate::models::HorizontallyScrollableText::default());
$self.app.should_ignore_quit_key = true; $self.app.ignore_special_keys_for_textbox_input = true;
true true
} else { } else {
@@ -349,7 +349,7 @@ macro_rules! handle_table_events {
.app .app
.push_navigation_stack(config.searching_block.expect("Searching block is undefined")); .push_navigation_stack(config.searching_block.expect("Searching block is undefined"));
$table.search = Some($crate::models::HorizontallyScrollableText::default()); $table.search = Some($crate::models::HorizontallyScrollableText::default());
$self.app.should_ignore_quit_key = true; $self.app.ignore_special_keys_for_textbox_input = true;
true true
} else { } else {
+22 -18
View File
@@ -48,6 +48,10 @@ mod tests {
true true
} }
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn new( fn new(
key: Key, key: Key,
app: &'a mut App<'b>, app: &'a mut App<'b>,
@@ -648,7 +652,7 @@ mod tests {
TableHandlerUnit::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::FilterMovies, None).handle(); TableHandlerUnit::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::FilterMovies, None).handle();
assert!(app.data.radarr_data.movies.filtered_items.is_some()); assert!(app.data.radarr_data.movies.filtered_items.is_some());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app app
.data .data
@@ -684,7 +688,7 @@ mod tests {
TableHandlerUnit::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::FilterMovies, None).handle(); TableHandlerUnit::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::FilterMovies, None).handle();
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.movies.filtered_items.is_none()); assert!(app.data.radarr_data.movies.filtered_items.is_none());
assert_eq!( assert_eq!(
app.get_current_route(), app.get_current_route(),
@@ -735,7 +739,7 @@ mod tests {
active_radarr_block: ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
) { ) {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
@@ -744,7 +748,7 @@ mod tests {
TableHandlerUnit::new(ESC_KEY, &mut app, active_radarr_block, None).handle(); TableHandlerUnit::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.search, None); assert_eq!(app.data.radarr_data.movies.search, None);
} }
@@ -754,7 +758,7 @@ mod tests {
active_radarr_block: ActiveRadarrBlock, active_radarr_block: ActiveRadarrBlock,
) { ) {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.push_navigation_stack(active_radarr_block.into()); app.push_navigation_stack(active_radarr_block.into());
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
@@ -773,7 +777,7 @@ mod tests {
TableHandlerUnit::new(ESC_KEY, &mut app, active_radarr_block, None).handle(); TableHandlerUnit::new(ESC_KEY, &mut app, active_radarr_block, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.filter, None); assert_eq!(app.data.radarr_data.movies.filter, None);
assert_eq!(app.data.radarr_data.movies.filtered_items, None); assert_eq!(app.data.radarr_data.movies.filtered_items, None);
assert_eq!(app.data.radarr_data.movies.filtered_state, None); assert_eq!(app.data.radarr_data.movies.filtered_state, None);
@@ -820,7 +824,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::SearchMovie.into() ActiveRadarrBlock::SearchMovie.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.radarr_data.movies.search, app.data.radarr_data.movies.search,
Some(HorizontallyScrollableText::default()) Some(HorizontallyScrollableText::default())
@@ -847,7 +851,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.search, None); assert_eq!(app.data.radarr_data.movies.search, None);
} }
@@ -869,7 +873,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.search, None); assert_eq!(app.data.radarr_data.movies.search, None);
} }
@@ -894,7 +898,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::FilterMovies.into() ActiveRadarrBlock::FilterMovies.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.movies.filter.is_some()); assert!(app.data.radarr_data.movies.filter.is_some());
} }
@@ -918,14 +922,14 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert!(app.data.radarr_data.movies.filter.is_none()); assert!(app.data.radarr_data.movies.filter.is_none());
} }
#[test] #[test]
fn test_filter_table_key_resets_previous_filter() { fn test_filter_table_key_resets_previous_filter() {
let mut app = App::test_default(); let mut app = App::test_default();
app.should_ignore_quit_key = true; app.ignore_special_keys_for_textbox_input = true;
app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::Movies.into());
app.data.radarr_data = create_test_radarr_data(); app.data.radarr_data = create_test_radarr_data();
app app
@@ -947,7 +951,7 @@ mod tests {
app.get_current_route(), app.get_current_route(),
ActiveRadarrBlock::FilterMovies.into() ActiveRadarrBlock::FilterMovies.into()
); );
assert!(app.should_ignore_quit_key); assert!(app.ignore_special_keys_for_textbox_input);
assert_eq!( assert_eq!(
app.data.radarr_data.movies.filter, app.data.radarr_data.movies.filter,
Some(HorizontallyScrollableText::default()) Some(HorizontallyScrollableText::default())
@@ -974,7 +978,7 @@ mod tests {
.handle(); .handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into()); assert_eq!(app.get_current_route(), ActiveRadarrBlock::Movies.into());
assert!(!app.should_ignore_quit_key); assert!(!app.ignore_special_keys_for_textbox_input);
assert_eq!(app.data.radarr_data.movies.filter, None); assert_eq!(app.data.radarr_data.movies.filter, None);
} }
@@ -1040,7 +1044,7 @@ mod tests {
app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default()); app.data.radarr_data.movies.search = Some(HorizontallyScrollableText::default());
TableHandlerUnit::new( TableHandlerUnit::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::SearchMovie, ActiveRadarrBlock::SearchMovie,
None, None,
@@ -1049,7 +1053,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.movies.search.as_ref().unwrap().text, app.data.radarr_data.movies.search.as_ref().unwrap().text,
"h" "a"
); );
} }
@@ -1065,7 +1069,7 @@ mod tests {
app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default()); app.data.radarr_data.movies.filter = Some(HorizontallyScrollableText::default());
TableHandlerUnit::new( TableHandlerUnit::new(
Key::Char('h'), Key::Char('a'),
&mut app, &mut app,
ActiveRadarrBlock::FilterMovies, ActiveRadarrBlock::FilterMovies,
None, None,
@@ -1074,7 +1078,7 @@ mod tests {
assert_str_eq!( assert_str_eq!(
app.data.radarr_data.movies.filter.as_ref().unwrap().text, app.data.radarr_data.movies.filter.as_ref().unwrap().text,
"h" "a"
); );
} }
+64 -11
View File
@@ -1,10 +1,4 @@
use anyhow::Result; use anyhow::Result;
use std::panic::PanicHookInfo;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{io, panic, process};
use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser}; use clap::{crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser};
use clap_complete::generate; use clap_complete::generate;
use crossterm::execute; use crossterm::execute;
@@ -16,6 +10,11 @@ use network::NetworkTrait;
use ratatui::backend::CrosstermBackend; use ratatui::backend::CrosstermBackend;
use ratatui::Terminal; use ratatui::Terminal;
use reqwest::Client; use reqwest::Client;
use std::panic::PanicHookInfo;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{io, panic, process};
use tokio::select; use tokio::select;
use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Receiver;
use tokio::sync::{mpsc, Mutex}; use tokio::sync::{mpsc, Mutex};
@@ -24,12 +23,14 @@ use utils::{
build_network_client, load_config, start_cli_no_spinner, start_cli_with_spinner, tail_logs, build_network_client, load_config, start_cli_no_spinner, start_cli_with_spinner, tail_logs,
}; };
use crate::app::App; use crate::app::{log_and_print_error, App};
use crate::cli::Command; use crate::cli::Command;
use crate::event::input_event::{Events, InputEvent}; use crate::event::input_event::{Events, InputEvent};
use crate::event::Key; use crate::event::Key;
use crate::network::{Network, NetworkEvent}; use crate::network::{Network, NetworkEvent};
use crate::ui::ui; use crate::ui::theme::{Theme, ThemeDefinitionsWrapper};
use crate::ui::{ui, THEME};
use crate::utils::load_theme_config;
mod app; mod app;
mod cli; mod cli;
@@ -74,6 +75,22 @@ struct Cli {
help = "The Managarr configuration file to use" help = "The Managarr configuration file to use"
)] )]
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
#[arg(
long,
global = true,
value_parser,
env = "MANAGARR_THEMES_FILE",
help = "The Managarr themes file to use"
)]
themes_file: Option<PathBuf>,
#[arg(
long,
global = true,
value_parser,
env = "MANAGARR_THEME",
help = "The name of the Managarr theme to use"
)]
theme: Option<String>,
#[arg( #[arg(
long, long,
global = true, global = true,
@@ -98,10 +115,12 @@ async fn main() -> Result<()> {
} else { } else {
confy::load("managarr", "config")? confy::load("managarr", "config")?
}; };
let theme_name = config.theme.clone();
let spinner_disabled = args.disable_spinner; let spinner_disabled = args.disable_spinner;
debug!("Managarr loaded using config: {config:?}"); debug!("Managarr loaded using config: {config:?}");
config.validate(); config.validate();
config.post_process_initialization(); config.post_process_initialization();
let reqwest_client = build_network_client(&config); let reqwest_client = build_network_client(&config);
let (sync_network_tx, sync_network_rx) = mpsc::channel(500); let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
let cancellation_token = CancellationToken::new(); let cancellation_token = CancellationToken::new();
@@ -140,7 +159,12 @@ async fn main() -> Result<()> {
std::thread::spawn(move || { std::thread::spawn(move || {
start_networking(sync_network_rx, &app_nw, cancellation_token, reqwest_client) start_networking(sync_network_rx, &app_nw, cancellation_token, reqwest_client)
}); });
start_ui(&app).await?; start_ui(
&app,
&args.themes_file,
args.theme.unwrap_or(theme_name.unwrap_or_default()),
)
.await?;
} }
} }
@@ -174,7 +198,36 @@ async fn start_networking(
} }
} }
async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> { async fn start_ui(
app: &Arc<Mutex<App<'_>>>,
themes_file_arg: &Option<PathBuf>,
theme_name: String,
) -> Result<()> {
let theme_definitions_wrapper = if let Some(ref theme_file) = themes_file_arg {
load_theme_config(theme_file.to_str().expect("Invalid theme file specified"))?
} else {
confy::load("managarr", "themes").unwrap_or_else(|_| ThemeDefinitionsWrapper::default())
};
let theme = if !theme_name.is_empty() {
let theme_definition = theme_definitions_wrapper
.theme_definitions
.iter()
.find(|t| t.name == theme_name);
if theme_definition.is_none() {
log_and_print_error(format!("The specified theme was not found: {theme_name}"));
process::exit(1);
}
theme_definition.unwrap().theme
} else {
debug!("No theme specified, using default theme");
Theme::default()
};
debug!("Managarr loaded using theme: {theme:?}");
theme.validate();
THEME.set(theme);
let mut stdout = io::stdout(); let mut stdout = io::stdout();
enable_raw_mode()?; enable_raw_mode()?;
@@ -193,7 +246,7 @@ async fn start_ui(app: &Arc<Mutex<App<'_>>>) -> Result<()> {
match input_events.next()? { match input_events.next()? {
InputEvent::KeyEvent(key) => { InputEvent::KeyEvent(key) => {
if key == Key::Char('q') && !app.should_ignore_quit_key { if key == Key::Char('q') && !app.ignore_special_keys_for_textbox_input {
break; break;
} }
+1 -1
View File
@@ -249,7 +249,7 @@ pub struct MediaInfo {
pub video_bit_depth: i64, pub video_bit_depth: i64,
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub video_bitrate: i64, pub video_bitrate: i64,
pub video_codec: String, pub video_codec: Option<String>,
#[derivative(Default(value = "Number::from(0)"))] #[derivative(Default(value = "Number::from(0)"))]
pub video_fps: Number, pub video_fps: Number,
pub resolution: String, pub resolution: String,
+2 -2
View File
@@ -253,7 +253,7 @@ pub struct MediaInfo {
pub video_bit_depth: i64, pub video_bit_depth: i64,
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub video_bitrate: i64, pub video_bitrate: i64,
pub video_codec: String, pub video_codec: Option<String>,
#[derivative(Default(value = "Number::from(0)"))] #[derivative(Default(value = "Number::from(0)"))]
pub video_fps: Number, pub video_fps: Number,
pub resolution: String, pub resolution: String,
@@ -288,7 +288,7 @@ pub struct Season {
#[serde(deserialize_with = "super::from_i64")] #[serde(deserialize_with = "super::from_i64")]
pub season_number: i64, pub season_number: i64,
pub monitored: bool, pub monitored: bool,
pub statistics: SeasonStatistics, pub statistics: Option<SeasonStatistics>,
} }
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)] #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
+1 -1
View File
@@ -1300,7 +1300,7 @@ impl Network<'_, '_> {
Runtime: {}", Runtime: {}",
media_info.video_bit_depth, media_info.video_bit_depth,
media_info.video_bitrate, media_info.video_bitrate,
media_info.video_codec, media_info.video_codec.unwrap_or_default(),
media_info.video_fps.as_f64().unwrap(), media_info.video_fps.as_f64().unwrap(),
media_info.resolution, media_info.resolution,
media_info.scan_type, media_info.scan_type,
+1 -1
View File
@@ -4008,7 +4008,7 @@ mod test {
audio_stream_count: 1, audio_stream_count: 1,
video_bit_depth: 10, video_bit_depth: 10,
video_bitrate: 0, video_bitrate: 0,
video_codec: "x265".to_owned(), video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(), video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x804".to_owned(), resolution: "1920x804".to_owned(),
run_time: "2:00:00".to_owned(), run_time: "2:00:00".to_owned(),
+1 -1
View File
@@ -1408,7 +1408,7 @@ impl Network<'_, '_> {
Subtitles: {}", Subtitles: {}",
media_info.video_bit_depth, media_info.video_bit_depth,
media_info.video_bitrate, media_info.video_bitrate,
media_info.video_codec, media_info.video_codec.unwrap_or_default(),
media_info.video_fps.as_f64().unwrap(), media_info.video_fps.as_f64().unwrap(),
media_info.resolution, media_info.resolution,
media_info.scan_type, media_info.scan_type,
+2 -2
View File
@@ -5670,7 +5670,7 @@ mod test {
audio_stream_count: 1, audio_stream_count: 1,
video_bit_depth: 10, video_bit_depth: 10,
video_bitrate: 0, video_bitrate: 0,
video_codec: "x265".to_owned(), video_codec: Some("x265".to_owned()),
video_fps: Number::from_f64(23.976).unwrap(), video_fps: Number::from_f64(23.976).unwrap(),
resolution: "1920x1080".to_owned(), resolution: "1920x1080".to_owned(),
run_time: "23:51".to_owned(), run_time: "23:51".to_owned(),
@@ -5700,7 +5700,7 @@ mod test {
title: None, title: None,
season_number: 1, season_number: 1,
monitored: true, monitored: true,
statistics: season_statistics(), statistics: Some(season_statistics()),
} }
} }
+148
View File
@@ -0,0 +1,148 @@
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition};
use ratatui::style::Color;
use std::str::FromStr;
#[cfg(test)]
#[path = "builtin_themes_tests.rs"]
mod builtin_themes_tests;
pub(in crate::ui) fn watermelon_dark_theme() -> Theme {
Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
}
}
pub(in crate::ui) fn dracula_theme() -> Theme {
Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#232326").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
}
}
pub(in crate::ui) fn eldritch_theme() -> Theme {
Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#212337").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#f16c75").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#f265b5").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#04d1f9").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fc79").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
..Theme::default()
}
}
pub fn get_builtin_themes() -> Vec<ThemeDefinition> {
vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark_theme(),
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula_theme(),
},
ThemeDefinition {
name: "eldritch".to_owned(),
theme: eldritch_theme(),
},
]
}
+143
View File
@@ -0,0 +1,143 @@
#[cfg(test)]
mod test {
use crate::ui::builtin_themes::get_builtin_themes;
use crate::ui::theme::{Background, Style, Theme, ThemeDefinition};
use pretty_assertions::assert_eq;
use ratatui::prelude::Color;
use std::str::FromStr;
#[test]
fn test_builtin_themes() {
let watermelon_dark = Theme {
background: Some(Background {
enabled: Some(false),
color: Some(Color::from_str("#233237").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#00FF00").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#80ffbf").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ff8080").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff19d9").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#8c19ff").unwrap()),
}),
..Theme::default()
};
let dracula = Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#232326").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#ff5555").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#ffb86c").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#ff79c6").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#6272a4").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#50fa7b").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fa8c").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#f8f8f2").unwrap()),
}),
..Theme::default()
};
let eldritch = Theme {
background: Some(Background {
enabled: Some(true),
color: Some(Color::from_str("#212337").unwrap()),
}),
default: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
downloaded: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
downloading: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
failure: Some(Style {
color: Some(Color::from_str("#f16c75").unwrap()),
}),
missing: Some(Style {
color: Some(Color::from_str("#f7c67f").unwrap()),
}),
unmonitored_missing: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
help: Some(Style {
color: Some(Color::from_str("#7081d0").unwrap()),
}),
primary: Some(Style {
color: Some(Color::from_str("#f265b5").unwrap()),
}),
secondary: Some(Style {
color: Some(Color::from_str("#04d1f9").unwrap()),
}),
success: Some(Style {
color: Some(Color::from_str("#37f499").unwrap()),
}),
warning: Some(Style {
color: Some(Color::from_str("#f1fc79").unwrap()),
}),
unreleased: Some(Style {
color: Some(Color::from_str("#ebfafa").unwrap()),
}),
..Theme::default()
};
let expected_themes = vec![
ThemeDefinition {
name: "default".to_owned(),
theme: Theme::default(),
},
ThemeDefinition {
name: "watermelon-dark".to_owned(),
theme: watermelon_dark,
},
ThemeDefinition {
name: "dracula".to_owned(),
theme: dracula,
},
ThemeDefinition {
name: "eldritch".to_owned(),
theme: eldritch,
},
];
assert_eq!(expected_themes, get_builtin_themes());
}
}
+12 -2
View File
@@ -1,3 +1,4 @@
use std::cell::Cell;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use ratatui::layout::{Constraint, Flex, Layout, Rect}; use ratatui::layout::{Constraint, Flex, Layout, Rect};
@@ -15,19 +16,26 @@ use crate::app::App;
use crate::models::{HorizontallyScrollableText, Route, TabState}; use crate::models::{HorizontallyScrollableText, Route, TabState};
use crate::ui::radarr_ui::RadarrUi; use crate::ui::radarr_ui::RadarrUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::theme::Theme;
use crate::ui::utils::{ use crate::ui::utils::{
background_block, borderless_block, centered_rect, logo_block, title_block, title_block_centered, background_block, borderless_block, centered_rect, logo_block, title_block, title_block_centered,
unstyled_title_block,
}; };
use crate::ui::widgets::input_box::InputBox; use crate::ui::widgets::input_box::InputBox;
use crate::ui::widgets::popup::Size; use crate::ui::widgets::popup::Size;
mod builtin_themes;
mod radarr_ui; mod radarr_ui;
mod sonarr_ui; mod sonarr_ui;
mod styles; mod styles;
pub mod theme;
mod utils; mod utils;
mod widgets; mod widgets;
static HIGHLIGHT_SYMBOL: &str = "=> "; static HIGHLIGHT_SYMBOL: &str = "=> ";
thread_local! {
pub static THEME: Cell<Theme> = Cell::new(Theme::default());
}
pub trait DrawUi { pub trait DrawUi {
fn accepts(route: Route) -> bool; fn accepts(route: Route) -> bool;
@@ -100,7 +108,9 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
} }
fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let block = title_block("Error | <esc> to close").failure().bold(); let block = unstyled_title_block("Error | <esc> to close")
.failure()
.bold();
app.error.scroll_left_or_reset( app.error.scroll_left_or_reset(
area.width as usize, area.width as usize,
@@ -130,7 +140,7 @@ pub fn draw_popup(
fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect { fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect {
if title.is_empty() { if title.is_empty() {
f.render_widget(layout_block(), area); f.render_widget(layout_block().default(), area);
} else { } else {
f.render_widget(title_block(title), area); f.render_widget(title_block(title), area);
} }

Some files were not shown because too many files have changed in this diff Show More