soulsync/tests/test_video_download_auto.py
BoulderBadgeDad 9a3ca6ba4e video downloads: header 'Auto' picks ONE best across all sources; fix YT playlist leak
Auto: replaced 'Manual all' + 'Auto all' (which fired auto on every source = up to
one download PER source = duplicate copies) with a single header 'Auto' button. It
searches every source, waits for them all to settle, compares the accepted+grabbable
hits across ALL sources by quality-profile score (tie-break on availability), and
grabs exactly ONE winner — the chosen row gets the auto ring + live tracker. Per-source
Manual/Auto buttons are unchanged.

Bug fix: viewing a YouTube channel populated the playlist section in the SHARED
show-detail DOM; opening a real movie/show afterward still showed those playlists,
because ytResetPlaylists() used the kind-scoped q() (pointing at the wrong root on a
movie load) and wasn't called on normal loads. Now it targets the show subpage
directly and runs from resetExtras() (every detail load), so stale playlists are
always cleared.

node --check clean; 20 wiring/regression tests green; all video-only.
2026-06-20 10:48:55 -07:00

80 lines
3.6 KiB
Python

"""Video Download modal — per-source 'Auto' (search + auto-grab the best) wiring.
The download view already has a Manual search (you pick a release). This adds an
Auto button per source that runs the SAME search and then grabs the best release
for the quality profile. The "best" comes for free: the backend returns hits
sorted accepted→score→availability (see test_video_api.py
::test_downloads_search_endpoint_ranks_and_filters asserting results[0].accepted),
so Auto just takes the first accepted hit that has an uploader.
String-contract level (like tests/test_video_automations_builder.py) so a refactor
can't silently unwire the auto path or make manual + auto diverge.
"""
from __future__ import annotations
from pathlib import Path
_ROOT = Path(__file__).resolve().parent.parent
_VIEW = (_ROOT / "webui" / "static" / "video" / "video-download-view.js").read_text(encoding="utf-8")
_CSS = (_ROOT / "webui" / "static" / "video" / "video-side.css").read_text(encoding="utf-8")
def test_each_source_has_manual_and_auto_buttons():
assert 'data-vdl-search="' in _VIEW # Manual (pick yourself)
assert 'data-vdl-auto="' in _VIEW # Auto (best pick)
# Renamed for clarity: Manual vs Auto (icon + label now split into spans).
assert '>Manual<' in _VIEW and '>Auto<' in _VIEW
# The harsh lightning was redesigned out in favour of a monochrome sparkle.
assert '' not in _VIEW
assert 'vdl-btn-ic--auto' in _VIEW
def test_header_has_single_auto_best_button():
# The old "Manual all" + "Auto all" pair is replaced by ONE header "Auto" that
# searches every source and grabs the single best release across all of them.
assert 'data-vdl-auto-best' in _VIEW
assert 'data-vdl-auto-all' not in _VIEW # the per-source-grab-all footgun is gone
assert 'data-vdl-search-all' not in _VIEW
assert 'Manual all' not in _VIEW and 'Auto all' not in _VIEW
def test_auto_best_picks_one_winner_across_all_sources():
assert 'function _autoBest(' in _VIEW
assert 'function _grabBestAcross(' in _VIEW
# it compares accepted+grabbable hits across every source by profile score…
assert "r.accepted && r.username" in _VIEW
assert '(r.score || 0) > (best.score || 0)' in _VIEW
# …and grabs exactly ONE (no per-source loop of grabs)
assert _VIEW.count('beginTracking(') >= 3 # def + doGrab + _autoPick (+ _grabBestAcross)
def test_click_handler_routes_auto_best_and_per_source():
assert "closest('[data-vdl-auto-best]')" in _VIEW
assert "closest('[data-vdl-auto]')" in _VIEW # per-source auto still works
def test_search_threads_a_done_callback():
# Auto needs to act when the search SETTLES, not on the first tick.
assert 'function searchInto(container, resultsEl, params, triggerRows, onDone)' in _VIEW
assert 'function _pollSearch(resultsEl, params, id, triggerRows, pollMs, onDone)' in _VIEW
assert 'if (onDone) onDone();' in _VIEW
def test_autopick_takes_first_accepted_with_uploader():
assert 'function _autoPick(' in _VIEW
# picks the first accepted hit that has an uploader (best, since pre-sorted)
assert 'rows[i].accepted && rows[i].username' in _VIEW
def test_manual_and_auto_share_one_grab_path():
# Both go through buildGrabPayload + sendGrab so they can't diverge.
assert 'function buildGrabPayload(' in _VIEW
assert 'function sendGrab(' in _VIEW
assert _VIEW.count('sendGrab(buildGrabPayload(') >= 2 # doGrab + _autoPick
def test_auto_button_is_styled():
assert '.vdl-src-auto' in _CSS
assert '.vdl-res--auto' in _CSS # the chosen card gets a ring
assert '.vdl-src-actions' in _CSS