diff --git a/emmental/__init__.py b/emmental/__init__.py index 5c4d3a5..61b4902 100644 --- a/emmental/__init__.py +++ b/emmental/__init__.py @@ -9,6 +9,7 @@ from . import header from . import mpris2 from . import nowplaying from . import options +from . import playlist from . import sidebar from . import window from gi.repository import GObject @@ -25,6 +26,7 @@ class Application(Adw.Application): """Our custom Adw.Application.""" db = GObject.Property(type=db.Connection) + factory = GObject.Property(type=playlist.Factory) mpris = GObject.Property(type=mpris2.Connection) player = GObject.Property(type=audio.Player) win = GObject.Property(type=window.Window) @@ -162,6 +164,13 @@ class Application(Adw.Application): self.mpris.player.connect("SetPosition", self.__set_position) self.mpris.player.connect("Stop", self.player.stop) + def connect_playlist_factory(self) -> None: + """Connect the playlist factory properties.""" + self.db.playlists.bind_property("previous", + self.factory, "db-previous") + self.win.sidebar.bind_property("selected-playlist", + self.factory, "db-visible") + def do_handle_local_options(self, opts: GLib.VariantDict) -> int: """Handle any command line options.""" if opts.contains("version"): @@ -175,6 +184,7 @@ class Application(Adw.Application): Adw.Application.do_startup(self) self.db = db.Connection() self.mpris = mpris2.Connection() + self.factory = playlist.Factory(self.db) self.player = audio.Player() gsetup.add_style() @@ -186,6 +196,7 @@ class Application(Adw.Application): self.win = self.build_window() self.add_window(self.win) self.connect_mpris2() + self.connect_playlist_factory() def do_activate(self) -> None: """Handle the Adw.Application::activate signal.""" diff --git a/emmental/playlist/__init__.py b/emmental/playlist/__init__.py new file mode 100644 index 0000000..11f7642 --- /dev/null +++ b/emmental/playlist/__init__.py @@ -0,0 +1,27 @@ +# Copyright 2023 (c) Anna Schumaker. +"""An object for managing the visible and active playlists.""" +from gi.repository import GObject +from .. import db + + +class Factory(GObject.GObject): + """Our playlist model factory.""" + + sql = GObject.Property(type=db.Connection) + + db_active = GObject.Property(type=db.playlist.Playlist) + db_previous = GObject.Property(type=db.playlist.Playlist) + db_visible = GObject.Property(type=db.playlist.Playlist) + + def __init__(self, sql: db.Connection): + """Initialize the Playlist Factory.""" + super().__init__(sql=sql) + self.sql.bind_property("active-playlist", self, "db-active") + self.connect("notify", self.__notify_db_playlists) + + def __notify_db_playlists(self, factory: GObject.GObject, param) -> None: + match param.name: + case "db-active" | "db-previous" | "db-visible": + plist = self.get_property(param.name) + name = "" if plist is None else plist.name + print(f"factory: {param.name[3:]} playlist is:", name) diff --git a/tests/playlist/__init__.py b/tests/playlist/__init__.py new file mode 100644 index 0000000..d645978 --- /dev/null +++ b/tests/playlist/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Needed to fix the unique basename problem with pytest.""" diff --git a/tests/playlist/test_factory.py b/tests/playlist/test_factory.py new file mode 100644 index 0000000..0b7e67d --- /dev/null +++ b/tests/playlist/test_factory.py @@ -0,0 +1,63 @@ +# Copyright 2023 (c) Anna Schumaker. +"""Test our Playlist Manager object.""" +import io +import unittest.mock +import emmental.playlist +import tests.util +from gi.repository import GObject + + +@unittest.mock.patch("sys.stdout", new_callable=io.StringIO) +class TestFactory(tests.util.TestCase): + """Test the Playlist Factory class.""" + + def setUp(self): + """Set up common variables.""" + super().setUp() + self.sql.playlists.load(now=True) + self.user_plist = self.sql.playlists.create("User Playlist") + self.factory = emmental.playlist.Factory(self.sql) + + def test_init(self, mock_stdout: io.StringIO): + """Check that the Playlist Factory is set up properly.""" + self.assertIsInstance(self.factory, GObject.GObject) + self.assertEqual(self.factory.sql, self.sql) + + def test_active(self, mock_stdout: io.StringIO): + """Test the active playlist property.""" + self.assertIsNone(self.factory.db_active) + + self.sql.set_active_playlist(self.user_plist) + self.assertEqual(self.factory.db_active, self.user_plist) + self.assertRegex(mock_stdout.getvalue(), + "factory: active playlist is: User Playlist") + + self.sql.set_active_playlist(None) + self.assertIsNone(self.factory.db_active) + self.assertRegex(mock_stdout.getvalue(), "active playlist is: ") + + def test_previous(self, mock_stdout: io.StringIO): + """Test the previous playlist property.""" + self.assertIsNone(self.factory.db_previous) + + self.factory.db_previous = self.sql.playlists.previous + self.assertEqual(self.factory.db_previous, self.sql.playlists.previous) + self.assertRegex(mock_stdout.getvalue(), + r"factory: previous playlist is: Previous Tracks") + + self.factory.db_previous = None + self.assertRegex(mock_stdout.getvalue(), + "factory: previous playlist is: ") + + def test_visible(self, mock_stdout: io.StringIO): + """Test the visible playlist property.""" + self.assertIsNone(self.factory.db_visible) + + self.factory.db_visible = self.user_plist + self.assertEqual(self.factory.db_visible, self.user_plist) + self.assertRegex(mock_stdout.getvalue(), + "factory: visible playlist is: User Playlist") + + self.factory.db_visible = None + self.assertRegex(mock_stdout.getvalue(), + "factory: visible playlist is: ") diff --git a/tests/test_emmental.py b/tests/test_emmental.py index 57c8221..53e1c0c 100644 --- a/tests/test_emmental.py +++ b/tests/test_emmental.py @@ -45,6 +45,7 @@ class TestEmmental(unittest.TestCase): """Test that the startup signal works as expected.""" self.assertIsNone(self.application.db) self.assertIsNone(self.application.mpris) + self.assertIsNone(self.application.factory) self.assertIsNone(self.application.player) self.assertIsNone(self.application.win) @@ -53,6 +54,8 @@ class TestEmmental(unittest.TestCase): self.assertIsInstance(self.application.mpris, emmental.mpris2.Connection) self.assertIsInstance(self.application.player, emmental.audio.Player) + self.assertIsInstance(self.application.factory, + emmental.playlist.Factory) self.assertIsInstance(self.application.win, emmental.window.Window) mock_startup.assert_called() @@ -154,6 +157,25 @@ class TestEmmental(unittest.TestCase): self.assertEqual(win.sidebar.sql, self.application.db) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) + def test_playlist_factory(self, mock_stdout: io.StringIO): + """Test that the Playlist Factory is wired up properly.""" + self.application.db = emmental.db.Connection() + self.application.player = emmental.audio.Player() + self.application.factory = emmental.playlist.Factory( + self.application.db) + self.application.win = self.application.build_window() + self.application.connect_playlist_factory() + + self.application.db.playlists.load(now=True) + playlist = self.application.db.playlists.create("Test Playlist") + + self.application.win.sidebar.selected_playlist = playlist + self.assertEqual(self.application.factory.db_visible, playlist) + + self.assertEqual(self.application.factory.db_previous, + self.application.db.playlists.previous) + @unittest.mock.patch("sys.stdout", new_callable=io.StringIO) def test_replaygain(self, mock_stdout: io.StringIO): """Test setting replaygain modes."""