aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_video_default_season.py
blob: 898d3f52e0212fd7dbb4c5917951f4200a206d0c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""
Which season a show's detail modal opens on.

Reported live: a show with a dozen seasons opened on season 6. Every season was
in the list and none was missing — the *default* was wrong, which is the harder
kind to notice and the easier kind to shrug at.

`VideoDetailModal` took it from `repEntry.season`. `repEntry` is the show's
"representative entry", and the poster grid picks it as
`episodes.find((e) => e.thumb_hash) || episodes[0]` — the first episode that
has a thumbnail, so the card has a fallback frame when TMDB has no poster
(`6af05ab`). The season tabs then read that same entry as "the episode the
reader is looking at" (`0b0da86`), which it never was on that path. With the
episodes sorted by (season, episode), a show whose first five seasons had no
thumbnail yet — a partial enrichment pass, or ffmpeg failing on those
particular files — hands back a season-6 episode, and the modal opens there.

Two different meanings of "representative" that were never the same thing. The
default is now `defaultSeason(show)`, which reads the season list and nothing
else, and this holds it to that: no input here carries a thumbnail at all.

The function is read out of `video-app.js` rather than duplicated — a copy
would keep passing after the original changed.
"""

import json
import re
import shutil
import subprocess
from pathlib import Path

import pytest

STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
APP = STATIC / "video-app.js"

pytestmark = pytest.mark.skipif(
    shutil.which("node") is None or not APP.exists(),
    reason="node or the SPA sources are not available")

BLOCK = re.compile(r"^function defaultSeason\(show\) \{.*?^\}", re.M | re.S)


@pytest.fixture(scope="module")
def source():
    m = BLOCK.search(APP.read_text())
    assert m, ("defaultSeason is no longer where this test reads it from — the "
               "season a show opens on is untested until this is fixed")
    return m.group(0)


def _default(tmp_path, source, seasons):
    """`seasons` as `buildSeasons` returns them: ascending, specials first."""
    show = None if seasons is None else {
        "seasons": [{"season": n, "episodes": []} for n in seasons]}
    script = tmp_path / "case.js"
    script.write_text(f"""
        {source}
        console.log(JSON.stringify(defaultSeason({json.dumps(show)})));
    """)
    proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
    assert proc.returncode == 0, proc.stderr
    return json.loads(proc.stdout)


def test_a_show_opens_on_its_first_season(tmp_path, source):
    assert _default(tmp_path, source, [1, 2, 3, 4, 5, 6, 7, 8]) == 1


def test_specials_do_not_become_the_default(tmp_path, source):
    """`buildSeasons` sorts ascending, so season 0 is first in the list and
    would win a naive `seasons[0]`. Nobody means the extras by "the start"."""
    assert _default(tmp_path, source, [0, 1, 2, 3]) == 1


def test_a_show_of_nothing_but_specials_opens_on_them(tmp_path, source):
    """The one case where season 0 is the right answer: there is no other."""
    assert _default(tmp_path, source, [0]) == 0


def test_a_show_that_starts_at_season_two_opens_there(tmp_path, source):
    """Half a show on disk is ordinary. The first season *present* is the
    answer, not the number 1, which would select a season that is not there
    and filter the episode list down to nothing."""
    assert _default(tmp_path, source, [2, 3, 4]) == 2


def test_the_answer_does_not_depend_on_which_episode_has_a_thumbnail(tmp_path, source):
    """The defect, as the shape of the input.

    `defaultSeason` is handed the season list and nothing else — there is no
    episode, no `thumb_hash`, no representative entry to be misled by. That is
    the fix: the reported show opened on season 6 because its first five
    seasons had no thumbnails yet, and no argument here can carry that.
    """
    reported = list(range(1, 9))
    assert _default(tmp_path, source, reported) == 1
    assert _default(tmp_path, source, list(reversed(reported))) == 1, (
        "the first *season*, not the first entry in whatever order the list "
        "arrived in")


@pytest.mark.parametrize("show", [None, []])
def test_no_seasons_means_no_selection(tmp_path, source, show):
    """A movie has no `show` at all, and an empty one must not throw: the
    modal renders no picker and `selectedSeason` stays null."""
    assert _default(tmp_path, source, show) is None