feat: browse channel playlists

This commit is contained in:
2026-10-25 21:53:30 +01:00
parent 6cc4e18f72
commit 65200626d2
7 changed files with 483 additions and 27 deletions
+72
View File
@@ -3749,6 +3749,78 @@ fn back_history_uses_source_identity_after_config_reorders_or_removes_views() {
assert!(app.job.is_none());
}
#[test]
fn channel_playlists_filter_open_in_provider_order_and_retain_history_and_config() {
let mut app = app(&["tubular"]);
let press = |app: &mut App, code| {
app.key(KeyEvent::new(code, KeyModifiers::NONE));
};
press(&mut app, KeyCode::Char('L'));
assert!(app.browser.is_none());
let channel = ViewSource::Channel("https://www.youtube.com/@RustVideos".into());
let mut view = View::new("@RustVideos".into(), channel.clone());
view.loaded = true;
app.views.push(view);
app.switch_view(app.views.len() - 1);
press(&mut app, KeyCode::Char('L'));
assert_eq!(app.browser.as_ref().unwrap().kind, BrowserKind::Playlists);
app.finish_playlists(Ok(r#"{"entries":[{"id":"PLJ5MFiMfElFA","title":"Conference"},{"id":"PLJ5MFiMfElFB","title":"Tutorials"}]}"#.into()));
press(&mut app, KeyCode::Char('i'));
app.paste("tutor");
assert_eq!(
app.browser.as_ref().unwrap().selected_choice().unwrap().0,
"Tutorials"
);
press(&mut app, KeyCode::Enter);
assert!(app.browser.is_none());
let playlist = ViewSource::Playlist("PLJ5MFiMfElFB".into());
assert_eq!(app.views[app.view_index].source, playlist);
app.finish_page(Ok(r#"{"entries":[{"id":"jNQXAC9IVRw","title":"First","timestamp":100},{"id":"rQ_J9WH6CGk","title":"Second","timestamp":200}]}"#.into()));
let view = &app.views[app.view_index];
assert_eq!(
view.videos
.iter()
.map(|video| video.title.as_str())
.collect::<Vec<_>>(),
["First", "Second"]
);
assert!(!view.has_more);
press(&mut app, KeyCode::Char('j'));
press(&mut app, KeyCode::Char(' '));
assert_eq!(
app.views[app.view_index].playback_videos()[0].id,
"rQ_J9WH6CGk"
);
press(&mut app, KeyCode::Char('-'));
assert_eq!(app.views[app.view_index].source, channel);
press(&mut app, KeyCode::Char('+'));
assert_eq!(app.views[app.view_index].source, playlist);
assert_eq!(app.views[app.view_index].selection.selected(), Some(1));
app.apply_config(config::Config {
categories: [("Unrelated".into(), Vec::new())].into(),
..Default::default()
});
assert_eq!(app.views[app.view_index].source, playlist);
assert_eq!(app.temporary_view, Some(playlist));
assert_eq!(app.views[app.view_index].selection.selected(), Some(1));
assert!(app.config.subscriptions().is_empty());
press(&mut app, KeyCode::Char('-'));
press(&mut app, KeyCode::Char('L'));
app.finish_playlists(Err("Extractor failed".into()));
assert_eq!(
app.browser.as_ref().unwrap().error.as_deref(),
Some("Extractor failed")
);
press(&mut app, KeyCode::Esc);
assert!(app.browser.is_none());
press(&mut app, KeyCode::Char('L'));
app.finish_playlists(Ok(r#"{"entries":[]}"#.into()));
assert!(app.browser.as_ref().unwrap().choices.is_empty());
assert!(app.browser.as_ref().unwrap().error.is_none());
press(&mut app, KeyCode::Esc);
assert_eq!(app.views[app.view_index].source, channel);
}
#[test]
fn video_channel_shortcut_reuses_saved_views_and_requires_valid_identity() {
let mut app = app(&["tubular"]);