aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_playlist_ui.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_playlist_ui.py')
-rw-r--r--packages/meshbay-hub/tests/test_playlist_ui.py108
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"] == []