Files
Bjarne Øverli e14d84c419
Release / build (amd64, darwin, macos-latest) (push) Has been cancelled
Release / build (amd64, linux, ubuntu-latest) (push) Has been cancelled
Release / build (amd64, windows, windows-latest) (push) Has been cancelled
Release / build (arm64, darwin, macos-latest) (push) Has been cancelled
Release / build (arm64, linux, ubuntu-latest) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / update-homebrew (push) Has been cancelled
Add play/append/queue actions to Spotify and YouTube search results
Search results overlays now support Enter (play now), a (append), and
q (queue next) on the selected track, instead of only routing to a
Spotify cloud-playlist add. The cloud-playlist add moves to p.

YouTube/SoundCloud net search gains a results picker (ytsearch10 /
scsearch10) using the same keymap, replacing the previous one-shot
ytsearch1 auto-queue. Adds shared playTrackImmediate / appendTrack /
queueTrackNext helpers and closeNetSearch / closeSpotSearch helpers
that drop cached result slices on close.

playlist.IsYTSearch generalises the ytsearch:/ytsearchN:/scsearch:/
scsearchN: prefix check used by IsURL, IsYouTubeURL, and IsYTDL.
2026-05-01 21:15:40 +02:00

252 lines
6.5 KiB
Go

package playlist
import "testing"
func TestIsURL(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"http://example.com/stream.mp3", true},
{"https://example.com/stream.mp3", true},
{"ytsearch:lofi hip hop", true},
{"ytsearch1:some song", true},
{"ytsearch10:multi result query", true},
{"scsearch:artist name", true},
{"scsearch1:track name", true},
{"scsearch10:multi result query", true},
{"ytsearchabc:bad", false},
{"/home/user/music/song.mp3", false},
{"relative/path.flac", false},
{"", false},
{"ftp://files.example.com/song.mp3", false},
{"spotify:track:abc123", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsURL(tt.path); got != tt.want {
t.Errorf("IsURL(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsM3U(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"https://example.com/playlist.m3u", true},
{"https://example.com/playlist.m3u8", true},
{"https://example.com/playlist.M3U", true},
{"/home/user/playlist.m3u", true},
{"/home/user/playlist.m3u8", true},
{"https://example.com/stream.mp3", false},
{"/home/user/song.mp3", false},
{"", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsM3U(tt.path); got != tt.want {
t.Errorf("IsM3U(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsLocalM3U(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"/home/user/playlist.m3u", true},
{"/home/user/playlist.m3u8", true},
{"https://example.com/playlist.m3u", false},
{"/home/user/song.mp3", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsLocalM3U(tt.path); got != tt.want {
t.Errorf("IsLocalM3U(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsPLS(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"https://example.com/station.pls", true},
{"https://example.com/station.PLS", true},
{"/home/user/station.pls", true},
{"https://example.com/stream.mp3", false},
{"/home/user/song.mp3", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsPLS(tt.path); got != tt.want {
t.Errorf("IsPLS(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsLocalPLS(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"/home/user/station.pls", true},
{"https://example.com/station.pls", false},
{"/home/user/song.mp3", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsLocalPLS(tt.path); got != tt.want {
t.Errorf("IsLocalPLS(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsYouTubeURL(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"https://www.youtube.com/watch?v=dQw4w9WgXcQ", true},
{"https://youtube.com/watch?v=abc123", true},
{"https://youtu.be/abc123", true},
{"https://m.youtube.com/watch?v=abc123", true},
// YouTube Music URLs should NOT match
{"https://music.youtube.com/watch?v=abc123", false},
// ytsearch protocols should NOT match
{"ytsearch:lofi hip hop", false},
{"ytsearch1:some song", false},
{"ytsearch10:multi result query", false},
// Non-YouTube
{"https://soundcloud.com/artist/track", false},
{"/local/file.mp3", false},
{"", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsYouTubeURL(tt.path); got != tt.want {
t.Errorf("IsYouTubeURL(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsYouTubeMusicURL(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"https://music.youtube.com/watch?v=abc123", true},
{"https://www.music.youtube.com/watch?v=abc123", true},
// Regular YouTube should NOT match
{"https://www.youtube.com/watch?v=abc123", false},
{"https://youtu.be/abc123", false},
{"/local/file.mp3", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsYouTubeMusicURL(tt.path); got != tt.want {
t.Errorf("IsYouTubeMusicURL(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsYTDL(t *testing.T) {
tests := []struct {
path string
want bool
}{
// YouTube
{"https://www.youtube.com/watch?v=abc123", true},
{"https://youtu.be/abc123", true},
// YouTube Music
{"https://music.youtube.com/watch?v=abc123", true},
// Search protocols
{"ytsearch:lofi hip hop", true},
{"ytsearch1:some song", true},
{"ytsearch10:multi result query", true},
{"scsearch:artist name", true},
{"scsearch1:track name", true},
{"scsearch5:multi result query", true},
// SoundCloud
{"https://soundcloud.com/artist/track", true},
// Bandcamp
{"https://bandcamp.com/album", true},
{"https://artist.bandcamp.com/album/name", true},
// Bilibili
{"https://bilibili.com/video/BV123", true},
{"https://www.bilibili.com/video/BV123", true},
{"https://space.bilibili.com/12345", true},
{"https://b23.tv/abc123", true},
// NetEase
{"https://music.163.com/song?id=12345", true},
// Non-YTDL
{"https://example.com/stream.mp3", false},
{"/local/file.mp3", false},
{"", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsYTDL(tt.path); got != tt.want {
t.Errorf("IsYTDL(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsFeed(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"https://example.com/feed.xml", true},
{"https://example.com/podcast.rss", true},
{"https://example.com/feed.atom", true},
{"https://example.com/podcast.XML", true},
// Non-feed
{"https://example.com/stream.mp3", false},
{"/local/feed.xml", false}, // not a URL
{"", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsFeed(tt.path); got != tt.want {
t.Errorf("IsFeed(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}
func TestIsXiaoyuzhouEpisode(t *testing.T) {
tests := []struct {
path string
want bool
}{
{"https://www.xiaoyuzhoufm.com/episode/abc123", true},
{"https://xiaoyuzhoufm.com/episode/abc123", true},
{"https://m.xiaoyuzhoufm.com/episode/abc123", true},
// Not an episode path
{"https://www.xiaoyuzhoufm.com/podcast/abc123", false},
// Not xiaoyuzhou
{"https://example.com/episode/abc123", false},
{"/local/file.mp3", false},
}
for _, tt := range tests {
t.Run(tt.path, func(t *testing.T) {
if got := IsXiaoyuzhouEpisode(tt.path); got != tt.want {
t.Errorf("IsXiaoyuzhouEpisode(%q) = %v, want %v", tt.path, got, tt.want)
}
})
}
}