diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_playlist_ui.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_playlist_ui.py | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_playlist_ui.py b/packages/meshbay-hub/tests/test_playlist_ui.py new file mode 100644 index 0000000..c449dc7 --- /dev/null +++ b/packages/meshbay-hub/tests/test_playlist_ui.py @@ -0,0 +1,108 @@ +""" +The playlist menus, pressed in a real browser. + +The store is covered against a stubbed node, and the merge and the sealing by +their own tests. None of that reaches the part a person touches: whether the +toolbar button opens a menu at all, whether naming a playlist works (Electron +has no `prompt` — it *throws*, which is how the Files toolbar's New folder +button came to do nothing), and whether "add to playlist" on a cover puts the +right tracks in the right playlist. + +The probe renders the shipped `GroupPage`, `MusicPlayerBar` and playlist menus +and presses the real controls. Labels come back in whatever locale the browser +picked, so what is asserted is shape and behaviour — counts, order, and what +ended up in the store — rather than English strings. +""" + +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +HARNESS = Path(__file__).parent / "harness" / "playlist_ui_probe.py" +STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" + +pytestmark = pytest.mark.skipif( + shutil.which("google-chrome") is None or not (STATIC / "playlist-menu.js").exists(), + reason="Chrome or the SPA sources are not available") + + +@pytest.fixture(scope="module") +def steps(): + proc = subprocess.run(["python3", str(HARNESS)], + capture_output=True, text=True, timeout=300) + assert proc.returncode == 0, f"probe failed: {proc.stdout}{proc.stderr}" + out = json.loads(proc.stdout) + assert "error" not in out, out + assert not out.get("logs"), f"the page logged: {out['logs']}" + return {s["step"]: s for s in out["steps"]} + + +def test_one_button_opens_the_five_playlist_verbs_in_order(steps): + """Load, create, delete, remove a track — the order asked for — and Sync + now, which has to live somewhere and this is the only playlist surface.""" + assert len(steps["toolbar menu"]["items"]) == 5 + + +def test_a_playlist_is_named_in_a_field_and_not_a_prompt(steps): + """`window.prompt` throws in Electron and does not return null, so it is + banned outright (`test_no_prompt_in_the_spa.py`). Anything that needs typed + input needs a field, and this is the one that does.""" + s = steps["created"] + assert s["modalGone"] is True, "the modal stayed open, so nothing was saved" + assert [p["name"] for p in s["lists"]] == ["Soirée"] + assert s["lists"][0]["count"] == 0 + + +def test_the_cover_menu_carries_the_queue_verbs_and_add_to_playlist(steps): + assert len(steps["cover menu"]["items"]) == 4 + + +def test_favourites_is_offered_first_before_it_has_ever_been_used(steps): + """The reserved playlist is materialised on first use, so the submenu has + it on a fresh account — and has it first, as the design promises.""" + items = steps["add-to submenu"]["items"] + submenu = items[4:] + assert len(submenu) == 3, submenu + assert submenu[1] == "Soirée" + + +def test_adding_an_album_from_its_cover_puts_its_tracks_in_the_playlist(steps): + s = steps["added to the playlist"] + assert s["lists"] == [{"name": "Soirée", "count": 3}] + assert s["note"], "nothing said it had happened" + + +def test_loading_a_playlist_replaces_the_queue_with_its_tracks(steps): + """Below `onPlayQueue` a playlist and an album are indistinguishable, which + is why auto-advance, shuffle and prefetch are unchanged by construction.""" + assert steps["loaded into the queue"]["play"] == ["A2-t1", "A2-t2", "A2-t3"] + + +def test_the_tracklist_submenu_is_two_levels_and_fetched_when_expanded(steps): + """As asked: the playlist, then its tracks. The second level is read from + IndexedDB when it is opened — building it eagerly would read every + playlist's tracks to draw a menu nobody may open.""" + items = steps["the tracklist submenu"]["items"] + assert "A2-t1" in items and "A2-t3" in items + assert items.index("A2-t1") > 0 + # The tracks sit under their playlist, between it and the next top-level + # item — expanded in place rather than in a flyout. + assert items[-1] == steps["toolbar menu"]["items"][-1] + + +def test_removing_a_track_removes_that_one(steps): + s = steps["track removed"] + assert s["lists"] == [{"name": "Soirée", "count": 2}] + assert s["tracks"] == ["A2-t1", "A2-t3"], "the wrong track was removed" + + +def test_deleting_a_playlist_asks_first(steps): + """A deletion is a tombstone: there is nothing in the interface that undoes + it. `confirm` and not a component — Electron implements it and a dozen + places in this SPA already use it.""" + s = steps["deleted"] + assert s["asked"] is True, "a playlist was deleted without asking" + assert s["lists"] == [] |