feat: add watch-later views and Terebi watched-state tracking
This commit is contained in:
+99
@@ -105,6 +105,10 @@ actions! {
|
||||
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"];
|
||||
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"];
|
||||
@@ -458,6 +462,21 @@ impl Bindings {
|
||||
(&[Quit, Interrupt], "Quit"),
|
||||
],
|
||||
),
|
||||
(
|
||||
"WATCH LATER",
|
||||
&[
|
||||
(&[WatchLaterSave], "Save the selected video"),
|
||||
(
|
||||
&[WatchLaterBrowse],
|
||||
"Browse category watch later; combined elsewhere",
|
||||
),
|
||||
(&[WatchLaterFilter], "Cycle all / unwatched / watched"),
|
||||
(
|
||||
&[WatchLaterRemove],
|
||||
"Confirm removal of filtered subset (combined: all matches)",
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
"FULL DESCRIPTION",
|
||||
&[
|
||||
@@ -948,6 +967,86 @@ 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'\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,
|
||||
),
|
||||
] {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
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 invalid_entries_and_collisions_have_actionable_errors() {
|
||||
for (source, path) in [
|
||||
|
||||
Reference in New Issue
Block a user