diff --git a/tests/test_video_wishlist_clear.py b/tests/test_video_wishlist_clear.py index 9e03b0f4..6e154a75 100644 --- a/tests/test_video_wishlist_clear.py +++ b/tests/test_video_wishlist_clear.py @@ -130,3 +130,16 @@ def test_clear_button_wired_for_all_tabs(): assert "kind: kind" in _JS # clears the active tab # shown only when the active tab has items assert "function updateClearBtn(" in _JS + + +def test_nav_badge_uses_the_endpoint_total_not_a_partial_sum(): + # regression: movie/episode and YouTube counts load separately, so neither setter may + # compute the nav badge from its partial state (it 'switches' to the TV-only number). + # Both must defer to the authoritative /wishlist/counts endpoint via refreshBadge(). + import re + sc = _JS[_JS.index("function setCounts("):_JS.index("function setYtCounts(")] + syc = _JS[_JS.index("function setYtCounts("):] + syc = syc[:syc.index("function ", 1)] + assert "refreshBadge();" in sc and "refreshBadge();" in syc + assert "state.counts.movie + state.counts.episode" not in _JS # old partial-total math gone + assert "/api/video/wishlist/counts" in _JS # the endpoint is the source diff --git a/webui/static/video/video-wishlist.js b/webui/static/video/video-wishlist.js index 8294bc2f..953295da 100644 --- a/webui/static/video/video-wishlist.js +++ b/webui/static/video/video-wishlist.js @@ -299,12 +299,18 @@ } // ── counts / badges / pager ─────────────────────────────────────────────── + // The nav badge is the WHOLE wishlist (movies + episodes + YouTube videos). movie/episode + // and YouTube counts arrive from SEPARATE loads, and on the dashboard only one of them + // runs — so neither setter can compute the whole total from its partial state without + // clobbering the other (the bug: badge shows the right number, then 'switches' to the + // TV-only count). The endpoint /wishlist/counts is the single source of truth for the + // grand total, so the setters just (re)sync the nav badge from it. function setCounts(counts) { state.counts = { movie: (counts && counts.movie) || 0, show: (counts && counts.show) || 0, episode: (counts && counts.episode) || 0 }; var cm = $('[data-vwsh-count-movie]'); if (cm) cm.textContent = state.counts.movie; var cs = $('[data-vwsh-count-show]'); if (cs) cs.textContent = state.counts.show; - updateBadges(counts && counts.total != null ? counts.total : (state.counts.movie + state.counts.episode)); + refreshBadge(); // authoritative grand total updateSub(); updateClearBtn(); } @@ -312,6 +318,7 @@ state.ytChannel = (counts && counts.channel) || 0; state.ytVideo = (counts && counts.video) || 0; var cy = $('[data-vwsh-count-youtube]'); if (cy) cy.textContent = state.ytVideo; + refreshBadge(); // authoritative grand total updateSub(); updateClearBtn(); }