This applies to both the Table and child Playlists models. I'm doing my own idle handling already for searching, so we can rely on that instead of needing Gtk to do it. The benefit to this is that we can select playlists programmatically during startup, since we don't need to worry about the Table not being fully loaded yet. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
509 lines
22 KiB
Python
509 lines
22 KiB
Python
# Copyright 2022 (c) Anna Schumaker
|
|
"""Tests our ListStore and Playlist objects."""
|
|
import pathlib
|
|
import unittest
|
|
import unittest.mock
|
|
import emmental.db.playlist
|
|
import tests.util.playlist
|
|
from gi.repository import Gio
|
|
from gi.repository import Gtk
|
|
|
|
|
|
class TestPlaylistRow(unittest.TestCase):
|
|
"""Tests our shared Playlist Row."""
|
|
|
|
def setUp(self):
|
|
"""Set up common variables."""
|
|
self.table = Gio.ListStore()
|
|
self.table.add_track = unittest.mock.Mock(return_value=True)
|
|
self.table.remove_track = unittest.mock.Mock(return_value=True)
|
|
self.table.move_track_down = unittest.mock.Mock(return_value=True)
|
|
self.table.move_track_up = unittest.mock.Mock(return_value=True)
|
|
self.table.get_trackids = unittest.mock.Mock(return_value={1, 2, 3})
|
|
self.table.get_track_order = unittest.mock.Mock()
|
|
self.table.refilter = unittest.mock.Mock()
|
|
self.table.queue = emmental.db.idle.Queue()
|
|
self.table.update = unittest.mock.Mock(return_value=True)
|
|
|
|
self.playlist = emmental.db.playlist.Playlist(table=self.table,
|
|
propertyid=0,
|
|
name="Test Playlist")
|
|
self.track = emmental.db.tracks.Track(table=None, trackid=12345)
|
|
|
|
def test_init(self):
|
|
"""Test that the Playlist object is configured correctly."""
|
|
self.assertIsInstance(self.playlist, emmental.db.table.Row)
|
|
self.assertIsInstance(self.playlist.tracks,
|
|
emmental.db.tracks.TrackidSet)
|
|
self.assertEqual(self.playlist.table, self.table)
|
|
|
|
self.assertEqual(self.playlist.propertyid, 0)
|
|
self.assertEqual(self.playlist.name, "Test Playlist")
|
|
self.assertFalse(self.playlist.active)
|
|
|
|
self.assertEqual(self.playlist.loop, "None")
|
|
self.assertFalse(self.playlist.shuffle)
|
|
self.assertEqual(self.playlist.current_trackid, 0)
|
|
self.assertEqual(self.playlist.sort_order, "")
|
|
|
|
self.assertEqual(self.playlist.n_tracks, 0)
|
|
self.assertFalse(self.playlist.user_tracks)
|
|
self.assertFalse(self.playlist.tracks_loaded)
|
|
|
|
playlist2 = emmental.db.playlist.Playlist(table=self.table,
|
|
propertyid=1,
|
|
name="Test Name", active=1,
|
|
shuffle=True, loop="Track",
|
|
current_trackid=42,
|
|
sort_order="Track Number")
|
|
self.assertEqual(playlist2.propertyid, 1)
|
|
self.assertEqual(playlist2.name, "Test Name")
|
|
self.assertEqual(playlist2.current_trackid, 42)
|
|
self.assertEqual(playlist2.sort_order, "Track Number")
|
|
self.assertEqual(playlist2.loop, "Track")
|
|
self.assertTrue(playlist2.shuffle)
|
|
self.assertTrue(playlist2.active)
|
|
|
|
def test_children(self):
|
|
"""Test the child playlist properties."""
|
|
self.assertIsNone(self.playlist.child_set)
|
|
self.assertIsNone(self.playlist.children)
|
|
|
|
table = emmental.db.table.Table(None)
|
|
self.playlist.add_children(table, set())
|
|
|
|
self.assertIsInstance(self.playlist.child_set,
|
|
emmental.db.table.TableSubset)
|
|
self.assertEqual(self.playlist.child_set.table, table)
|
|
self.assertSetEqual(self.playlist.child_set.keyset.keys, set())
|
|
|
|
self.assertIsInstance(self.playlist.children, Gtk.FilterListModel)
|
|
self.assertEqual(self.playlist.children.get_filter(),
|
|
table.get_filter())
|
|
self.assertEqual(self.playlist.children.get_model(),
|
|
self.playlist.child_set)
|
|
self.assertFalse(self.playlist.children.get_incremental())
|
|
|
|
playlist2 = emmental.db.playlist.Playlist(table=self.table,
|
|
propertyid=2, name="Plist2")
|
|
playlist2.add_children(table, {1, 2, 3})
|
|
self.assertSetEqual(playlist2.child_set.keyset.keys, {1, 2, 3})
|
|
|
|
def test_parent(self):
|
|
"""Test the parent playlist property."""
|
|
self.assertIsNone(self.playlist.parent)
|
|
|
|
def test_do_update(self):
|
|
"""Test the do_update() function."""
|
|
for (prop, value) in [("name", "New Name"), ("propertyid", 12345),
|
|
("children", Gtk.FilterListModel()),
|
|
("n-tracks", 42), ("user-tracks", True),
|
|
("tracks-loaded", True),
|
|
("tracks-movable", True)]:
|
|
with self.subTest(property=prop):
|
|
self.playlist.set_property(prop, value)
|
|
self.table.update.assert_not_called()
|
|
|
|
for (prop, value) in [("active", True), ("loop", "Track"),
|
|
("shuffle", True), ("sort-order", "my order"),
|
|
("current-trackid", 42)]:
|
|
with self.subTest(property=prop):
|
|
self.playlist.set_property(prop, value)
|
|
self.table.update.assert_called_with(self.playlist,
|
|
prop, value)
|
|
|
|
def test_add_child(self):
|
|
"""Test adding a child playlist to the playlist."""
|
|
table = emmental.db.table.Table(None)
|
|
child1 = tests.util.table.MockRow(table=table, number=1)
|
|
child2 = tests.util.table.MockRow(table=table, number=2)
|
|
self.playlist.add_children(table, set())
|
|
|
|
self.playlist.add_child(child1)
|
|
self.assertIn(child1, self.playlist.child_set)
|
|
self.table.refilter.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
|
|
|
self.playlist.add_child(child2)
|
|
self.table.refilter.assert_called_once()
|
|
|
|
def test_add_track(self):
|
|
"""Test adding a track to the playlist."""
|
|
self.playlist.add_track(self.track, idle=True)
|
|
self.table.add_track.assert_called_with(self.playlist, self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.playlist._Playlist__add_track, self.track))
|
|
|
|
self.playlist.add_track(self.track)
|
|
self.table.add_track.assert_called_with(self.playlist, self.track)
|
|
self.assertIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.playlist.n_tracks, 1)
|
|
|
|
self.playlist.tracks.trackids.clear()
|
|
self.table.add_track.return_value = False
|
|
self.playlist.add_track(self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
|
|
def test_get_track_order(self):
|
|
"""Test the get_track_order() function."""
|
|
self.table.get_track_order.return_value = {1: 3, 2: 2, 3: 1}
|
|
self.assertDictEqual(self.playlist.get_track_order(),
|
|
{1: 3, 2: 2, 3: 1})
|
|
self.table.get_track_order.assert_called_with(self.playlist)
|
|
|
|
def test_has_child(self):
|
|
"""Test the playlist has_child() function."""
|
|
table = emmental.db.table.Table(None)
|
|
child = tests.util.table.MockRow(table=table, number=1)
|
|
self.playlist.add_children(table, set())
|
|
|
|
self.assertFalse(self.playlist.has_child(child))
|
|
self.playlist.add_child(child)
|
|
self.assertTrue(self.playlist.has_child(child))
|
|
self.playlist.remove_child(child)
|
|
self.assertFalse(self.playlist.has_child(child))
|
|
|
|
def test_has_track(self):
|
|
"""Test the playlist has_track() function."""
|
|
self.assertFalse(self.playlist.has_track(self.track))
|
|
self.playlist.add_track(self.track)
|
|
self.assertTrue(self.playlist.has_track(self.track))
|
|
self.playlist.remove_track(self.track)
|
|
self.assertFalse(self.playlist.has_track(self.track))
|
|
|
|
def test_move_tracks(self):
|
|
"""Test the move_track_up() and move_track_down() functions."""
|
|
self.assertFalse(self.playlist.tracks_movable)
|
|
|
|
self.assertTrue(self.playlist.move_track_down(self.track))
|
|
self.table.move_track_down.assert_called_with(self.playlist,
|
|
self.track)
|
|
|
|
self.assertTrue(self.playlist.move_track_up(self.track))
|
|
self.table.move_track_up.assert_called_with(self.playlist, self.track)
|
|
|
|
def test_remove_child(self):
|
|
"""Test removing a child playlist from the playlist."""
|
|
table = emmental.db.table.Table(None)
|
|
child1 = tests.util.table.MockRow(table=table, number=1)
|
|
child2 = tests.util.table.MockRow(table=table, number=2)
|
|
self.playlist.add_children(table, set())
|
|
self.playlist.add_child(child1)
|
|
self.playlist.add_child(child2)
|
|
self.table.refilter.reset_mock()
|
|
|
|
self.playlist.remove_child(child1)
|
|
self.assertFalse(child1 in self.playlist.child_set)
|
|
self.table.refilter.assert_not_called()
|
|
|
|
self.playlist.remove_child(child2)
|
|
self.table.refilter.assert_called_with(Gtk.FilterChange.MORE_STRICT)
|
|
|
|
def test_remove_track(self):
|
|
"""Test removing a track from the playlist."""
|
|
self.playlist.tracks.trackids.add(self.track.trackid)
|
|
self.playlist.remove_track(self.track, idle=True)
|
|
self.table.remove_track.assert_not_called()
|
|
self.assertIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.playlist._Playlist__remove_track, self.track))
|
|
|
|
self.playlist.remove_track(self.track)
|
|
self.table.remove_track.assert_called_with(self.playlist, self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
self.assertEqual(self.playlist.n_tracks, 0)
|
|
|
|
self.playlist.tracks.trackids.add(self.track.trackid)
|
|
self.table.remove_track.return_value = False
|
|
self.playlist.remove_track(self.track)
|
|
self.table.remove_track.assert_called_with(self.playlist, self.track)
|
|
self.assertNotIn(self.track, self.playlist.tracks)
|
|
|
|
def test_load_tracks(self):
|
|
"""Test loading a Playlist's Tracks."""
|
|
self.assertEqual(self.playlist.n_tracks, 0)
|
|
self.assertFalse(self.playlist.tracks_loaded)
|
|
|
|
self.assertTrue(self.playlist.load_tracks())
|
|
self.table.get_trackids.assert_called_with(self.playlist)
|
|
self.assertSetEqual(self.playlist.tracks.trackids, {1, 2, 3})
|
|
self.assertTrue(self.playlist.tracks_loaded)
|
|
|
|
self.table.get_trackids.reset_mock()
|
|
self.assertTrue(self.playlist.load_tracks())
|
|
self.table.get_trackids.assert_not_called()
|
|
|
|
def test_reload_tracks(self):
|
|
"""Test reloading a Playlist's Tracks."""
|
|
self.playlist.tracks_loaded = True
|
|
self.playlist.reload_tracks(idle=True)
|
|
self.table.get_trackids.assert_not_called()
|
|
self.assertEqual(self.table.queue[0], (self.playlist.load_tracks,))
|
|
|
|
self.playlist.tracks_loaded = True
|
|
self.playlist.reload_tracks()
|
|
self.table.get_trackids.assert_called_with(self.playlist)
|
|
self.assertSetEqual(self.playlist.tracks.trackids, {1, 2, 3})
|
|
|
|
|
|
class TestPlaylistTable(tests.util.TestCase):
|
|
"""Tests our Playlist Table."""
|
|
|
|
def setUp(self):
|
|
"""Set up common variables."""
|
|
super().setUp()
|
|
self.table = tests.util.playlist.MockTable(self.sql)
|
|
self.sql("DELETE FROM playlist_properties")
|
|
|
|
self.library = self.sql.libraries.create(pathlib.Path("/a/b"))
|
|
self.album = self.sql.albums.create("Test Album", "Artist", "2023-03")
|
|
self.medium = self.sql.media.create(self.album, "", number=1)
|
|
self.year = self.sql.years.create(2023)
|
|
self.track = self.sql.tracks.create(self.library,
|
|
pathlib.Path("/a/b/c.ogg"),
|
|
self.medium, self.year)
|
|
|
|
def test_treemodel(self):
|
|
"""Check that the table's treemodel was set up properly."""
|
|
self.assertIsInstance(self.table.treemodel, Gtk.TreeListModel)
|
|
self.assertEqual(self.table.treemodel.get_model(), self.table)
|
|
self.assertFalse(self.table.treemodel.get_passthrough())
|
|
self.assertFalse(self.table.treemodel.get_autoexpand())
|
|
|
|
root = self.table.create("Root")
|
|
self.assertIsNone(self.table._Table__create_tree(root))
|
|
root.children = Gtk.FilterListModel()
|
|
self.assertEqual(self.table._Table__create_tree(root), root.children)
|
|
|
|
def test_add_track(self):
|
|
"""Test adding a track to a playlist."""
|
|
self.assertTrue(self.table.system_tracks)
|
|
|
|
plist = self.table.create("Test Playlist")
|
|
self.library.deleting = True
|
|
self.assertFalse(self.table.add_track(plist, None))
|
|
self.assertFalse(self.table.add_track(plist, self.track))
|
|
|
|
self.library.deleting = False
|
|
self.assertTrue(self.table.add_track(plist, self.track))
|
|
cur = self.sql("SELECT COUNT(*) FROM system_tracks")
|
|
self.assertEqual(cur.fetchone()["COUNT(*)"], 1)
|
|
self.assertFalse(self.table.add_track(plist, self.track))
|
|
|
|
self.table.system_tracks = False
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.add_track(plist, self.track)
|
|
|
|
def test_construct(self):
|
|
"""Test constructing a new playlist."""
|
|
self.assertIsNone(self.table.active_playlist)
|
|
|
|
plist1 = self.table.construct(propertyid=1, name="Test")
|
|
self.assertIsInstance(plist1, emmental.db.playlist.Playlist)
|
|
self.assertEqual(plist1.table, self.table)
|
|
self.assertEqual(plist1.propertyid, 1)
|
|
self.assertEqual(plist1.name, "Test")
|
|
self.assertFalse(plist1.active)
|
|
|
|
plist2 = self.table.construct(propertyid=2, name="Test 2", active=True)
|
|
self.assertEqual(self.table.active_playlist, plist2)
|
|
self.assertEqual(self.sql.active_playlist, plist2)
|
|
self.assertTrue(plist2.active)
|
|
|
|
self.assertEqual(self.table.queue[0], (plist1.load_tracks,))
|
|
self.assertEqual(self.table.queue[1], (plist2.load_tracks,))
|
|
|
|
def test_get_sort_key(self):
|
|
"""Test getting a sort key for a playlist."""
|
|
plist = self.table.create("Playlist 1")
|
|
self.assertTupleEqual(self.table.get_sort_key(plist),
|
|
("playlist", "1"))
|
|
|
|
def test_clear(self):
|
|
"""Test clearing the active_playlist property."""
|
|
plist = self.table.create("Playlist 1")
|
|
self.table.active_playlist = plist
|
|
self.table.clear()
|
|
self.assertIsNone(self.table.active_playlist)
|
|
self.assertEqual(len(self.table), 0)
|
|
|
|
def test_delete(self):
|
|
"""Test deleting the active playlist with system tracks."""
|
|
self.table.system_tracks = True
|
|
plist = self.table.create("Test Playlist")
|
|
plist.add_track(self.track)
|
|
self.sql.set_active_playlist(plist)
|
|
|
|
self.assertTrue(self.table.delete(plist))
|
|
self.assertIsNone(self.table.active_playlist)
|
|
self.assertIsNone(self.sql.active_playlist)
|
|
self.assertNotIn(plist, self.table)
|
|
|
|
cur = self.sql("SELECT COUNT(*) FROM system_tracks")
|
|
self.assertEqual(cur.fetchone()["COUNT(*)"], 0)
|
|
|
|
def test_get_trackids(self):
|
|
"""Test getting the set of trackids for this playlist."""
|
|
self.assertTrue(self.table.system_tracks)
|
|
self.table._Table__autodelete = unittest.mock.Mock()
|
|
|
|
plist = self.table.create("Test Playlist")
|
|
self.assertSetEqual(self.table.get_trackids(plist), set())
|
|
self.table._Table__autodelete.assert_called_with(plist)
|
|
|
|
plist.add_track(self.track)
|
|
self.assertSetEqual(self.table.get_trackids(plist),
|
|
{self.track.trackid})
|
|
|
|
self.library.deleting = True
|
|
self.assertSetEqual(self.table.get_trackids(plist), set())
|
|
|
|
self.table.system_tracks = False
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.get_trackids(plist)
|
|
|
|
def test_get_track_order(self):
|
|
"""Test getting track sort keys for a playlist."""
|
|
plist = self.table.create("Test Playlist")
|
|
plist.sort_order = "my order"
|
|
self.sql.tracks.map_sort_order = unittest.mock.Mock()
|
|
self.sql.tracks.map_sort_order.return_value = {1: 3, 2: 2, 3: 1}
|
|
self.assertDictEqual(self.table.get_track_order(plist),
|
|
{1: 3, 2: 2, 3: 1})
|
|
self.sql.tracks.map_sort_order.assert_called_with("my order")
|
|
|
|
self.sql.tracks.map_sort_order.reset_mock()
|
|
plist.tracks_movable = True
|
|
self.assertDictEqual(self.table.get_track_order(plist),
|
|
{1: 3, 2: 2, 3: 1})
|
|
self.sql.tracks.map_sort_order.assert_called_with("my order")
|
|
|
|
plist.sort_order = "user"
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.get_track_order(plist)
|
|
|
|
def test_move_track_down(self):
|
|
"""Test moving tracks down in the sort order."""
|
|
plist = self.table.create("Test Playlist")
|
|
self.assertFalse(self.table.move_track_down(plist, self.track))
|
|
|
|
plist.tracks_movable = True
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.move_track_down(plist, self.track)
|
|
|
|
self.table.do_move_track_down = unittest.mock.Mock(return_value=False)
|
|
self.table.move_track_down(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "")
|
|
|
|
self.table.do_move_track_down.return_value = True
|
|
self.table.move_track_down(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "user")
|
|
|
|
def test_move_track_up(self):
|
|
"""Test moving tracks up in the sort order."""
|
|
plist = self.table.create("Test Playlist")
|
|
self.assertFalse(self.table.move_track_up(plist, self.track))
|
|
|
|
plist.tracks_movable = True
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.move_track_up(plist, self.track)
|
|
|
|
self.table.do_move_track_up = unittest.mock.Mock(return_value=False)
|
|
self.table.move_track_up(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "")
|
|
|
|
self.table.do_move_track_up.return_value = True
|
|
self.table.move_track_up(plist, self.track)
|
|
self.assertEqual(plist.sort_order, "user")
|
|
|
|
def test_refilter(self):
|
|
"""Test refiltering the playlist table."""
|
|
self.table.queue.push(unittest.mock.Mock())
|
|
|
|
with unittest.mock.patch.object(self.table.get_filter(),
|
|
"changed") as mock_changed:
|
|
self.table.refilter(Gtk.FilterChange.MORE_STRICT)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.table._Table__refilter,
|
|
Gtk.FilterChange.MORE_STRICT))
|
|
mock_changed.assert_not_called()
|
|
|
|
self.table.refilter(Gtk.FilterChange.LESS_STRICT)
|
|
self.assertEqual(self.table.queue[0],
|
|
(self.table._Table__refilter,
|
|
Gtk.FilterChange.LESS_STRICT))
|
|
mock_changed.assert_not_called()
|
|
|
|
self.table.queue.complete()
|
|
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
|
|
|
def test_remove_track(self):
|
|
"""Test adding a track to a playlist."""
|
|
self.assertTrue(self.table.system_tracks)
|
|
self.table._Table__autodelete = unittest.mock.Mock()
|
|
|
|
plist = self.table.create("Test Playlist")
|
|
plist.add_track(self.track)
|
|
self.assertTrue(self.table.remove_track(plist, self.track))
|
|
cur = self.sql("SELECT COUNT(*) FROM system_tracks")
|
|
self.assertEqual(cur.fetchone()["COUNT(*)"], 0)
|
|
self.assertFalse(self.table.remove_track(plist, self.track))
|
|
self.table._Table__autodelete.assert_called_with(plist)
|
|
|
|
self.table.system_tracks = False
|
|
with self.assertRaises(NotImplementedError):
|
|
self.table.remove_track(plist, self.track)
|
|
|
|
def test_update(self):
|
|
"""Test updating playlist properties."""
|
|
plist1 = self.table.create("Test Playlist 1")
|
|
plist2 = self.table.create("Test Playlist 2")
|
|
plist1.active = True
|
|
plist1.loop = "Track"
|
|
plist1.shuffle = True
|
|
plist1.sort_order = "Track Number"
|
|
plist1.current_trackid = self.track.trackid
|
|
|
|
self.assertEqual(self.table.active_playlist, plist1)
|
|
row = self.sql("""SELECT active, loop, shuffle,
|
|
current_trackid, sort_order
|
|
FROM playlist_properties
|
|
WHERE propertyid=?""", plist1.propertyid).fetchone()
|
|
self.assertEqual(row["active"], True)
|
|
self.assertEqual(row["current_trackid"], self.track.trackid)
|
|
self.assertEqual(row["sort_order"], "Track Number")
|
|
self.assertEqual(row["loop"], "Track")
|
|
self.assertTrue(row["shuffle"])
|
|
|
|
plist1.current_trackid = 0
|
|
plist2.active = True
|
|
self.assertEqual(self.table.active_playlist, plist2)
|
|
row = self.sql("""SELECT active, current_trackid
|
|
FROM playlist_properties WHERE propertyid=?""",
|
|
plist1.propertyid).fetchone()
|
|
self.assertEqual(row["active"], False)
|
|
self.assertEqual(row["current_trackid"], None)
|
|
|
|
def test_autodelete(self):
|
|
"""Test automatically deleting playlists."""
|
|
plist = self.table.create("Test Playlist")
|
|
self.table.queue.cancel()
|
|
|
|
self.assertFalse(self.table.autodelete)
|
|
self.table._Table__autodelete(plist)
|
|
self.assertFalse(self.table.queue.running)
|
|
self.assertIn(plist, self.table)
|
|
|
|
self.table.autodelete = True
|
|
self.table._Table__autodelete(plist)
|
|
self.assertTupleEqual(self.table.queue[0],
|
|
(self.table._Table__do_autodelete, plist))
|
|
|
|
plist.n_tracks = 1
|
|
self.assertTrue(self.table._Table__do_autodelete(plist))
|
|
self.assertIn(plist, self.table)
|
|
|
|
plist.n_tracks = 0
|
|
self.assertTrue(self.table._Table__do_autodelete(plist))
|
|
self.assertNotIn(plist, self.table)
|