feat: scope individual and bulk removal to collection views
This commit is contained in:
+1
-1
@@ -670,7 +670,7 @@ mod tests {
|
||||
|
||||
let directory = TestDirectory::new();
|
||||
let path = directory.0.join("config.toml");
|
||||
let key_section = "# custom controls\n[keys]\napp.quit = ['x', 'ctrl+x'] # quit note\nview.refresh = []\nvideo.saved.remove = []\n\n[keys.selector]\npin = 'f2' # pin note\n";
|
||||
let key_section = "# custom controls\n[keys]\napp.quit = ['x', 'ctrl+x'] # quit note\nview.refresh = []\nview.collection.remove.selected = []\n\n[keys.selector]\npin = 'f2' # pin note\n";
|
||||
let sections = [
|
||||
("keys", key_section),
|
||||
("settings", "[settings]\npinned_categories = []\n"),
|
||||
|
||||
+229
-135
@@ -13,6 +13,9 @@ pub enum Context {
|
||||
Normal,
|
||||
Info,
|
||||
Description,
|
||||
CollectionNormal,
|
||||
CollectionInfo,
|
||||
CollectionDescription,
|
||||
SearchInput,
|
||||
SelectorNormal,
|
||||
SelectorInput,
|
||||
@@ -26,6 +29,9 @@ const ALL: &[Context] = &[
|
||||
Context::Normal,
|
||||
Context::Info,
|
||||
Context::Description,
|
||||
Context::CollectionNormal,
|
||||
Context::CollectionInfo,
|
||||
Context::CollectionDescription,
|
||||
Context::SearchInput,
|
||||
Context::SelectorNormal,
|
||||
Context::SelectorInput,
|
||||
@@ -34,7 +40,19 @@ const ALL: &[Context] = &[
|
||||
Context::TagInput,
|
||||
Context::Help,
|
||||
];
|
||||
const VIEW: &[Context] = &[Context::Normal, Context::Info, Context::Description];
|
||||
const VIEW: &[Context] = &[
|
||||
Context::Normal,
|
||||
Context::Info,
|
||||
Context::Description,
|
||||
Context::CollectionNormal,
|
||||
Context::CollectionInfo,
|
||||
Context::CollectionDescription,
|
||||
];
|
||||
const COLLECTION: &[Context] = &[
|
||||
Context::CollectionNormal,
|
||||
Context::CollectionInfo,
|
||||
Context::CollectionDescription,
|
||||
];
|
||||
const TEXT: &[Context] = &[
|
||||
Context::SearchInput,
|
||||
Context::SelectorInput,
|
||||
@@ -45,6 +63,9 @@ const NON_TEXT: &[Context] = &[
|
||||
Context::Normal,
|
||||
Context::Info,
|
||||
Context::Description,
|
||||
Context::CollectionNormal,
|
||||
Context::CollectionInfo,
|
||||
Context::CollectionDescription,
|
||||
Context::SelectorNormal,
|
||||
Context::TagList,
|
||||
Context::Help,
|
||||
@@ -79,7 +100,7 @@ macro_rules! actions {
|
||||
|
||||
actions! {
|
||||
Interrupt, "app.interrupt", "Exit immediately", ALL, ["ctrl+c"];
|
||||
Quit, "app.quit", "Quit the application", &[Context::Normal, Context::Info, Context::Description, Context::Help], ["q"];
|
||||
Quit, "app.quit", "Quit the application", &[Context::Normal, Context::Info, Context::Description, Context::CollectionNormal, Context::CollectionInfo, Context::CollectionDescription, Context::Help], ["q"];
|
||||
Help, "help.toggle", "Open or close keyboard help", NON_TEXT, ["?"];
|
||||
InputSubmit, "input.submit", "Submit search, browse selector, leave tag filter or stage new tag", TEXT, ["enter"];
|
||||
InputCancel, "input.cancel", "Leave text input without submitting", TEXT, ["escape"];
|
||||
@@ -99,20 +120,19 @@ actions! {
|
||||
VideoLast, "video.select.last", "Select the last video", VIEW, ["G", "end"];
|
||||
InfoOverview, "video.info.overview", "Show the video overview", VIEW, ["d"];
|
||||
InfoDescription, "video.info.description", "Show the video description", VIEW, ["d"];
|
||||
InfoHide, "video.info.hide", "Close the video preview", &[Context::Info, Context::Description], ["escape"];
|
||||
InfoHide, "video.info.hide", "Close the video preview", &[Context::Info, Context::Description, Context::CollectionInfo, Context::CollectionDescription], ["escape"];
|
||||
CategoryBrowse, "category.browse", "Browse categories", VIEW, ["c"];
|
||||
ChannelBrowse, "channel.browse", "Browse subscribed channels", VIEW, ["u"];
|
||||
VideoChannel, "video.channel", "Open the selected video's channel", VIEW, ["U"];
|
||||
ChannelTags, "channel.tags", "Edit the selected channel's category tags", VIEW, ["s"];
|
||||
CategoryTags, "category.tags", "Edit category tags for all channels in this category", VIEW, ["S"];
|
||||
WatchLaterSave, "video.watch_later.save", "Save the selected video to watch later", VIEW, ["w"];
|
||||
WatchLaterBrowse, "view.watch_later.browse", "Browse this category's watch later, or the combined collection elsewhere", &[Context::Normal], ["W"];
|
||||
WatchLaterFilter, "view.watch_later.filter", "Cycle watch-later filter: all, unwatched, watched", &[Context::Normal], ["f"];
|
||||
WatchLaterRemove, "view.watch_later.remove", "Confirm removal of the current filtered watch-later subset", &[Context::Normal], ["D"];
|
||||
WatchLaterRemoveSelected, "video.watch_later.remove", "Remove only the selected watch-later video (also in previews)", VIEW, ["X"];
|
||||
WatchLaterBrowse, "view.watch_later.browse", "Browse this category's watch later, or the combined collection elsewhere", &[Context::Normal, Context::CollectionNormal], ["W"];
|
||||
WatchLaterFilter, "view.watch_later.filter", "Cycle watch-later filter: all, unwatched, watched", &[Context::Normal, Context::CollectionNormal], ["f"];
|
||||
CollectionRemoveSelected, "view.collection.remove.selected", "Remove the selected video from the current collection (also in its previews)", COLLECTION, ["x"];
|
||||
CollectionRemoveBulk, "view.collection.remove.bulk", "Confirm removal of the current collection subset", COLLECTION, ["X"];
|
||||
SavedVideoSave, "video.saved.save", "Save the selected video (also in previews)", VIEW, ["v"];
|
||||
SavedVideoBrowse, "view.saved.browse", "Browse this category's saved videos, or the combined collection elsewhere", &[Context::Normal], ["V"];
|
||||
SavedVideoRemove, "video.saved.remove", "Remove the selected saved video (also in previews)", VIEW, ["x"];
|
||||
SavedVideoBrowse, "view.saved.browse", "Browse this category's saved videos, or the combined collection elsewhere", &[Context::Normal, Context::CollectionNormal], ["V"];
|
||||
SelectorNext, "selector.select.next", "Select the next category or channel", &[Context::SelectorNormal], ["j", "down"];
|
||||
SelectorPrevious, "selector.select.previous", "Select the previous category or channel", &[Context::SelectorNormal], ["k", "up"];
|
||||
SelectorInputNext, "selector.input.next", "Select the next match while editing the filter", &[Context::SelectorInput], ["down", "ctrl+j"];
|
||||
@@ -130,10 +150,10 @@ actions! {
|
||||
TagNew, "tags.new", "Enter a new category tag name", &[Context::TagList], ["n"];
|
||||
TagSave, "tags.save", "Save category tag changes", &[Context::TagList], ["enter"];
|
||||
TagCancel, "tags.cancel", "Discard category tag changes and close", &[Context::TagList], ["escape"];
|
||||
DescriptionNext, "video.description.scroll.next", "Scroll the description down one line", &[Context::Description], ["down", "ctrl+j"];
|
||||
DescriptionPrevious, "video.description.scroll.previous", "Scroll the description up one line", &[Context::Description], ["up", "ctrl+k"];
|
||||
DescriptionPageNext, "video.description.page.next", "Scroll the description down one page", &[Context::Description], ["pagedown"];
|
||||
DescriptionPagePrevious, "video.description.page.previous", "Scroll the description up one page", &[Context::Description], ["pageup"];
|
||||
DescriptionNext, "video.description.scroll.next", "Scroll the description down one line", &[Context::Description, Context::CollectionDescription], ["down", "ctrl+j"];
|
||||
DescriptionPrevious, "video.description.scroll.previous", "Scroll the description up one line", &[Context::Description, Context::CollectionDescription], ["up", "ctrl+k"];
|
||||
DescriptionPageNext, "video.description.page.next", "Scroll the description down one page", &[Context::Description, Context::CollectionDescription], ["pagedown"];
|
||||
DescriptionPagePrevious, "video.description.page.previous", "Scroll the description up one page", &[Context::Description, Context::CollectionDescription], ["pageup"];
|
||||
HelpNext, "help.scroll.next", "Scroll help down one line", &[Context::Help], ["j", "down", "ctrl+j"];
|
||||
HelpPrevious, "help.scroll.previous", "Scroll help up one line", &[Context::Help], ["k", "up", "ctrl+k"];
|
||||
HelpPageNext, "help.page.next", "Scroll help down one page", &[Context::Help], ["pagedown"];
|
||||
@@ -475,14 +495,6 @@ impl Bindings {
|
||||
"Browse category watch later; combined elsewhere",
|
||||
),
|
||||
(&[WatchLaterFilter], "Cycle all / unwatched / watched"),
|
||||
(
|
||||
&[WatchLaterRemoveSelected],
|
||||
"Remove the selected video (also in previews)",
|
||||
),
|
||||
(
|
||||
&[WatchLaterRemove],
|
||||
"Confirm removal of filtered subset (combined: all matches)",
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
@@ -496,9 +508,26 @@ impl Bindings {
|
||||
&[SavedVideoBrowse],
|
||||
"Browse category saved videos; combined elsewhere",
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
"COLLECTION LISTS",
|
||||
&[
|
||||
(
|
||||
&[SavedVideoRemove],
|
||||
"Remove the selected video (also in previews)",
|
||||
&[CollectionRemoveSelected],
|
||||
"Remove selected from current watch-later / saved list only",
|
||||
),
|
||||
(
|
||||
&[CollectionRemoveBulk],
|
||||
"Confirm removal of current category / filtered subset",
|
||||
),
|
||||
(
|
||||
&[],
|
||||
"Previews inherit their underlying collection's removal scope",
|
||||
),
|
||||
(
|
||||
&[],
|
||||
"Bulk confirmation: y / Enter confirms; n / Esc cancels",
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -658,16 +687,29 @@ fn overlap_winner(context: Context, first: Action, second: Action) -> Option<Act
|
||||
return Some(first);
|
||||
}
|
||||
if pair(InfoOverview, InfoDescription) && VIEW.contains(&context) {
|
||||
return Some(if context == Context::Info {
|
||||
InfoDescription
|
||||
} else {
|
||||
InfoOverview
|
||||
});
|
||||
return Some(
|
||||
if matches!(context, Context::Info | Context::CollectionInfo) {
|
||||
InfoDescription
|
||||
} else {
|
||||
InfoOverview
|
||||
},
|
||||
);
|
||||
}
|
||||
if pair(InfoHide, ViewPrevious) && matches!(context, Context::Info | Context::Description) {
|
||||
if pair(InfoHide, ViewPrevious)
|
||||
&& matches!(
|
||||
context,
|
||||
Context::Info
|
||||
| Context::Description
|
||||
| Context::CollectionInfo
|
||||
| Context::CollectionDescription
|
||||
)
|
||||
{
|
||||
return Some(InfoHide);
|
||||
}
|
||||
if context == Context::Description {
|
||||
if matches!(
|
||||
context,
|
||||
Context::Description | Context::CollectionDescription
|
||||
) {
|
||||
if pair(DescriptionNext, VideoNext) {
|
||||
return Some(DescriptionNext);
|
||||
}
|
||||
@@ -753,6 +795,9 @@ mod tests {
|
||||
(Context::Normal, Action::InfoOverview),
|
||||
(Context::Info, Action::InfoDescription),
|
||||
(Context::Description, Action::InfoOverview),
|
||||
(Context::CollectionNormal, Action::InfoOverview),
|
||||
(Context::CollectionInfo, Action::InfoDescription),
|
||||
(Context::CollectionDescription, Action::InfoOverview),
|
||||
] {
|
||||
assert_eq!(
|
||||
press(&defaults, context, KeyCode::Char('d'), KeyModifiers::NONE),
|
||||
@@ -951,8 +996,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aliases_replace_defaults_and_can_disable_actions() {
|
||||
let bindings = configured("[keys.video.saved]\nremove = []\n[keys.view]\nrefresh = ['x', 'CTRL+y']\n[keys.app]\nquit = []\n[keys.input]\nsubmit = 'RETURN'\ncancel = 'EsC'\n").unwrap();
|
||||
let quoted = configured("[keys]\n'video.saved.remove' = []\n'view.refresh' = ['x', 'CTRL+y']\n'app.quit' = []\n'input.submit' = 'RETURN'\n'input.cancel' = 'EsC'\n").unwrap();
|
||||
let bindings = configured("[keys.view.collection.remove]\nselected = []\n[keys.view]\nrefresh = ['x', 'CTRL+y']\n[keys.app]\nquit = []\n[keys.input]\nsubmit = 'RETURN'\ncancel = 'EsC'\n").unwrap();
|
||||
let quoted = configured("[keys]\n'view.collection.remove.selected' = []\n'view.refresh' = ['x', 'CTRL+y']\n'app.quit' = []\n'input.submit' = 'RETURN'\n'input.cancel' = 'EsC'\n").unwrap();
|
||||
assert_eq!(quoted, bindings);
|
||||
assert_eq!(
|
||||
press(
|
||||
@@ -993,124 +1038,173 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watch_later_overrides_disable_and_respect_contexts() {
|
||||
let defaults = Bindings::default();
|
||||
for (key, action) in [
|
||||
('w', Action::WatchLaterBrowse),
|
||||
('d', Action::WatchLaterRemove),
|
||||
] {
|
||||
assert_eq!(
|
||||
press(
|
||||
&defaults,
|
||||
Context::Normal,
|
||||
KeyCode::Char(key),
|
||||
KeyModifiers::SHIFT
|
||||
),
|
||||
Some(action),
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
press(
|
||||
&defaults,
|
||||
Context::Info,
|
||||
KeyCode::Char('w'),
|
||||
KeyModifiers::NONE
|
||||
),
|
||||
Some(Action::WatchLaterSave),
|
||||
);
|
||||
assert_eq!(
|
||||
press(
|
||||
&defaults,
|
||||
Context::Info,
|
||||
KeyCode::Char('d'),
|
||||
KeyModifiers::NONE
|
||||
),
|
||||
Some(Action::InfoDescription),
|
||||
);
|
||||
let bindings = configured(
|
||||
"[keys.video.watch_later]\nsave = 'alt+w'\nremove = []\n[keys.view.watch_later]\nbrowse = 'shift+x'\nfilter = 'ctrl+f'\nremove = []\n",
|
||||
)
|
||||
.unwrap();
|
||||
for context in ALL {
|
||||
assert_eq!(
|
||||
press(&bindings, *context, KeyCode::Char('w'), KeyModifiers::ALT),
|
||||
VIEW.contains(context).then_some(Action::WatchLaterSave),
|
||||
);
|
||||
for (code, modifiers, action) in [
|
||||
(
|
||||
KeyCode::Char('x'),
|
||||
KeyModifiers::SHIFT,
|
||||
Action::WatchLaterBrowse,
|
||||
),
|
||||
(
|
||||
KeyCode::Char('f'),
|
||||
KeyModifiers::CONTROL,
|
||||
Action::WatchLaterFilter,
|
||||
),
|
||||
fn collection_removal_defaults_are_scoped_and_inherit_view_actions() {
|
||||
let bindings = Bindings::default();
|
||||
for &context in ALL {
|
||||
for (key, action) in [
|
||||
('x', Action::CollectionRemoveSelected),
|
||||
('X', Action::CollectionRemoveBulk),
|
||||
] {
|
||||
assert_eq!(
|
||||
press(&bindings, *context, code, modifiers),
|
||||
(*context == Context::Normal).then_some(action),
|
||||
);
|
||||
}
|
||||
for key in ['w', 'W', 'f', 'D'] {
|
||||
assert_eq!(
|
||||
press(&bindings, *context, KeyCode::Char(key), KeyModifiers::NONE),
|
||||
None,
|
||||
press(&bindings, context, KeyCode::Char(key), KeyModifiers::NONE),
|
||||
COLLECTION.contains(&context).then_some(action),
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
press(&bindings, context, KeyCode::Char('D'), KeyModifiers::NONE),
|
||||
None,
|
||||
);
|
||||
}
|
||||
for (ordinary, collection) in [
|
||||
(Context::Normal, Context::CollectionNormal),
|
||||
(Context::Info, Context::CollectionInfo),
|
||||
(Context::Description, Context::CollectionDescription),
|
||||
] {
|
||||
for &(key, action) in &bindings.contexts[ordinary as usize] {
|
||||
assert_eq!(
|
||||
bindings.resolve(collection, KeyEvent::new(key.code, key.modifiers)),
|
||||
Some(action),
|
||||
"{ordinary:?} / {collection:?}: {key:?}",
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
bindings.contexts[collection as usize].len(),
|
||||
bindings.contexts[ordinary as usize].len() + 2,
|
||||
);
|
||||
}
|
||||
let help = bindings.help_text().to_string();
|
||||
assert!(help.contains("Alt+w") && help.contains("X") && help.contains("Ctrl+f"));
|
||||
assert!(help.contains("unbound") && help.contains("WATCH LATER"));
|
||||
let collision = configured("[keys]\nview.watch_later.browse = 'w'")
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(
|
||||
collision.contains("view.watch_later.browse")
|
||||
&& collision.contains("video.watch_later.save")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saved_video_overrides_disable_and_respect_contexts() {
|
||||
let defaults = Bindings::default();
|
||||
for context in ALL {
|
||||
for (key, action, enabled) in [
|
||||
('v', Action::SavedVideoSave, VIEW.contains(context)),
|
||||
('V', Action::SavedVideoBrowse, *context == Context::Normal),
|
||||
('x', Action::SavedVideoRemove, VIEW.contains(context)),
|
||||
] {
|
||||
assert_eq!(
|
||||
press(&defaults, *context, KeyCode::Char(key), KeyModifiers::NONE),
|
||||
enabled.then_some(action),
|
||||
);
|
||||
}
|
||||
}
|
||||
fn collection_overrides_aliases_normalize_and_disable() {
|
||||
let bindings = configured(
|
||||
"[keys.video.watch_later]\nremove = []\n[keys.video.saved]\nsave = 'alt+v'\nremove = []\n[keys.view.saved]\nbrowse = 'shift+x'\n",
|
||||
"[keys.view.collection.remove]\nselected = ['option+x', 'alt+x']\nbulk = 'shift+b'\n",
|
||||
)
|
||||
.unwrap();
|
||||
for context in ALL {
|
||||
assert_eq!(
|
||||
press(&bindings, *context, KeyCode::Char('v'), KeyModifiers::ALT),
|
||||
VIEW.contains(context).then_some(Action::SavedVideoSave),
|
||||
);
|
||||
assert_eq!(
|
||||
press(&bindings, *context, KeyCode::Char('x'), KeyModifiers::SHIFT),
|
||||
(*context == Context::Normal).then_some(Action::SavedVideoBrowse),
|
||||
);
|
||||
for key in ['v', 'V', 'x'] {
|
||||
let quoted = configured(
|
||||
"[keys]\n'view.collection.remove.selected' = 'alt+x'\n'view.collection.remove.bulk' = 'B'\n",
|
||||
).unwrap();
|
||||
assert_eq!(bindings, quoted);
|
||||
assert_eq!(bindings.label(Action::CollectionRemoveSelected), "Alt+x");
|
||||
assert_eq!(bindings.label(Action::CollectionRemoveBulk), "B");
|
||||
let help = bindings.help_text().to_string();
|
||||
assert!(help.contains("Alt+x"));
|
||||
assert!(help.lines().any(|line| line.trim_start().starts_with("B ")));
|
||||
for &context in ALL {
|
||||
for (code, modifiers, action) in [
|
||||
(
|
||||
KeyCode::Char('x'),
|
||||
KeyModifiers::ALT,
|
||||
Action::CollectionRemoveSelected,
|
||||
),
|
||||
(
|
||||
KeyCode::Char('b'),
|
||||
KeyModifiers::SHIFT,
|
||||
Action::CollectionRemoveBulk,
|
||||
),
|
||||
(
|
||||
KeyCode::Char('B'),
|
||||
KeyModifiers::NONE,
|
||||
Action::CollectionRemoveBulk,
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
press(&bindings, *context, KeyCode::Char(key), KeyModifiers::NONE),
|
||||
None,
|
||||
press(&bindings, context, code, modifiers),
|
||||
COLLECTION.contains(&context).then_some(action),
|
||||
);
|
||||
}
|
||||
for key in ['x', 'X'] {
|
||||
assert_eq!(
|
||||
press(&bindings, context, KeyCode::Char(key), KeyModifiers::NONE),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
let collision = configured("[keys]\nview.saved.browse = 'v'")
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(collision.contains("view.saved.browse") && collision.contains("video.saved.save"));
|
||||
let disabled = configured(
|
||||
"[keys.view.collection.remove]\nselected = []\nbulk = []\n[keys.view]\nrefresh = ['x', 'shift+x']\n",
|
||||
).unwrap();
|
||||
for &context in ALL {
|
||||
for key in ['x', 'X'] {
|
||||
assert_eq!(
|
||||
press(&disabled, context, KeyCode::Char(key), KeyModifiers::NONE),
|
||||
VIEW.contains(&context).then_some(Action::ViewRefresh),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collection_contexts_validate_inherited_binding_conflicts() {
|
||||
for source in [
|
||||
"[keys]\nview.refresh = 'x'",
|
||||
"[keys]\nview.watch_later.browse = 'X'",
|
||||
"[keys]\nview.saved.browse = 'X'",
|
||||
"[keys]\nview.collection.remove.selected = 'q'",
|
||||
"[keys]\nview.collection.remove.bulk = 'down'",
|
||||
] {
|
||||
let error = configured(source).unwrap_err();
|
||||
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
|
||||
assert!(error.to_string().contains("Collection"), "{error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collection_browsing_and_save_overrides_retain_contexts() {
|
||||
let bindings = configured(
|
||||
"[keys.video.watch_later]\nsave = 'alt+w'\n[keys.video.saved]\nsave = 'alt+v'\n[keys.view.watch_later]\nbrowse = 'shift+x'\nfilter = 'ctrl+f'\n[keys.view.saved]\nbrowse = 'alt+b'\n[keys.view.collection.remove]\nbulk = []\n",
|
||||
).unwrap();
|
||||
for &context in ALL {
|
||||
for (key, modifiers, action, enabled) in [
|
||||
(
|
||||
'w',
|
||||
KeyModifiers::ALT,
|
||||
Action::WatchLaterSave,
|
||||
VIEW.contains(&context),
|
||||
),
|
||||
(
|
||||
'v',
|
||||
KeyModifiers::ALT,
|
||||
Action::SavedVideoSave,
|
||||
VIEW.contains(&context),
|
||||
),
|
||||
(
|
||||
'x',
|
||||
KeyModifiers::SHIFT,
|
||||
Action::WatchLaterBrowse,
|
||||
matches!(context, Context::Normal | Context::CollectionNormal),
|
||||
),
|
||||
(
|
||||
'f',
|
||||
KeyModifiers::CONTROL,
|
||||
Action::WatchLaterFilter,
|
||||
matches!(context, Context::Normal | Context::CollectionNormal),
|
||||
),
|
||||
(
|
||||
'b',
|
||||
KeyModifiers::ALT,
|
||||
Action::SavedVideoBrowse,
|
||||
matches!(context, Context::Normal | Context::CollectionNormal),
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
press(&bindings, context, KeyCode::Char(key), modifiers),
|
||||
enabled.then_some(action)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn obsolete_removal_names_are_unknown() {
|
||||
for name in [
|
||||
"view.watch_later.remove",
|
||||
"video.watch_later.remove",
|
||||
"video.saved.remove",
|
||||
] {
|
||||
let error = configured(&format!("[keys]\n'{name}' = []")).unwrap_err();
|
||||
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
|
||||
assert!(error.to_string().contains("unknown action"), "{error}");
|
||||
assert!(error.to_string().contains(name), "{error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+564
-108
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user