Coller un lien de playlist (youtube.com/playlist?list=...) dans le champ URL affiche toutes ses videos dans la meme zone de resultats que la recherche de chaine ; chaque ligne a son bouton "Ajouter", et un bouton "Tout ajouter a la file" ajoute le reste d'un coup. Serveur : - GET /api/playlist (PlaylistDiscoveryService) : yt-dlp --flat-playlist -J. -J plutot que -j : le titre de la playlist n'existe qu'a la racine, et une playlist vide reste un resultat valide. - playlistUrl.ts : n'accepte que les liens youtube.com/playlist?list=<id> et reconstruit l'URL canonique depuis le seul identifiant valide (rien d'autre de l'entree n'atteint yt-dlp). Un lien watch?v=...&list=... reste une video (--no-playlist etait deja passe au telechargement). - videoSummary.ts : conversion d'une entree --flat-playlist en resume de video, extraite de ChannelDiscoveryService pour etre partagee (chaine et playlist parsent desormais identiquement). ChannelVideoSummary devient VideoSummary (et ChannelVideoDto -> VideoDto cote client). Client : - Le champ URL accepte video ou playlist (isPlaylistUrl aiguille) ; zone de resultats partagee (createResultsView). - "Tout ajouter" : POST /api/queue sequentiels (ordre conserve, pas de doublon avec les lignes deja ajoutees). Un echec n'interrompt pas le lot : nombre d'echecs + premier message affiches, seules les lignes en echec redeviennent ajoutables. Le mode est lu une seule fois pour tout le lot. - Miniatures de resultats en loading="lazy" (une playlist peut compter des centaines de videos). Verifie contre le vrai yt-dlp (2026.07.04) : forme reelle de -J relevee (fixtures de test calquees dessus), playlist de 1607 videos listee en ~11 s (delai fixe a 120 s). Teste dans le navigateur reel : playlist de 13 videos, ajout unitaire, "tout ajouter" (13 elements, aucun doublon), echec partiel simule + reprise, playlist inexistante, lien video avec list=, recherche de chaine (non-regression). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
32 lines
1.3 KiB
TypeScript
32 lines
1.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { isPlaylistUrl } from '../src/playlistUrl';
|
|
|
|
describe('isPlaylistUrl', () => {
|
|
it.each([
|
|
'https://www.youtube.com/playlist?list=PLKDZ1ig0uz-U',
|
|
'https://youtube.com/playlist?list=PLKDZ1ig0uz-U',
|
|
'https://m.youtube.com/playlist?list=PLKDZ1ig0uz-U',
|
|
'https://music.youtube.com/playlist?list=OLAK5uy_abc',
|
|
'https://www.youtube.com/playlist?si=abc&list=PLKDZ1ig0uz-U&index=2',
|
|
' https://www.youtube.com/playlist?list=PLKDZ1ig0uz-U ',
|
|
])('reconnaît %j comme une playlist', (input) => {
|
|
expect(isPlaylistUrl(input)).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
['un lien de vidéo', 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'],
|
|
[
|
|
'un lien de vidéo dans une playlist',
|
|
'https://www.youtube.com/watch?v=dQw4w9WgXcQ&list=PLabc',
|
|
],
|
|
['un lien court dans une playlist', 'https://youtu.be/dQw4w9WgXcQ?list=PLabc'],
|
|
['une playlist sans identifiant', 'https://www.youtube.com/playlist'],
|
|
['une playlist à identifiant vide', 'https://www.youtube.com/playlist?list='],
|
|
['un autre domaine', 'https://example.com/playlist?list=PLabc'],
|
|
['un schéma http', 'http://www.youtube.com/playlist?list=PLabc'],
|
|
['du texte quelconque', 'pas une url'],
|
|
['une chaîne vide', ''],
|
|
])('ne prend pas %s pour une playlist', (_label, input) => {
|
|
expect(isPlaylistUrl(input)).toBe(false);
|
|
});
|
|
});
|